async def manual_login(self): client = await Client.create(headless=False) page = await client.new_page() await client.stealth(page) await client.goto("/login", page) await page.waitForSelector(".profile", options={"timeout": 0}) await page.hover(".profile") await page.waitFor(".profile-actions > li:first-child") # going to "View profile" page await page.click(".profile-actions > li:first-child") await page.waitForSelector(".share-title", options={"timeout": 0}) username = await page.Jeval(".share-title", pageFunction="element => element.textContent") username = username.strip() sub_title = await page.Jeval( ".share-sub-title", pageFunction="element => element.textContent", ) logger.info(f"🔑 Logged as @{username} aka {sub_title}") cookies = await page.cookies() loaders.write( f"{settings.HOME_DIR}/settings.toml", {**BASE_SETTINGS, **{"COOKIES": json.dumps(cookies), "USERNAME": username}}, env="default", ) await client.browser.close()
async def trending(self, amount: int = 50, lang: str = "en") -> List[FeedItem]: logger.info("📈 Getting trending items") items = await Trending(client=self.client).feed(amount=amount, lang=lang) logger.info(f"📹 Found {len(items)} videos") _trending = FeedItems(__root__=items) return _trending.__root__
async def user_feed(self, username: str, amount: int = 50) -> List[FeedItem]: username = f"@{username.lstrip('@')}" logger.info(f"📈 Getting {username} feed") items = await User(client=self.client).feed(username=username, amount=amount) logger.info(f"📹 Found {len(items)} videos") feed = FeedItems(__root__=items) return feed.__root__
async def __aexit__( self, exc_type: typing.Type[BaseException] = None, exc_value: BaseException = None, traceback: TracebackType = None, ) -> None: logger.debug("🤔Trying to close browser..") await self.client.browser.close() logger.debug("✋ Browser successfully closed") logger.info( "✋ TikTokPy finished working. Session lasted: {}", humanize.naturaldelta(datetime.now() - self.started_at), )
def load_or_create_settings(path: Optional[str]): path = path or DEFAULT_PATH if not Path(path).exists(): default_settings_path = str(Path.cwd() / Path(DEFAULT_PATH)) logger.info( f'🔧 Settings in path directory not found "{Path(path).absolute()}". ' f"I'll create default settings here: {default_settings_path}", ) Path(path).parent.mkdir(parents=True, exist_ok=True) loaders.write(default_settings_path, BASE_SETTINGS, env="default") settings.load_file(path=path) logger.info("🔧 Settings successfully loaded")
async def unlike(self, username: str, video_id: str): page: Page = await self.client.new_page( blocked_resources=["image", "media", "font"]) logger.debug(f"ЁЯСе Unlike video id {video_id} of @{username}") like_info_queue: asyncio.Queue = asyncio.Queue(maxsize=1) video_info_queue: asyncio.Queue = asyncio.Queue(maxsize=1) page.on( "response", lambda res: asyncio.create_task( catch_response_info(res, video_info_queue, "/item/detail"), ), ) page.on( "response", lambda res: asyncio.create_task( catch_response_info(res, like_info_queue, "/commit/item/digg"), ), ) logger.info( f"ЁЯзн Going to @{username}'s video {video_id} page for unlike") await self.client.goto( f"/@{username}/video/{video_id}", page=page, options={"waitUntil": "networkidle0"}, ) video_info = await video_info_queue.get() if not video_info["itemInfo"]["itemStruct"]["digged"]: logger.info(f"ЁЯШП @{username}'s video {video_id} already unliked") return like_part = await page.J(".like-part") if like_part: await page.click(".like-part") else: await page.click( ".video-feed-container .lazyload-wrapper:first-child .bar-item-wrapper:first-child", ) like_info = await like_info_queue.get() if like_info["status_code"] == 0: logger.info(f"ЁЯСО @{username}'s video {video_id} unliked") else: logger.warning( f"тЪая╕П @{username}'s video {video_id} probably not unliked") await page.close()
def __init__(self, settings_path: Optional[str] = None): self.started_at = datetime.now() init_logger() load_or_create_settings(path=settings_path) if settings.get("COOKIES") and settings.get("USERNAME"): logger.info(f"✅ Used cookies of @{settings.USERNAME}") else: logger.info("🛑 Cookies not found, anonymous mode") logger.info("🥳 TikTokPy initialized")
async def unfollow(self, username: str): page: Page = await self.client.new_page( blocked_resources=["image", "media", "font"]) logger.debug(f"👥 Unfollow {username}") unfollow_info_queue: asyncio.Queue = asyncio.Queue(maxsize=1) page.on( "response", lambda res: asyncio.create_task( catch_response_info(res, unfollow_info_queue, "/commit/follow/user"), ), ) logger.info(f"🧭 Going to {username}'s page for unfollowing") await self.client.goto( f"/@{username.lstrip('@')}", page=page, options={"waitUntil": "networkidle0"}, ) try: follow_title: str = await page.Jeval( ".follow-button", pageFunction="element => element.textContent", ) except ElementHandleError: print("ElementHandleError") return if follow_title.lower() != "following" and follow_title.lower( ) != "friends": logger.info(f"😏 {username} already unfollowed") return await page.click(".follow-button") unfollow_info = await unfollow_info_queue.get() if unfollow_info["status_code"] == 0: logger.info(f"➖ {username} unfollowed") else: logger.warning(f"⚠️ {username} probably not unfollowed") await page.close()
async def follow(self, username: str): page: Page = await self.client.new_page( blocked_resources=["image", "media", "font"]) logger.debug(f"ЁЯСе Follow {username}") follow_info_queue: asyncio.Queue = asyncio.Queue(maxsize=1) page.on( "response", lambda res: asyncio.create_task( catch_response_info(res, follow_info_queue, "/commit/follow/user"), ), ) logger.info(f"ЁЯзн Going to {username}'s page for following") await self.client.goto( f"/@{username.lstrip('@')}", page=page, options={"waitUntil": "networkidle0"}, ) follow_title: str = await page.Jeval( ".follow-button", pageFunction="element => element.textContent", ) if follow_title.lower() != "follow": logger.info(f"ЁЯШП {username} already followed") return await page.click(".follow-button") follow_info = await follow_info_queue.get() if follow_info["status_code"] == 0: logger.info(f"тЮХ {username} followed") else: logger.warning(f"тЪая╕П {username} probably not followed") await page.close()
async def unlike(self, username: str, video_id: str): page: Page = await self.client.new_page( blocked_resources=["image", "media", "font"]) logger.debug(f"👥 Unlike video id {video_id} of @{username}") like_info_queue: asyncio.Queue = asyncio.Queue(maxsize=1) page.on( "response", lambda res: asyncio.create_task( catch_response_info(res, like_info_queue, "/commit/item/digg"), ), ) logger.info( f"🧭 Going to @{username}'s video {video_id} page for unlike") await self.client.goto( f"/@{username}/video/{video_id}", page=page, options={"waitUntil": "networkidle0"}, ) like_selector = ".lazyload-wrapper:first-child .item-action-bar.vertical > .bar-item-wrapper:first-child" # noqa: E501 is_unliked = await page.J(f'{like_selector} svg[fill="currentColor"]') if is_unliked: logger.info(f"😏 @{username}'s video {video_id} already unliked") return await page.click(like_selector) like_info = await like_info_queue.get() if like_info["status_code"] == 0: logger.info(f"👎 @{username}'s video {video_id} unliked") else: logger.warning( f"⚠️ @{username}'s video {video_id} probably not unliked") await page.close()
async def unlike(self, username: str, video_id: str): page: Page = await self.client.new_page( blocked_resources=["image", "media", "font"]) logger.debug(f"ЁЯСе Unlike video id {video_id} of @{username}") like_info_queue: asyncio.Queue = asyncio.Queue(maxsize=1) page.on( "response", lambda res: asyncio.create_task( catch_response_info(res, like_info_queue, "/commit/item/digg"), ), ) logger.info( f"ЁЯзн Going to @{username}'s video {video_id} page for unlike") await self.client.goto( f"/@{username}/video/{video_id}", page=page, options={"waitUntil": "networkidle0"}, ) like_element = await page.J("span.like") if "liked" not in like_element._remoteObject["description"]: logger.info(f"ЁЯШП @{username}'s video {video_id} already unliked") return await page.click(".like-part") like_info = await like_info_queue.get() if like_info["status_code"] == 0: logger.info(f"ЁЯСН @{username}'s video {video_id} unliked") else: logger.warning( f"тЪая╕П @{username}'s video {video_id} probably not unliked") await page.close()
async def feed(self, username: str, amount: int): page: Page = await self.client.new_page(blocked_resources=["image", "media", "font"]) logger.debug(f"ЁЯУи Request {username} feed") result: List[dict] = [] user_info_queue: asyncio.Queue = asyncio.Queue(maxsize=1) page.on( "response", lambda res: asyncio.create_task(catch_response_and_store(res, result)), ) page.on( "response", lambda res: asyncio.create_task( catch_response_info(res, user_info_queue, "/user/detail"), ), ) _ = await self.client.goto(f"/{username}", page=page, options={"waitUntil": "networkidle0"}) logger.debug(f"ЁЯУн Got {username} feed") await page.waitForSelector(".video-feed-item", options={"visible": True}) user_info = await user_info_queue.get() user_video_count = user_info["userInfo"]["stats"]["videoCount"] if user_video_count < amount: logger.info( f"тЪая╕П User {username} has only {user_video_count} videos. " f"Set amount from {amount} to {user_video_count}", ) amount = user_video_count pbar = tqdm(total=amount, desc=f"ЁЯУИ Getting {username} feed") pbar.n = min(len(result), amount) pbar.refresh() attempts = 0 last_result = len(result) while len(result) < amount: logger.debug("ЁЯЦ▒ Trying to scroll to last video item") await page.evaluate( """ document.querySelector('.video-feed-item:last-child') .scrollIntoView(); """, ) await page.waitFor(1_000) elements = await page.JJ(".video-feed-item") logger.debug(f"ЁЯФО Found {len(elements)} items for clear") pbar.n = min(len(result), amount) pbar.refresh() if last_result == len(result): attempts += 1 else: attempts = 0 if attempts > 10: pbar.clear() pbar.total = len(result) logger.info( f"тЪая╕П After 10 attempts found {len(result)} videos. " f"Probably some videos are private", ) break last_result = len(result) if len(elements) < 500: logger.debug("ЁЯФ╗ Too less for clearing page") continue await page.JJeval( ".video-feed-item:not(:last-child)", pageFunction="(elements) => elements.forEach(el => el.remove())", ) logger.debug(f"ЁЯОЙ Cleaned {len(elements) - 1} items from page") await page.waitFor(30_000) await page.close() pbar.close() return result[:amount]
async def feed(self, username: str, amount: int): page: Page = await self.client.new_page(blocked_resources=["image", "media", "font"]) logger.debug(f"📨 Request {username} feed") result: List[dict] = [] page.on( "response", lambda res: asyncio.create_task(catch_response_and_store(res, result)), ) _ = await self.client.goto(f"/{username}", page=page, options={"waitUntil": "networkidle0"}) logger.debug(f"📭 Got {username} feed") await page.waitForSelector(".video-feed-item", options={"visible": True}) pbar = tqdm(total=amount, desc=f"📈 Getting {username} feed") pbar.n = min(len(result), amount) pbar.refresh() attempts = 0 last_result = len(result) while len(result) < amount: logger.debug("🖱 Trying to scroll to last video item") await page.evaluate( """ document.querySelector('.video-feed-item:last-child') .scrollIntoView(); """, ) await page.waitFor(1_000) elements = await page.JJ(".video-feed-item") logger.debug(f"🔎 Found {len(elements)} items for clear") pbar.n = min(len(result), amount) pbar.refresh() if last_result == len(result): attempts += 1 else: attempts = 0 if attempts > 10: pbar.clear() pbar.total = len(result) logger.info( f"⚠️ After 10 attempts found {len(result)} videos. " f"Probably some videos are private", ) break last_result = len(result) if len(elements) < 500: logger.debug("🔻 Too less for clearing page") continue await page.JJeval( ".video-feed-item:not(:last-child)", pageFunction="(elements) => elements.forEach(el => el.remove())", ) logger.debug(f"🎉 Cleaned {len(elements) - 1} items from page") await page.waitFor(30_000) await page.close() pbar.close() return result[:amount]