From ad39eeaed166162f995c256f80eabc75c111c346 Mon Sep 17 00:00:00 2001 From: oscar Date: Thu, 13 Feb 2025 02:38:31 +0200 Subject: [PATCH] update - new scripts --- bunny.py | 424 +++++++++++++++++++++++++++++++++++++ fix_user_id.py | 24 +++ kick_downloader.py | 227 ++++++++++++++++++++ organize_bunny.py | 78 +++++++ profile_pic.py | 20 ++ snapchat_backer.py | 126 +++++++++++ snapchat_master_crawler.py | 66 ++++++ snappy_cleaner.py | 41 ++++ tiktok_dump.py | 140 ++++++++++++ tiktok_process.py | 58 +++++ twitch_downloader.py | 124 +++++++++++ 11 files changed, 1328 insertions(+) create mode 100644 bunny.py create mode 100644 fix_user_id.py create mode 100644 kick_downloader.py create mode 100644 organize_bunny.py create mode 100644 profile_pic.py create mode 100644 snapchat_backer.py create mode 100644 snapchat_master_crawler.py create mode 100644 snappy_cleaner.py create mode 100644 tiktok_dump.py create mode 100644 tiktok_process.py create mode 100644 twitch_downloader.py diff --git a/bunny.py b/bunny.py new file mode 100644 index 0000000..41fb743 --- /dev/null +++ b/bunny.py @@ -0,0 +1,424 @@ +import requests +import hashlib + +access_key = "ccd3f9d4-9e6f-4bd2-8f594402b5a7-3646-48fe" +video_library_id = 106867 + +def create_video(title): + url = f"https://video.bunnycdn.com/library/{video_library_id}/videos" + + payload = f"{{\"title\":\"{title}\"}}" + headers = { + "accept": "application/json", + "content-type": "application/*+json", + "AccessKey": access_key + } + + response = requests.post(url, data=payload, headers=headers) + + return response + +def generate_signature(library_id, api_key, expiration_time, video_id): + signature = hashlib.sha256((library_id + api_key + str(expiration_time) + video_id).encode()).hexdigest() + return signature + +def upload_video_process(file_path, video_id): + url = f"https://video.bunnycdn.com/library/{video_library_id}/videos/{video_id}" + + headers = { + "accept": "application/json", + "AccessKey": access_key + } + + with open(file_path, "rb") as file: + file_data = file.read() + + response = requests.put(url, headers=headers, data=file_data) + + return response.status_code + +def upload_video(file_path, title=None): + video_item = create_video(title) + if video_item.status_code != 200: + return False + + video_id = video_item.json()['guid'] + upload_video_process(file_path, video_id) + + return { + "embed_link": f"https://vz-58ca89f1-986.b-cdn.net/{video_id}/playlist.m3u8", + "animated_thumbnail": f"https://vz-58ca89f1-986.b-cdn.net/{video_id}/preview.webp", + "default_thumbnail": f"https://vz-58ca89f1-986.b-cdn.net/{video_id}/thumbnail.jpg", + } + + +def upload_video_recurbate(videoInfo): + title = f"{videoInfo['username']} {videoInfo['platform']}" + video_item = create_video(title) + if video_item.status_code != 200: + return False + + video_id = video_item.json()['guid'] + upload_video_process(videoInfo['filename'], video_id) + + videoInfo["embed_link"] = f"https://vz-58ca89f1-986.b-cdn.net/{video_id}/playlist.m3u8" + videoInfo["animated_thumbnail"] = f"https://vz-58ca89f1-986.b-cdn.net/{video_id}/preview.webp" + videoInfo["default_thumbnail"] = f"https://vz-58ca89f1-986.b-cdn.net/{video_id}/thumbnail.jpg" + + return True + +def delete_video(video_id): + video_id = video_id.replace('https://vz-58ca89f1-986.b-cdn.net/', '').replace('/playlist.m3u8', '') + + url = f"https://video.bunnycdn.com/library/{video_library_id}/videos/{video_id}" + + headers = { + "accept": "application/json", + "AccessKey": access_key + } + + response = requests.delete(url, headers=headers) + + return response.status_code + +def list_videos(): + url = f"https://video.bunnycdn.com/library/{video_library_id}/videos?page=1&itemsPerPage=2147483647&orderBy=date" + + headers = { + "accept": "application/json", + "AccessKey": access_key + } + + response = requests.get(url, headers=headers) + + return response.json()['items'] + +def get_heatmap(video_id): + url = "https://video.bunnycdn.com/library/libraryId/videos/videoId/heatmap" + url = url.replace('libraryId', str(video_library_id)).replace('videoId', str(video_id)) + + headers = { + "accept": "application/json", + "AccessKey": access_key + } + + response = requests.get(url, headers=headers).json() + + return response + +def get_video(video_id): + url = "https://video.bunnycdn.com/library/libraryId/videos/videoId" + url = url.replace('libraryId', str(video_library_id)).replace('videoId', str(video_id)) + + headers = { + "accept": "application/json", + "AccessKey": access_key + } + + response = requests.get(url, headers=headers).json() + + return response + + +import os +import requests +from requests.exceptions import HTTPError +from urllib import parse + +class Storage: + def __init__(self, api_key, storage_zone, storage_zone_region="de"): + """ + Creates an object for using BunnyCDN Storage API + Parameters + ---------- + api_key : String + Your bunnycdn storage + Apikey/FTP password of + storage zone + + storage_zone : String + Name of your storage zone + + storage_zone_region(optional parameter) : String + The storage zone region code + as per BunnyCDN + """ + self.headers = { + # headers to be passed in HTTP requests + "AccessKey": api_key, + "Content-Type": "application/json", + "Accept": "applcation/json", + } + + # applying constraint that storage_zone must be specified + assert storage_zone != "", "storage_zone is not specified/missing" + + # For generating base_url for sending requests + if storage_zone_region == "de" or storage_zone_region == "": + self.base_url = "https://storage.bunnycdn.com/" + storage_zone + "/" + else: + self.base_url = ( + "https://" + + storage_zone_region + + ".storage.bunnycdn.com/" + + storage_zone + + "/" + ) + + def DownloadFile(self, storage_path, download_path=os.getcwd()): + """ + This function will get the files and subfolders of storage zone mentioned in path + and download it to the download_path location mentioned + Parameters + ---------- + storage_path : String + The path of the directory + (including file name and excluding storage zone name) + from which files are to be retrieved + download_path : String + The directory on local server to which downloaded file must be saved + Note:For download_path instead of '\' '\\' should be used example: C:\\Users\\XYZ\\OneDrive + """ + + assert ( + storage_path != "" + ), "storage_path must be specified" # to make sure storage_path is not null + # to build correct url + if storage_path[0] == "/": + storage_path = storage_path[1:] + if storage_path[-1] == "/": + storage_path = storage_path[:-1] + url = self.base_url + parse.quote(storage_path) + file_name = url.split("/")[-1] # For storing file name + + # to return appropriate help messages if file is present or not and download file if present + try: + response = requests.get(url, headers=self.headers, stream=True) + response.raise_for_status() + except HTTPError as http: + return { + "status": "error", + "HTTP": response.status_code, + "msg": f"Http error occured {http}", + } + except Exception as err: + return { + "status": "error", + "HTTP": response.status_code, + "msg": f"error occured {err}", + } + else: + download_path = os.path.join(download_path, file_name) + # Downloading file + with open(download_path, "wb") as file: + + for chunk in response.iter_content(chunk_size=1024): + if chunk: + file.write(chunk) + return { + "status": "success", + "HTTP": response.status_code, + "msg": "File downloaded Successfully", + } + + def PutFile( + self, + file_name, + storage_path=None, + local_upload_file_path=os.getcwd(), + ): + + """ + This function uploads files to your BunnyCDN storage zone + Parameters + ---------- + storage_path : String + The path of directory in storage zone + (including the name of file as desired and excluding storage zone name) + to which file is to be uploaded + file_name : String + The name of the file as stored in local server + local_upload_file_path : String + The path of file as stored in local server(excluding file name) + from where file is to be uploaded + Examples + -------- + file_name : 'ABC.txt' + local_upload_file_path : 'C:\\User\\Sample_Directory' + storage_path : '/.txt' + #Here .txt because the file being uploaded in example is txt + """ + local_upload_file_path = os.path.join(local_upload_file_path, file_name) + + # to build correct url + if storage_path is not None and storage_path != "": + if storage_path[0] == "/": + storage_path = storage_path[1:] + if storage_path[-1] == "/": + storage_path = storage_path[:-1] + url = self.base_url + parse.quote(storage_path) + else: + url = self.base_url + parse.quote(file_name) + with open(local_upload_file_path, "rb") as file: + file_data = file.read() + response = requests.put(url, data=file_data, headers=self.headers) + try: + response.raise_for_status() + except HTTPError as http: + return { + "status": "error", + "HTTP": response.status_code, + "msg": f"Upload Failed HTTP Error Occured: {http}", + } + else: + return { + "status": "success", + "HTTP": response.status_code, + "msg": "The File Upload was Successful", + } + + def DeleteFile(self, storage_path=""): + """ + This function deletes a file or folder mentioned in the storage_path from the storage zone + Parameters + ---------- + storage_path : The directory path to your file (including file name) or folder which is to be deleted. + If this is the root of your storage zone, you can ignore this parameter. + """ + # Add code below + assert ( + storage_path != "" + ), "storage_path must be specified" # to make sure storage_path is not null + # to build correct url + if storage_path[0] == "/": + storage_path = storage_path[1:] + url = self.base_url + parse.quote(storage_path) + + try: + response = requests.delete(url, headers=self.headers) + response.raise_for_status + except HTTPError as http: + return { + "status": "error", + "HTTP": response.raise_for_status(), + "msg": f"HTTP Error occured: {http}", + } + except Exception as err: + return { + "status": "error", + "HTTP": response.status_code, + "msg": f"Object Delete failed ,Error occured:{err}", + } + else: + return { + "status": "success", + "HTTP": response.status_code, + "msg": "Object Successfully Deleted", + } + + def GetStoragedObjectsList(self, storage_path=None): + """ + This functions returns a list of files and directories located in given storage_path. + Parameters + ---------- + storage_path : The directory path that you want to list. + """ + # to build correct url + if storage_path is not None: + if storage_path[0] == "/": + storage_path = storage_path[1:] + if storage_path[-1] != "/": + url = self.base_url + parse.quote(storage_path) + "/" + else: + url = self.base_url + # Sending GET request + try: + response = requests.get(url, headers=self.headers) + response.raise_for_status() + except HTTPError as http: + return { + "status": "error", + "HTTP": response.status_code, + "msg": f"http error occured {http}", + } + else: + storage_list = [] + for dictionary in response.json(): + temp_dict = {} + for key in dictionary: + if key == "ObjectName" and dictionary["IsDirectory"] is False: + temp_dict["File_Name"] = dictionary[key] + if key == "ObjectName" and dictionary["IsDirectory"]: + temp_dict["Folder_Name"] = dictionary[key] + storage_list.append(temp_dict) + return storage_list + + def MoveFile(self, old_path, new_path): + """ + Moves a file by downloading from the old path and uploading to the new path, + then deleting from the old path. Uses existing PutFile and DeleteFile methods. + + Parameters + ---------- + old_path : str + The current path (relative to storage zone root) of the file to move. + new_path : str + The new path (relative to storage zone root) for the file. + + Returns + ------- + dict + A dictionary containing 'status', 'msg', and optionally 'HTTP'. + """ + # Validate arguments + if not old_path or not new_path: + return { + "status": "error", + "msg": "Both old_path and new_path must be provided." + } + + # 1. Download from old_path to a temporary local directory + # If you already have the file locally, you can skip this download step. + download_response = self.DownloadFile(old_path, download_path="temp") + if download_response.get("status") != "success": + return { + "status": "error", + "msg": f"Failed to download file for moving. Reason: {download_response.get('msg', 'unknown')}", + "HTTP": download_response.get("HTTP") + } + + # Extract the filename from old_path to know what we downloaded + filename = os.path.basename(old_path) + + # 2. Upload to new_path using existing PutFile + # We'll assume new_path includes the desired filename. If it does not, adjust logic. + put_response = self.PutFile( + file_name=filename, + storage_path=new_path, # e.g. "folder/newfile.jpg" + local_upload_file_path="temp" # where we downloaded it + ) + if put_response.get("status") != "success": + return { + "status": "error", + "msg": f"Failed to upload file to new path. Reason: {put_response.get('msg', 'unknown')}", + "HTTP": put_response.get("HTTP") + } + + # 3. Delete the original file using existing DeleteFile + delete_response = self.DeleteFile(old_path) + if delete_response.get("status") != "success": + return { + "status": "error", + "msg": f"Failed to delete old file. Reason: {delete_response.get('msg', 'unknown')}", + "HTTP": delete_response.get("HTTP") + } + + # (Optional) Clean up the local temp file + local_temp_path = os.path.join("temp", filename) + if os.path.exists(local_temp_path): + os.remove(local_temp_path) + + return { + "status": "success", + "msg": f"File successfully moved from '{old_path}' to '{new_path}'." + } \ No newline at end of file diff --git a/fix_user_id.py b/fix_user_id.py new file mode 100644 index 0000000..caf3a47 --- /dev/null +++ b/fix_user_id.py @@ -0,0 +1,24 @@ +import config + +db, cursor = config.gen_connection() + +cursor.execute("SELECT DISTINCT username FROM media WHERE user_id IS NULL AND platform = 'instagram';") +usernames = [username[0] for username in cursor.fetchall()] + +for username in usernames: + print(f"Username: {username}") + + cursor.execute("SELECT DISTINCT user_id FROM media WHERE username = %s AND user_id IS NOT NULL;", [username]) + possible_user_ids = [user_id for user_id, in cursor.fetchall()] + if len(possible_user_ids) == 0: + print(f"No user_id found for {username}") + continue + + if len(possible_user_ids) > 1: + print(f"Multiple user_ids found for {username}: {possible_user_ids}") + continue + + user_id = possible_user_ids[0] + cursor.execute("UPDATE media SET user_id = %s WHERE username = %s AND user_id IS NULL;", [user_id, username]) + db.commit() + print(f"[{cursor.rowcount}] Updated user_id for {username}") \ No newline at end of file diff --git a/kick_downloader.py b/kick_downloader.py new file mode 100644 index 0000000..c574382 --- /dev/null +++ b/kick_downloader.py @@ -0,0 +1,227 @@ +import requests + +user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" + +videos_cookie = "USER_LOCALE=en; _cfuvid=XPSOsPnC.GolOMHuCpDhhA9IC3dZs2VjyH6VlvHBrak-1736200174559-0.0.1.1-604800000; Zb8Yq4Gq0BDtyTgD2FKm3EBvdj5TIfT8O4T6L4w7=eyJpdiI6IjA0V3Evbi9MN0xWTm5Qc21LSURucXc9PSIsInZhbHVlIjoidGdqejd6bmQxTGhkWTJQNFFxcGxHNEVUaHJ6SUxFU0Z6RU1QdkVvZE94eWVYRS80VWhZYmNQQ3JENHVSZTFQeWV3RnJPeFZtSVRocEZoT3A0dnRMSHpqY1hYS1lkYU1vaFEvN0treTJpMmpGVXp2TEdDVGpGeW9tcHBqUTRpblh4TGhmNGY2L2dURkRvc2h2czNLK2pDbHluL3loemtlc1NRaVZGUXpreTdtK3E0Ny9xV2N2UStiY3BNNzQ5SWhPSVV2YmMyNHJhTTJwV2k1dERhbTRDKzZwZGtCTTlQelBDZ2gwTzBLRVpqL3Nwa3VOcDdyWHRnMHBQYXRZSXlhaVFuRjFOWGMwa3VhTU9ZSVM5NFJ5NnFyNEZNaVJ6SUszOTJ4cmUvZ3BZU2lqYkZrY0tXVS84dExBdjltelpGdnJ3STRCVFJnQjdsMm0yOW5XUUNmOWwwWFNDbUlEbzQwNjNaK1ZxZnJDWXorN0Q5RVlGSnk4TitvbHBJL2NHZERLclhaNnh6elRKdEhhVk1zeEg0YnVyMC81TGF1bmVQSFJrZFdMaGs2d0dPSysvaTVKdm5TQWNobnFNTU1ITGdLYSIsIm1hYyI6IjQ2MGQ5MTFkYTA5YTg4NTVhNDhmZThiYTk3OThiMzJhYWYyMDA3Y2VmMmEzZWRmOGFkN2E1ZTE1MTA5MjRmNTAiLCJ0YWciOiIifQ==; lZiPtxKwAqyahxufRO6jbpdvC9uRDrCYbgQ9Z4aW=eyJpdiI6IldhU0hiT1RoRkc1d2VWdUJqdFZPdkE9PSIsInZhbHVlIjoiaEhuNGppRnNXZXFuK1FuNlZwUUFlSE04ODZnOFBqeWxZcE9vRm1SQ0Q0WFJXS1hXZWk2WXY4TWRpTVJRVnZOSmIzS0NPTEd4Slk1clVhWkVnc1lQSXVJUVYyamNNbDE2YmNsd2psVUdtNDVhTnBjQTZHL3pnejFrS2k2dWZybkdRUVRnbU5XVnJTYXVQOFlYUjRuMjMwdy9ZUXlFRW9xRUlLNzJ0OGZBTGJXVkdMVlJMYVZybVFER0tpOTA3b2hBWVByN0EzV3gxK2RzK2RGWnJJbFdVWDBhVXkrdVZpQXlINWphbTZHaEluZW1wK200SWZFZ3o3VE5DVE15K3FQamMzdDhMWGdmZWtoTUxsbWI1WHNPUHFwNTlnYjBXNmtPOTRKZXh6Ym9XNzJvaytYZk9CVVdqd3pJaG5kWmZXMzdaSW9GZXFjbjk5RHd2Y1hWTGdNSG5GbDhXb1ZEc1NFWnJqMU1ibHBvY0hibkQwckN2WTBEaVJvNDY4aDZnU3dSVkpFQk5ZYlF6alhjZTFHTjRzQ0ttVE5BTmVFWVE3dURLNzBLWThoSG5YMDc0elkwTzhiQml2cnQ3Q2J3VkxJOCIsIm1hYyI6IjFkYTU5MjM1YjNjMzg2ZGM2ZmE5NDE3ZDcwZTRmYTY3MTNmZTkyNDkxY2IzZTExYmY0YjcxNGY2ODg2MTBjMGMiLCJ0YWciOiIifQ==; 5cjeFdP65qgqhABa5zdSwVe98cLCVREcp88fTZqn=eyJpdiI6IjZLaUJWcVdNY0tlZlVZNVhLUVdSRWc9PSIsInZhbHVlIjoieGs3YkJSQW95VUlYaVhtSWxlc1J2N2lmV1NqWUh2UXRTZlJJT2ZsL1dXVlBMa1k2TnJCOXpEY1Q4OXd4eThLc0d4NVBHMDU0L1Z0MDAwUTdGYkNsc1NsaWc4L2h1UHR6UGdEZE5OVkdMVlczK3BQWFYzNlJGV2t5TW5Hb05YT0VUZnFwRlo3WjhrbXlLcHFvYmErNjNJYTljVXNpQzVFR2dXaDMyU01UUGF2cVRTaGwzVU1HOXVzQWZUTUNUb0N5M3RGQ2o4cFNyTEtLM0MyR0ZxZlZySjFUaHk2NG1ONXVBc2V5QWJtNFhBdXU2TmpqeUF1TitLcHhZUExPdlBPNW5PWXQ1WVgwSGFtWTVsSWRVOVNTMTUyNkc3NE45Vk1mLzFXbEVFS3U3SWdybmVaVzJPUjNDWWp6TituaFRmampZYkxpN1l0V3graEdjcVRaOFNkaVd3OStPalQ0a1NaNmVET3dKOWpkWVpXWGg5bVh3N1FQbXlydnJESkpQbDBrbHA4bUtISkMvcUtuOVVhc1A3eU5sVjhHdENJRUJPNFlUbzl3L3REcmZnQTZhTmU2b2p1aXE2MUNGdkNNalpWNlNqS3UvaUkwRDlqMk10K2hSdmk3cHc9PSIsIm1hYyI6IjNlZTJlYzkzMDhlMDU5YWY5ZDcwN2NiYjUwZDY1OGU1YzBhMzYwNGY4YTVjYzAzZjJmOGI1YzE2YmVmNjE2YTAiLCJ0YWciOiIifQ==; KP_UIDz-ssn=0RQPiLYS1lfwY0WNbRLO8JhfqkXEorOqKCFriSk7OR7XPgad3oB9Uu5jDabu78RCn3Bx91Znxj2ToL8AoPcvcnzFOEAf0T9c3tAk0Zkzl7vxJaNioXGS8DLxooBuJyOzD0eRV9Qe85h7mEDGHWbELEe2O86V0yWMnWjf7Kij2krB; KP_UIDz=0RQPiLYS1lfwY0WNbRLO8JhfqkXEorOqKCFriSk7OR7XPgad3oB9Uu5jDabu78RCn3Bx91Znxj2ToL8AoPcvcnzFOEAf0T9c3tAk0Zkzl7vxJaNioXGS8DLxooBuJyOzD0eRV9Qe85h7mEDGHWbELEe2O86V0yWMnWjf7Kij2krB; __stripe_mid=e3982e51-13f1-4f6d-ab6f-5a73283fc07fc5e5a4; showMatureContent=true; __stripe_sid=737a7032-68c1-44ed-935a-f10ea6fb9a152d2d74; volume=0; tile_player_muted=true; cf_clearance=g7tAuajHsJTtG2hC7RTtSF3DHTWBMjfaS61z6cOaRaI-1736205550-1.2.1.1-BtRpFVXob7L0PKI1tADxWd63iD6oBlyyZx7uzIjAyG6_81uPe7JX8EuXjo_XhcznBUS3fYap5JfMtU0PGk81HOIKKFxwZ728nnbtpI9jQz1ZKOlO3yYn96K1H45zEwsQJvJvJ0YElGhgTQ5fUhCAA31FQ4JxGKLQvrU3ivaTLKtdTN4aZWdjrxz8wIiKsN4Eb0QWKK0n9vqcR.gvzlJiaH2HVrSDrZikbJgjO_OV9XoXIVdQ4Vm5LEjtlsvMzayil.b8Wzz1B1OswQHtGphVlNqhUsogAocE6f3USbYm4.ZeMqOqEL9wAVPU4umivC_wDKIOI6w7RW8qGvPPzD9tMm0TpbWCJy3fyI2kIBb7ifnoP3B1lTjf5T28FbgnntDERGNAoR8Uu1VQzHhZ83ePGg; __cf_bm=wrDxfttp5jbVeo49KftpeCTyQWg11bWzReo8MJwPp8I-1736205876-1.0.1.1-F8EmzFhgGpnceV91XVFMzdkl4RYB1hGfCSpXev.palD84jswmL_HV0DslH2qsw1Z92c.4S0pcqJuiehm.otr6w; sort_options_clips=sort=view?range=all; XSRF-TOKEN=eyJpdiI6IlpDQ2o5Z0Vuemc5aFROY29LaTFhaEE9PSIsInZhbHVlIjoiS3lqUE5OUlljcGtHa2lSUHdaS1FDdk94QmVza1kzNnQ4S2dnclBXakp3UWRtdUg1NkdNTmNCL3Zzdkh6OWZhak14dEVLblZDTFFUcGZYaTVMVWhsQ2UrcEpPN085Tkh1d3Vwc2pqbnhtdVZiemJpTDB1dC9Bc29DSzhsa2JPK0oiLCJtYWMiOiIyYzU1M2ZiODIxM2U0NzU3NjIzMzk1ZWFkYjZhZjcwOGQxZjJkMzY5YmMwODExNDJiYWE5MWQ0NjdjZGUzNmVmIiwidGFnIjoiIn0=; kick_session=eyJpdiI6Ikd5OXJCMzFqYUxMcVNKRVo4UjJUSkE9PSIsInZhbHVlIjoiUGtRVUxzYlBrTmhqR1NUYi9KZTVTTW40TGh0dU9GNnZKdkJlK1BxcmtWeWhtZG9IeXdMWnB0OFovTHVoamY4cnlFVy9CRnhFcG02d1dnQlNuYWhBZ0doUUt4V2Z4ZGVLaHh6T0wxbEtidmk1OTBQeS9iUTVydVhwLy9CSEpxR1giLCJtYWMiOiJjMzkwYzljZDk2MDE0MGY5OGU3NTBiNjAwOTZjOGZkNjhjMmE1NjkwOGM2MTZlNDA0MzA1NDJlM2I0NmI2OWFiIiwidGFnIjoiIn0=; zEtssaQ3IfAtrsEcXvsiAHe6PsVsric6EGVK5vAl=eyJpdiI6IjU3a2w2ZFNmanQxSTJ0amZiQ2R2RVE9PSIsInZhbHVlIjoidjhTMTkwaFkrT0xONHBtWHk3Q2IvVWtkaERYOGgwNzd4aEF6L2w1M2tMUFRnOWpGa0MvSzJmbW4yc0MwMmVId2dqck5lM1dRQjZRWDVCZmM5WmVMU0ZVb3hEOFFwZC8yaEdGYm9jem5YRWxRdFFkUmpTdlRhUWhLeE1sZ0c5ZWEzOHliSWhuVS9wUnJhb21LM09zUnhCTHE2OTNublFhdWZyMmVQUnpWTVpVUEdjSlFwZDlxTVZjTUNMVVUrc0Z6N3hnNkw0WEVHa1o4SWtFL043bzlxR1UwYTIrNzY4RFhQRmhnclp3NS8zdy83WFNtMEd3SW1BZVVETmtIN1hxSXJSS1hxaXBxTUI0ZWNtbVVsd1V2c1VSUkhyVGh4VWpyNFM3Q2g0cExFS0pvamdrWDNnVG1KamVSTngxVGZQR3R3cFd6TU5wcEhabkN2dGU2T3BpR1lLUHFubDYrTWgxd3RTeVMva2NLeFhUakxxN3lsUHVzVXVjZnVNUXFXOTNxR0hIbmhWNnl5QUltWi94VlViWitnZlZKUGhpbFFhUTJPby9iekFQU2VXUU5YMDBFTTg0b0RUNEFDaExzcEJkKyIsIm1hYyI6IjJkNjAwMDI2NWE1YzFhNWRhMWU3NGYzMjUzYmU1NDZjNDQyYmRjMjVhYTE0ZWEzMjZhYTM2NjA2OWI3ZmUzYzAiLCJ0YWciOiIifQ==; _dd_s=logs=1&id=9164d60e-ee5c-42e5-9d5f-6df4b2193d31&created=1736200174377&expire=1736206971407" +streams_cookie = "USER_LOCALE=en; Zb8Yq4Gq0BDtyTgD2FKm3EBvdj5TIfT8O4T6L4w7=eyJpdiI6IjA0V3Evbi9MN0xWTm5Qc21LSURucXc9PSIsInZhbHVlIjoidGdqejd6bmQxTGhkWTJQNFFxcGxHNEVUaHJ6SUxFU0Z6RU1QdkVvZE94eWVYRS80VWhZYmNQQ3JENHVSZTFQeWV3RnJPeFZtSVRocEZoT3A0dnRMSHpqY1hYS1lkYU1vaFEvN0treTJpMmpGVXp2TEdDVGpGeW9tcHBqUTRpblh4TGhmNGY2L2dURkRvc2h2czNLK2pDbHluL3loemtlc1NRaVZGUXpreTdtK3E0Ny9xV2N2UStiY3BNNzQ5SWhPSVV2YmMyNHJhTTJwV2k1dERhbTRDKzZwZGtCTTlQelBDZ2gwTzBLRVpqL3Nwa3VOcDdyWHRnMHBQYXRZSXlhaVFuRjFOWGMwa3VhTU9ZSVM5NFJ5NnFyNEZNaVJ6SUszOTJ4cmUvZ3BZU2lqYkZrY0tXVS84dExBdjltelpGdnJ3STRCVFJnQjdsMm0yOW5XUUNmOWwwWFNDbUlEbzQwNjNaK1ZxZnJDWXorN0Q5RVlGSnk4TitvbHBJL2NHZERLclhaNnh6elRKdEhhVk1zeEg0YnVyMC81TGF1bmVQSFJrZFdMaGs2d0dPSysvaTVKdm5TQWNobnFNTU1ITGdLYSIsIm1hYyI6IjQ2MGQ5MTFkYTA5YTg4NTVhNDhmZThiYTk3OThiMzJhYWYyMDA3Y2VmMmEzZWRmOGFkN2E1ZTE1MTA5MjRmNTAiLCJ0YWciOiIifQ==; lZiPtxKwAqyahxufRO6jbpdvC9uRDrCYbgQ9Z4aW=eyJpdiI6IldhU0hiT1RoRkc1d2VWdUJqdFZPdkE9PSIsInZhbHVlIjoiaEhuNGppRnNXZXFuK1FuNlZwUUFlSE04ODZnOFBqeWxZcE9vRm1SQ0Q0WFJXS1hXZWk2WXY4TWRpTVJRVnZOSmIzS0NPTEd4Slk1clVhWkVnc1lQSXVJUVYyamNNbDE2YmNsd2psVUdtNDVhTnBjQTZHL3pnejFrS2k2dWZybkdRUVRnbU5XVnJTYXVQOFlYUjRuMjMwdy9ZUXlFRW9xRUlLNzJ0OGZBTGJXVkdMVlJMYVZybVFER0tpOTA3b2hBWVByN0EzV3gxK2RzK2RGWnJJbFdVWDBhVXkrdVZpQXlINWphbTZHaEluZW1wK200SWZFZ3o3VE5DVE15K3FQamMzdDhMWGdmZWtoTUxsbWI1WHNPUHFwNTlnYjBXNmtPOTRKZXh6Ym9XNzJvaytYZk9CVVdqd3pJaG5kWmZXMzdaSW9GZXFjbjk5RHd2Y1hWTGdNSG5GbDhXb1ZEc1NFWnJqMU1ibHBvY0hibkQwckN2WTBEaVJvNDY4aDZnU3dSVkpFQk5ZYlF6alhjZTFHTjRzQ0ttVE5BTmVFWVE3dURLNzBLWThoSG5YMDc0elkwTzhiQml2cnQ3Q2J3VkxJOCIsIm1hYyI6IjFkYTU5MjM1YjNjMzg2ZGM2ZmE5NDE3ZDcwZTRmYTY3MTNmZTkyNDkxY2IzZTExYmY0YjcxNGY2ODg2MTBjMGMiLCJ0YWciOiIifQ==; 5cjeFdP65qgqhABa5zdSwVe98cLCVREcp88fTZqn=eyJpdiI6IjZLaUJWcVdNY0tlZlVZNVhLUVdSRWc9PSIsInZhbHVlIjoieGs3YkJSQW95VUlYaVhtSWxlc1J2N2lmV1NqWUh2UXRTZlJJT2ZsL1dXVlBMa1k2TnJCOXpEY1Q4OXd4eThLc0d4NVBHMDU0L1Z0MDAwUTdGYkNsc1NsaWc4L2h1UHR6UGdEZE5OVkdMVlczK3BQWFYzNlJGV2t5TW5Hb05YT0VUZnFwRlo3WjhrbXlLcHFvYmErNjNJYTljVXNpQzVFR2dXaDMyU01UUGF2cVRTaGwzVU1HOXVzQWZUTUNUb0N5M3RGQ2o4cFNyTEtLM0MyR0ZxZlZySjFUaHk2NG1ONXVBc2V5QWJtNFhBdXU2TmpqeUF1TitLcHhZUExPdlBPNW5PWXQ1WVgwSGFtWTVsSWRVOVNTMTUyNkc3NE45Vk1mLzFXbEVFS3U3SWdybmVaVzJPUjNDWWp6TituaFRmampZYkxpN1l0V3graEdjcVRaOFNkaVd3OStPalQ0a1NaNmVET3dKOWpkWVpXWGg5bVh3N1FQbXlydnJESkpQbDBrbHA4bUtISkMvcUtuOVVhc1A3eU5sVjhHdENJRUJPNFlUbzl3L3REcmZnQTZhTmU2b2p1aXE2MUNGdkNNalpWNlNqS3UvaUkwRDlqMk10K2hSdmk3cHc9PSIsIm1hYyI6IjNlZTJlYzkzMDhlMDU5YWY5ZDcwN2NiYjUwZDY1OGU1YzBhMzYwNGY4YTVjYzAzZjJmOGI1YzE2YmVmNjE2YTAiLCJ0YWciOiIifQ==; __stripe_mid=e3982e51-13f1-4f6d-ab6f-5a73283fc07fc5e5a4; showMatureContent=true; tile_player_muted=true; sort_options_clips=sort=view?range=all; stream_quality_cookie=720; volume=0; KP_UIDz-ssn=0MrL7PuuY1ecYCmYW9QcZPd00jVlO2zPfxQAOSbGSIfsn78YPjxvtSE89ZIr1cegxvsQDVg6t7uwevGlIkg6fiYVFXvnqSb1y3r97BHthO282dKXhM02IwkcVWeQ5LVc3PDaH1U6h9JRIAP4HCNcnARkQ2ZrLjL0dfKNNjvKHR7I; KP_UIDz=0MrL7PuuY1ecYCmYW9QcZPd00jVlO2zPfxQAOSbGSIfsn78YPjxvtSE89ZIr1cegxvsQDVg6t7uwevGlIkg6fiYVFXvnqSb1y3r97BHthO282dKXhM02IwkcVWeQ5LVc3PDaH1U6h9JRIAP4HCNcnARkQ2ZrLjL0dfKNNjvKHR7I; _cfuvid=FXvxAU5ASgX1ma90hpSGRpsdvI40Yp7YxDZQPhe3XdU-1736338951992-0.0.1.1-604800000; __cf_bm=vJqoLZMQnChEqTCPLZEaEtqpIJSTVzMVv3mhMPrV2N4-1736340611-1.0.1.1-p3TImgDhDkBWUjy7deWagLxWt_DCTwIjHKi6fBXMA4CIpQafPKu1R.Ji9S7fJkwDyB7GSr26DHD8DAPsQitxHw; cf_clearance=DoLlos55rsOW.N_5EgzqviM6FVlKodb_fA8_ZodlMxQ-1736340648-1.2.1.1-ux_OFOYU2QkchiJg82ggjSw0mbyNIVSs8OgUiyt7dBcsmgtBX6q3Kv4dUKYOTZyfpv27Lho44REjH5BsDdA2w4O2a_.R.fC_8joXmg_zA8WopHj8OV1HnOCzpLbaHD0n2MgZV5swsLmpACqYRDTfHCDKlGbR_rz39_G3YX6Ze3pyhJS7Gg8o5ynfMs3FqF_5M3xYn7GYIJoGuQLBNdLDzqjAWklZfFfHqrhs1pQbkl87E12McHdw0Y9UeSL05.JZTkwato25lcJKIRAEyiYgiXQZu12qnTv5tmbhYyTn8Hlr1YgkCYV0NRO.6TmWnwxmpocHUISSkz3DxekgZb60ad17wojbSVuR44XjuzkU1pLBoGBR4Xpt8QNV9eePx4W4Uc6pjK0gA8uPFBV6mDkUew; XSRF-TOKEN=eyJpdiI6IlF2alhLWGZES1VwQlhxMEdIVEwvcUE9PSIsInZhbHVlIjoiVmlJVTlMV0MzelAyVDR5cW53LzJPVGdUWlVDMlFoMTBFTmJYUmc2WENXMUdYYlhOSTdKc0JaT3NrdEE2VnhDekgvZ0grUnNLSis1TEZJc0NhazZyZGtDRG1nWmJFM0pWdnFKZ1ZndUV4cnc2WWVlellabFA0TC9MMTVGbjdpczIiLCJtYWMiOiJiMjBkOGJhMjUyNjhlODJkOTFhMTBmNDE0OThlMmIxZjMzMDVkNDE0NzkyOGVkNzJmMTk5ZTEwYjMxZGY1M2QwIiwidGFnIjoiIn0=; kick_session=eyJpdiI6ImxZOW5UOFB0VnV2Wkd3d2J3L3ZaRFE9PSIsInZhbHVlIjoid20rNDRvVWlEUkloMGxvYkRXcE5zUDNtZWxFVUVpcVUwcjcrTTQrZ0Rha3RBWjVnSmU4Q0xyUlRFckRVZFRQQmZwWS9ka3RKcW5JRE80MWxnSmhYbVpkSko4RC9nQUFDd1BSdkxPbWpQSUk4ZExDSCtrajUzMzFrMHVydi8zbFYiLCJtYWMiOiJjOTIyNTljMDNmZmU4OGFhZGVjYzViYTExZDA0MjU2ZmNkZDhhZGRhMzA4NTI0NzViMjk4NTRmZGNiZTc2NjVmIiwidGFnIjoiIn0=; zEtssaQ3IfAtrsEcXvsiAHe6PsVsric6EGVK5vAl=eyJpdiI6IlJxNU1tUEs5MHVYUU1OWjhuYjNmRWc9PSIsInZhbHVlIjoiUjZ2enh4L3htL0VObThGdEIrcGFuMmYxTUlIc1l3M2hEeFRIT08wak9heUw3ZkMvM0ROZWNsZUVMOW5tU0hDWW5hTUtrdDZManpYQzUyNGtaaXBKdnZ4Q041bTlHNEY2Tkd2TjNNNGc4V2NGOEwzdjkxVlQrMGVDL2VmVnE2T1NSbE5KQlAxUGpWSHkxV0FUN3RvV3Vwck5OTmxncWZraGtDZ1QzZUtwQ2l2dWllbXlCbEwweUEzOHBKL1JzWk9OaFN2U1BuTlJwOW1samUzdHVTUnUwZk02ZEZSeFFObmQxQ0VqU09DUkYzL0w2YUdQc3BOdlZUd2xkVkJtZ29Tb0hRRkhXeVc4NHJoVXdSelVOUmJKYU1YWHEvRklkT2dySHdtQnFDL3B6K1RMb1N6Qm0yWmliRTRyYXV4K0hzQUZBcktXdGpWclN1cUI5UGlpcW9UK2paaC95SnVtR3d3ZVBqZWg0TG9tc3U1QXVtUUJRUzkzalhueEVDZWovRlAwRnRIcE9oREREWWVKRVNQdFZ0Z0RsRXhUd2c1ZzhiSVgzNUI0QmNwQjFYUT0iLCJtYWMiOiIzODVkOTU3ZDFlYjY0NjlmOTBmOTYxYTBlMWM5NTE5M2VlYjNmNDQwZDljNjhkMmUxOGNlNDc4NDU4MjNhYTMxIiwidGFnIjoiIn0=; _dd_s=logs=1&id=fc0e4d94-77f2-4baf-9721-9f07ebaa7bc7&created=1736340647984&expire=1736341553875; sortOptions=sort=viewers_low_to_high" +categories_cookie = "USER_LOCALE=en; Zb8Yq4Gq0BDtyTgD2FKm3EBvdj5TIfT8O4T6L4w7=eyJpdiI6IjA0V3Evbi9MN0xWTm5Qc21LSURucXc9PSIsInZhbHVlIjoidGdqejd6bmQxTGhkWTJQNFFxcGxHNEVUaHJ6SUxFU0Z6RU1QdkVvZE94eWVYRS80VWhZYmNQQ3JENHVSZTFQeWV3RnJPeFZtSVRocEZoT3A0dnRMSHpqY1hYS1lkYU1vaFEvN0treTJpMmpGVXp2TEdDVGpGeW9tcHBqUTRpblh4TGhmNGY2L2dURkRvc2h2czNLK2pDbHluL3loemtlc1NRaVZGUXpreTdtK3E0Ny9xV2N2UStiY3BNNzQ5SWhPSVV2YmMyNHJhTTJwV2k1dERhbTRDKzZwZGtCTTlQelBDZ2gwTzBLRVpqL3Nwa3VOcDdyWHRnMHBQYXRZSXlhaVFuRjFOWGMwa3VhTU9ZSVM5NFJ5NnFyNEZNaVJ6SUszOTJ4cmUvZ3BZU2lqYkZrY0tXVS84dExBdjltelpGdnJ3STRCVFJnQjdsMm0yOW5XUUNmOWwwWFNDbUlEbzQwNjNaK1ZxZnJDWXorN0Q5RVlGSnk4TitvbHBJL2NHZERLclhaNnh6elRKdEhhVk1zeEg0YnVyMC81TGF1bmVQSFJrZFdMaGs2d0dPSysvaTVKdm5TQWNobnFNTU1ITGdLYSIsIm1hYyI6IjQ2MGQ5MTFkYTA5YTg4NTVhNDhmZThiYTk3OThiMzJhYWYyMDA3Y2VmMmEzZWRmOGFkN2E1ZTE1MTA5MjRmNTAiLCJ0YWciOiIifQ==; lZiPtxKwAqyahxufRO6jbpdvC9uRDrCYbgQ9Z4aW=eyJpdiI6IldhU0hiT1RoRkc1d2VWdUJqdFZPdkE9PSIsInZhbHVlIjoiaEhuNGppRnNXZXFuK1FuNlZwUUFlSE04ODZnOFBqeWxZcE9vRm1SQ0Q0WFJXS1hXZWk2WXY4TWRpTVJRVnZOSmIzS0NPTEd4Slk1clVhWkVnc1lQSXVJUVYyamNNbDE2YmNsd2psVUdtNDVhTnBjQTZHL3pnejFrS2k2dWZybkdRUVRnbU5XVnJTYXVQOFlYUjRuMjMwdy9ZUXlFRW9xRUlLNzJ0OGZBTGJXVkdMVlJMYVZybVFER0tpOTA3b2hBWVByN0EzV3gxK2RzK2RGWnJJbFdVWDBhVXkrdVZpQXlINWphbTZHaEluZW1wK200SWZFZ3o3VE5DVE15K3FQamMzdDhMWGdmZWtoTUxsbWI1WHNPUHFwNTlnYjBXNmtPOTRKZXh6Ym9XNzJvaytYZk9CVVdqd3pJaG5kWmZXMzdaSW9GZXFjbjk5RHd2Y1hWTGdNSG5GbDhXb1ZEc1NFWnJqMU1ibHBvY0hibkQwckN2WTBEaVJvNDY4aDZnU3dSVkpFQk5ZYlF6alhjZTFHTjRzQ0ttVE5BTmVFWVE3dURLNzBLWThoSG5YMDc0elkwTzhiQml2cnQ3Q2J3VkxJOCIsIm1hYyI6IjFkYTU5MjM1YjNjMzg2ZGM2ZmE5NDE3ZDcwZTRmYTY3MTNmZTkyNDkxY2IzZTExYmY0YjcxNGY2ODg2MTBjMGMiLCJ0YWciOiIifQ==; 5cjeFdP65qgqhABa5zdSwVe98cLCVREcp88fTZqn=eyJpdiI6IjZLaUJWcVdNY0tlZlVZNVhLUVdSRWc9PSIsInZhbHVlIjoieGs3YkJSQW95VUlYaVhtSWxlc1J2N2lmV1NqWUh2UXRTZlJJT2ZsL1dXVlBMa1k2TnJCOXpEY1Q4OXd4eThLc0d4NVBHMDU0L1Z0MDAwUTdGYkNsc1NsaWc4L2h1UHR6UGdEZE5OVkdMVlczK3BQWFYzNlJGV2t5TW5Hb05YT0VUZnFwRlo3WjhrbXlLcHFvYmErNjNJYTljVXNpQzVFR2dXaDMyU01UUGF2cVRTaGwzVU1HOXVzQWZUTUNUb0N5M3RGQ2o4cFNyTEtLM0MyR0ZxZlZySjFUaHk2NG1ONXVBc2V5QWJtNFhBdXU2TmpqeUF1TitLcHhZUExPdlBPNW5PWXQ1WVgwSGFtWTVsSWRVOVNTMTUyNkc3NE45Vk1mLzFXbEVFS3U3SWdybmVaVzJPUjNDWWp6TituaFRmampZYkxpN1l0V3graEdjcVRaOFNkaVd3OStPalQ0a1NaNmVET3dKOWpkWVpXWGg5bVh3N1FQbXlydnJESkpQbDBrbHA4bUtISkMvcUtuOVVhc1A3eU5sVjhHdENJRUJPNFlUbzl3L3REcmZnQTZhTmU2b2p1aXE2MUNGdkNNalpWNlNqS3UvaUkwRDlqMk10K2hSdmk3cHc9PSIsIm1hYyI6IjNlZTJlYzkzMDhlMDU5YWY5ZDcwN2NiYjUwZDY1OGU1YzBhMzYwNGY4YTVjYzAzZjJmOGI1YzE2YmVmNjE2YTAiLCJ0YWciOiIifQ==; __stripe_mid=e3982e51-13f1-4f6d-ab6f-5a73283fc07fc5e5a4; showMatureContent=true; tile_player_muted=true; sort_options_clips=sort=view?range=all; stream_quality_cookie=720; volume=0; KP_UIDz-ssn=0MrL7PuuY1ecYCmYW9QcZPd00jVlO2zPfxQAOSbGSIfsn78YPjxvtSE89ZIr1cegxvsQDVg6t7uwevGlIkg6fiYVFXvnqSb1y3r97BHthO282dKXhM02IwkcVWeQ5LVc3PDaH1U6h9JRIAP4HCNcnARkQ2ZrLjL0dfKNNjvKHR7I; KP_UIDz=0MrL7PuuY1ecYCmYW9QcZPd00jVlO2zPfxQAOSbGSIfsn78YPjxvtSE89ZIr1cegxvsQDVg6t7uwevGlIkg6fiYVFXvnqSb1y3r97BHthO282dKXhM02IwkcVWeQ5LVc3PDaH1U6h9JRIAP4HCNcnARkQ2ZrLjL0dfKNNjvKHR7I; sortOptions=sort=viewers_high_to_low; XSRF-TOKEN=eyJpdiI6InZtV0pwOUl2WVliMzVBR1Q0Nm0zM1E9PSIsInZhbHVlIjoidVY0USt1R2Joc0FRLy9LMGlPUVdseHpYTnBOTm1wQitYV2JTSUpETk0rRHdNR1RZS0d4aWc4aEdKcVdiTkhXZk1FZFR3Qm5ERVpLazdnY2ozaVRYSHJPMjRUMnp0RFRkZS9wNFo0TnZZbEg0M1JJcUpBTTFDQ2VpTXUzY2o3OGQiLCJtYWMiOiJjNzRlOWI1MDdiMTA5NjQyZGM0MmJkNjdmMDU2MjJiNzdkZWU2NWE2YzQ1NGUwN2YwNmNhOGNjMTUzMzdiNDM5IiwidGFnIjoiIn0=; kick_session=eyJpdiI6IjRGWlBqVUx1aGZHQnc1OThleXVoSFE9PSIsInZhbHVlIjoiODZqREZKOG0va2VwczlOQnM2ejRSVTFVeW9rWnVVQWNRMG93bEpnaTN0SkoxNHIxUzRRbkJ1WjlTa2dVWjdEbzdIY2VOQzRaY2NyOGNpRU03aHU4bHRxRHEybGtHZ1FiMzgzR0RZaFJseHlyL1ZTKy9zdSs0K3Fmd0w4NVJqcWQiLCJtYWMiOiIxYTgwMjQ5MWY5MzY1Yzk2ZDZlYmEwN2E2MWJlODc3MWNjMWZmN2YzMDQ4OWFlMGQ5NWE3YWZlYTkzNTE0M2VkIiwidGFnIjoiIn0=; zEtssaQ3IfAtrsEcXvsiAHe6PsVsric6EGVK5vAl=eyJpdiI6Ik1WeERNc2RjR1lNRUhveTJoYkVsR1E9PSIsInZhbHVlIjoiRUdaQXRleEJMWHBGd25sN29CRE5hdjRMb3p0UTJMN21qMzd6SnErZTdyS29jSjYrU3RvT3JTK1BKMFMrNzJzUzROdGp3YWl5MnFuS3A3Z0VnbWZxN2c1MXB3MmtVS3dwUC9ibFBFZHIyNGU1L1FYWVBLdXY3QXNWeUdKajE4QmdWanFKUzd4SWpwVC9vQXl3QVJUTm85V3ZKVnFWaGhqVTJ0TDNpSWh6dE1QVVc2STBhaXA4TlUwVjd6dkFnU1FoRW84SmpvOEdXbUFOVjkwZHRhZ2lmNVZ6Qm9IaGpDUEpwcXdEbGVDOGQ4M3BiSmpoYnNpTERLVjNWTXdWNjdYc0xYTFJWSGZvM0oyUzhOV1ZEbEdwTVJDUTV4dFdiVGk0RVNSaEVKZ00venBlNC93NFY5RkNLQmpHOU5OVkozNDZDdjliUkRHR1Bib2NsM2YxKzdiU0pnTkZDNWE0ai9PTkNlR0NqeURSSXNzR21hMkdSSmRUZEpYaWJhTUxzSktxUmlSL3JKd3RuRjRKSU1udlh5VWNGLzNPaU1laWdKVVZXR1NSbzQwSlZMdFJ1bHh1TWxQdDdBOEpLUEk1UUpRYyIsIm1hYyI6IjA5NWNmODIxNmFkNzBlZWEyNTk0NmU5NDRiYWYxMDc2ZWJiMGI4YTU5MWIzMzBmMDViNTE4MTk5MTA3YjJkYzUiLCJ0YWciOiIifQ==; _cfuvid=euWxqYL1tv9LpP.fp4nbZ2GYesW_sNEkHT1aliKZHGA-1736343178829-0.0.1.1-604800000; __cf_bm=mzo9qKsXPUNMtrSYRZRyys_yxdqSEV1hHsbXK.c1VJ4-1736346077-1.0.1.1-TlLd1K_gDH6WW.DJSkTepZSOeJzCkgxyOdX7kO4zRCwW8rG7zo6YZWa3MJvTaqrQXA16TS2D9Qe5e2wdhBX5iQ; _dd_s=logs=1&id=ac536f8f-4082-4d44-82c2-53b7956beb18&created=1736346122257&expire=1736347022257; cf_clearance=6NTiHykNI1FmiXNFkllmdmNSWATVWQY5ohYmvGhhC4Q-1736346122-1.2.1.1-h287.XC8AdjLJlyJjBNrZJMUtPhgnu_xb7oZRrwoswGEAXRDHGfgZNyE43MIqNVbtlhOly9f5rqyMDDQIsnUC9B8K6p4GAkX3vCJXOqh5MggAltksLoWyPwQzAHxs85xZ.0FuV9ZXlg2AQeExhXhFr0ROx_lKVw8dgWG8JLk_D_LkiEEnYlF02Wj_gpAbcBl5wdBzIXUIEsZB.bsTh3fTvOGCgDZxdzFiI15mpA4e_TT4RqWB9mGrDmrpVZaLnl.KOzFggAmlJnCHzHZBzFvpR1thFDY2TtIs9ej5CZGrlqTfBEgh5xNh4aZAAgbj1sojkTyD5vsjbpNLtKnb_9VfltvJ2QTc2sjvFMdxDvDFv1nz8jk.smVUWC0CDjGMeFoRkq2zXGY4oEVdhvdZtXGTA" +categories_cookie = "USER_LOCALE=en; Zb8Yq4Gq0BDtyTgD2FKm3EBvdj5TIfT8O4T6L4w7=eyJpdiI6IjA0V3Evbi9MN0xWTm5Qc21LSURucXc9PSIsInZhbHVlIjoidGdqejd6bmQxTGhkWTJQNFFxcGxHNEVUaHJ6SUxFU0Z6RU1QdkVvZE94eWVYRS80VWhZYmNQQ3JENHVSZTFQeWV3RnJPeFZtSVRocEZoT3A0dnRMSHpqY1hYS1lkYU1vaFEvN0treTJpMmpGVXp2TEdDVGpGeW9tcHBqUTRpblh4TGhmNGY2L2dURkRvc2h2czNLK2pDbHluL3loemtlc1NRaVZGUXpreTdtK3E0Ny9xV2N2UStiY3BNNzQ5SWhPSVV2YmMyNHJhTTJwV2k1dERhbTRDKzZwZGtCTTlQelBDZ2gwTzBLRVpqL3Nwa3VOcDdyWHRnMHBQYXRZSXlhaVFuRjFOWGMwa3VhTU9ZSVM5NFJ5NnFyNEZNaVJ6SUszOTJ4cmUvZ3BZU2lqYkZrY0tXVS84dExBdjltelpGdnJ3STRCVFJnQjdsMm0yOW5XUUNmOWwwWFNDbUlEbzQwNjNaK1ZxZnJDWXorN0Q5RVlGSnk4TitvbHBJL2NHZERLclhaNnh6elRKdEhhVk1zeEg0YnVyMC81TGF1bmVQSFJrZFdMaGs2d0dPSysvaTVKdm5TQWNobnFNTU1ITGdLYSIsIm1hYyI6IjQ2MGQ5MTFkYTA5YTg4NTVhNDhmZThiYTk3OThiMzJhYWYyMDA3Y2VmMmEzZWRmOGFkN2E1ZTE1MTA5MjRmNTAiLCJ0YWciOiIifQ%3D%3D; lZiPtxKwAqyahxufRO6jbpdvC9uRDrCYbgQ9Z4aW=eyJpdiI6IldhU0hiT1RoRkc1d2VWdUJqdFZPdkE9PSIsInZhbHVlIjoiaEhuNGppRnNXZXFuK1FuNlZwUUFlSE04ODZnOFBqeWxZcE9vRm1SQ0Q0WFJXS1hXZWk2WXY4TWRpTVJRVnZOSmIzS0NPTEd4Slk1clVhWkVnc1lQSXVJUVYyamNNbDE2YmNsd2psVUdtNDVhTnBjQTZHL3pnejFrS2k2dWZybkdRUVRnbU5XVnJTYXVQOFlYUjRuMjMwdy9ZUXlFRW9xRUlLNzJ0OGZBTGJXVkdMVlJMYVZybVFER0tpOTA3b2hBWVByN0EzV3gxK2RzK2RGWnJJbFdVWDBhVXkrdVZpQXlINWphbTZHaEluZW1wK200SWZFZ3o3VE5DVE15K3FQamMzdDhMWGdmZWtoTUxsbWI1WHNPUHFwNTlnYjBXNmtPOTRKZXh6Ym9XNzJvaytYZk9CVVdqd3pJaG5kWmZXMzdaSW9GZXFjbjk5RHd2Y1hWTGdNSG5GbDhXb1ZEc1NFWnJqMU1ibHBvY0hibkQwckN2WTBEaVJvNDY4aDZnU3dSVkpFQk5ZYlF6alhjZTFHTjRzQ0ttVE5BTmVFWVE3dURLNzBLWThoSG5YMDc0elkwTzhiQml2cnQ3Q2J3VkxJOCIsIm1hYyI6IjFkYTU5MjM1YjNjMzg2ZGM2ZmE5NDE3ZDcwZTRmYTY3MTNmZTkyNDkxY2IzZTExYmY0YjcxNGY2ODg2MTBjMGMiLCJ0YWciOiIifQ%3D%3D; 5cjeFdP65qgqhABa5zdSwVe98cLCVREcp88fTZqn=eyJpdiI6IjZLaUJWcVdNY0tlZlVZNVhLUVdSRWc9PSIsInZhbHVlIjoieGs3YkJSQW95VUlYaVhtSWxlc1J2N2lmV1NqWUh2UXRTZlJJT2ZsL1dXVlBMa1k2TnJCOXpEY1Q4OXd4eThLc0d4NVBHMDU0L1Z0MDAwUTdGYkNsc1NsaWc4L2h1UHR6UGdEZE5OVkdMVlczK3BQWFYzNlJGV2t5TW5Hb05YT0VUZnFwRlo3WjhrbXlLcHFvYmErNjNJYTljVXNpQzVFR2dXaDMyU01UUGF2cVRTaGwzVU1HOXVzQWZUTUNUb0N5M3RGQ2o4cFNyTEtLM0MyR0ZxZlZySjFUaHk2NG1ONXVBc2V5QWJtNFhBdXU2TmpqeUF1TitLcHhZUExPdlBPNW5PWXQ1WVgwSGFtWTVsSWRVOVNTMTUyNkc3NE45Vk1mLzFXbEVFS3U3SWdybmVaVzJPUjNDWWp6TituaFRmampZYkxpN1l0V3graEdjcVRaOFNkaVd3OStPalQ0a1NaNmVET3dKOWpkWVpXWGg5bVh3N1FQbXlydnJESkpQbDBrbHA4bUtISkMvcUtuOVVhc1A3eU5sVjhHdENJRUJPNFlUbzl3L3REcmZnQTZhTmU2b2p1aXE2MUNGdkNNalpWNlNqS3UvaUkwRDlqMk10K2hSdmk3cHc9PSIsIm1hYyI6IjNlZTJlYzkzMDhlMDU5YWY5ZDcwN2NiYjUwZDY1OGU1YzBhMzYwNGY4YTVjYzAzZjJmOGI1YzE2YmVmNjE2YTAiLCJ0YWciOiIifQ%3D%3D; __stripe_mid=e3982e51-13f1-4f6d-ab6f-5a73283fc07fc5e5a4; showMatureContent=true; tile_player_muted=true; sort_options_clips=sort%3Dview%26range%3Dall; stream_quality_cookie=720; KP_UIDz-ssn=0MrL7PuuY1ecYCmYW9QcZPd00jVlO2zPfxQAOSbGSIfsn78YPjxvtSE89ZIr1cegxvsQDVg6t7uwevGlIkg6fiYVFXvnqSb1y3r97BHthO282dKXhM02IwkcVWeQ5LVc3PDaH1U6h9JRIAP4HCNcnARkQ2ZrLjL0dfKNNjvKHR7I; KP_UIDz=0MrL7PuuY1ecYCmYW9QcZPd00jVlO2zPfxQAOSbGSIfsn78YPjxvtSE89ZIr1cegxvsQDVg6t7uwevGlIkg6fiYVFXvnqSb1y3r97BHthO282dKXhM02IwkcVWeQ5LVc3PDaH1U6h9JRIAP4HCNcnARkQ2ZrLjL0dfKNNjvKHR7I; _cfuvid=euWxqYL1tv9LpP.fp4nbZ2GYesW_sNEkHT1aliKZHGA-1736343178829-0.0.1.1-604800000; volume=0; sortOptions=sort%3Dviewers_high_to_low; cf_clearance=gP0FPFmx9TMcKB22RlvfB7.wB9Se_b.rbhzCsrOF_24-1736366052-1.2.1.1-2w4Y8TCAQ7BT_6iH_1fYhpc8wFnI0ZsUyRRnax.00ruJ1RiBOElg1CkiVluOGrncd_miW3FEHd.oSAF7C1oRxj7wDIj7YXQ2GZYc6ayqfO4NilzWXGgHAFbz72Imqw4ThFa.TOU9JLRR4Wded7jBVN1GTESpNOnjOcNzN7iIQwBm1vXOuAR3VH96UBrygsmtwsH7BsotHsahWZUwo094SS7bB8uSFvpbmJSCMX4KrNGvJXx0VkCBARZnJu_kTcdZUCWqVx_xS1mmQ2qiMSGzrBcGsUG9aoQHSg9aRL63Y1bOFYjcxv.lqiO7ifB.CAQZC6484Hn1MDA7pGlPTWjwwe1AYRJ8K7UbWi2o61D6jEmSOjsjJarWCU9XVxXtiUxFzQ01sjWd1PrNaXGPL073CA; __stripe_sid=00cbd1f6-1244-437a-b9d8-caa9090967c151d8da; XSRF-TOKEN=eyJpdiI6ImxrV2tHck9iRS9sa1lxVm5LTUp5SVE9PSIsInZhbHVlIjoiVVVISWtrbDg4amY0WGc0eXY3L3R5YUNIeHc5RHdoSVN2TEdLNUlLc2FucUlpZ0RqQnl4TlZrbjVCdnAzcWNsWmNVVVhwSkdMSzVNT2ViOWNKeG5FSnlXMGszS3FPZFFoSWtLZjYzT1Rpdk52N1o0MzU0MjQwYk9TdTVpNFI5S1IiLCJtYWMiOiI5YTJmNzIxOTA0YjNmNDBmMzAyODQ0OWE2MTc3NjQyYjU0MjE2NjU0MWZjZTUxNGEwYjA3ZjhjOTk0NmQ4MWE1IiwidGFnIjoiIn0%3D; kick_session=eyJpdiI6IkticEx2L3ZudWYxUXp5a1lCM3lwM1E9PSIsInZhbHVlIjoiWFZPRlhtY2xGWHJBMzhXajBPWU54TVlNSlpHSG5EcHd0Y1BDdGNNa09Xekd6RXNmYm5id3Z6Z0R3Y1VPYUhvSDUzR0JFSFRyNm8yU1ZmemQ1TFpmVjBGMmRtQ1dzcVYwUnJYVERxNkdOaWRBZ3JNMlNwWEhJaWgzQU1qZS9KckciLCJtYWMiOiJiZWYwODY1NDY5MWI0MzVhYmRmOTcwMDU3NGQwMjQ1ZDcwZjBiNWQ4MjhiMGYzZTM0NGE1NmUwNWQwN2FiM2I0IiwidGFnIjoiIn0%3D; zEtssaQ3IfAtrsEcXvsiAHe6PsVsric6EGVK5vAl=eyJpdiI6Ik5SNVFQY1dqUHNpakswU1lGUm1KQ2c9PSIsInZhbHVlIjoiblFKQkRIU2FtZWxLK2MwZDBKRXQ2MnIxeElPdThVZjUvVDUzR1JzenFYK250OEpFbHBsa2xTeVhXVUM1cktDWGNBTEg5QVdDNTd1UWJCM1hSL1RHNytFRldkai8yOWhMYW90T1RISFNWV1F3a29maDZkODQ3Q25OcWtzcVM1RnRPM0FoeVM2cWdZZmVHc2ZjODFuNzQybkdZK3RvU3VYUTJHVWxiYkR5M1IrQVdqRTFDNDBkZ0JwYlFXQmFYV1AwWVhMbkM1SVAxMGl3Nit3L0FMaFcvVmp1VEx5cXdvRm44ZEl1dExsZVNiTXYydWJaR3k5ZnZBbXFPaXIrcUs2bWJIYXU4ZkdxQTJlWjlxcXJlQ0ZNTEdNSVNQK0RmR3U2WU5RT0hXZEpka21OWFUzaWltRlMzRkxKSEprQ0hhTDJJd0xuTm04c1Njb2l4OXVkZXRybGp6UEhnUnpYbm9TdTZpa3Z6ZTF1ZVdXWE5IbnIvSWZVT1pzYmVDNkRnc2g2OEg0eitaNjFWYTl5MXlxOWxIZkF4NmRPWitGc3lmNGxLRkZlT2FvVXIvZz0iLCJtYWMiOiIxMGI0ZGY5ZmM5YzYzYmVhNzc2M2VkYzQ5MGY0ZjQyODdjNjk0MWY4MTUyMjhjZjFiZGIxMDZmYzJlNjE1YWI0IiwidGFnIjoiIn0%3D; __cf_bm=Nzqz3pHAEzG6Todu.QBH1U0i9Tjm_GS79v86DJuPHVQ-1736366434-1.0.1.1-ciGSZWwrHS5xrR5IG9s8gZT6pMh72Brakn0bfHYxfV1aUHxnSjAVV9Pgha25iZCtF.qYcSCDFd9BVuYIXEVN1g; _dd_s=logs=1&id=b4628b04-2919-429f-a5aa-466afaf1e236&created=1736361875977&expire=1736367427697" + +permanent_cookies = [ + "USER_LOCALE=en", + "showMatureContent=true", + "tile_player_muted=true", + "stream_quality_cookie=720", + "volume=0" +] + +headers = { + 'User-Agent': user_agent, +} + +def parse_cookie(cookie): + cookie_dict = {} + for item in cookie.split('; '): + key, value = item.split('=', 1) + cookie_dict[key] = value + return cookie_dict + +def build_categories_cookie(): + categories_cookie = {} + + categories_cookie['sortOptions'] = 'sort=viewers_high_to_low' + categories_cookie['sort_options_clips'] = 'sort=view?range=all' + categories_cookie['__stripe_mid'] = 'e3982e51-13f1-4f6d-ab6f-5a73283fc07fc5e5a4' + categories_cookie['Zb8Yq4Gq0BDtyTgD2FKm3EBvdj5TIfT8O4T6L4w7'] = 'eyJpdiI6IjA0V3Evbi9MN0xWTm5Qc21LSURucXc9PSIsInZhbHVlIjoidGdqejd6bmQxTGhkWTJQNFFxcGxHNEVUaHJ6SUxFU0Z6RU1QdkVvZE94eWVYRS80VWhZYmNQQ3JENHVSZTFQeWV3RnJPeFZtSVRocEZoT3A0dnRMSHpqY1hYS1lkYU1vaFEvN0treTJpMmpGVXp2TEdDVGpGeW9tcHBqUTRpblh4TGhmNGY2L2dURkRvc2h2czNLK2pDbHluL3loemtlc1NRaVZGUXpreTdtK3E0Ny9xV2N2UStiY3BNNzQ5SWhPSVV2YmMyNHJhTTJwV2k1dERhbTRDKzZwZGtCTTlQelBDZ2gwTzBLRVpqL3Nwa3VOcDdyWHRnMHBQYXRZSXlhaVFuRjFOWGMwa3VhTU9ZSVM5NFJ5NnFyNEZNaVJ6SUszOTJ4cmUvZ3BZU2lqYkZrY0tXVS84dExBdjltelpGdnJ3STRCVFJnQjdsMm0yOW5XUUNmOWwwWFNDbUlEbzQwNjNaK1ZxZnJDWXorN0Q5RVlGSnk4TitvbHBJL2NHZERLclhaNnh6elRKdEhhVk1zeEg0YnVyMC81TGF1bmVQSFJrZFdMaGs2d0dPSysvaTVKdm5TQWNobnFNTU1ITGdLYSIsIm1hYyI6IjQ2MGQ5MTFkYTA5YTg4NTVhNDhmZThiYTk3OThiMzJhYWYyMDA3Y2VmMmEzZWRmOGFkN2E1ZTE1MTA5MjRmNTAiLCJ0YWciOiIifQ==' + + return categories_cookie + + +def build_cookie_string(cookies): + cookie_string = "" + + for cookie in cookies: + cookie_string += f"{cookie}={cookies[cookie]}; " + + return cookie_string + +def get_clips(username): + api_url = f'https://kick.com/api/v2/channels/{username}/clips' + + params = { + 'sort': 'view', + 'time': 'all' + } + + response = requests.get(api_url, headers=headers, params=params) + + if response.status_code != 200: + print(f"Failed to fetch clips for {username}.") + return None + + data = response.json() + clips = data['clips'] + + while 'nextCursor' in data: + next_cursor = data['nextCursor'] + api_url = f'https://kick.com/api/v2/channels/{username}/clips' + + params = { + 'sort': 'view', + 'time': 'all', + 'cursor': next_cursor + } + + response = requests.get(api_url, headers=headers, params=params) + + if response.status_code != 200: + print(f"Failed to fetch clips for {username}.") + return None + + data = response.json() + clips.extend(data['clips']) + + return clips + +def parse_clip_data(clips): + data = [] + + for clip in clips: + creator_data = clip['channel'] + username = creator_data['username'] + channel_id = clip['channel_id'] + + duration = clip['duration'] + clip_id = clip['id'] + title = clip['title'] + views = clip['views'] + url = clip['video']['url'] + + data.append({'title': title, 'views': views, 'url': url, 'user_id': channel_id, 'username': username}) + + return data + +def parse_stream_data(streams): + data = [] + + for stream in streams: + creator_data = stream['channel'] + username = creator_data['username'] + channel_id = stream['channel_id'] + + title = stream['title'] + views = stream['views'] + url = stream['video']['url'] + + data.append({'title': title, 'views': views, 'url': url, 'user_id': channel_id, 'username': username}) + + return data + +def get_categories(): + url = 'https://kick.com/api/v1/subcategories' + + params = { + 'limit': 32, + 'page': 1 + } + + cookies = parse_cookie(categories_cookie) + + headers['Cookie'] = build_cookie_string(cookies) + response = requests.get(url, headers=headers, params=params) + + if response.status_code != 200: + print("Failed to fetch categories.") + return None + + data = response.json() + categories = data['data'] + + while len(data['data']) > 0: + print(f"Fetching page {params['page']}...") + params['page'] += 1 + + response = requests.get(url, headers=headers, params=params) + + if response.status_code != 200: + print("Failed to fetch categories.") + return None + + data = response.json() + categories.extend(data['data']) + + return categories + +def get_streams(category = 'pools-hot-tubs-bikinis'): + api_url = f'https://kick.com/stream/livestreams/en' + + params = { + 'page': 1, + 'limit': 24, + 'subcategory': category, + 'sort': 'desc' + } + + headers['Cookie'] = streams_cookie + response = requests.get(api_url, headers=headers, params=params) + + if response.status_code != 200: + print(f"Failed to fetch streams for {category}.") + return None + + data = response.json() + streams = data['data'] + + while len(data['data']) > 0: + params['page'] += 1 + + response = requests.get(api_url, headers=headers, params=params) + + if response.status_code != 200: + print(f"Failed to fetch clips for {username}.") + return None + + data = response.json() + clips.extend(data['data']) + + return streams + +def get_clips(username): + api_url = f'https://kick.com/api/v2/channels/{username}/clips' + + params = { + 'sort': 'view', + 'time': 'all' + } + + headers['Cookie'] = videos_cookie + response = requests.get(api_url, headers=headers, params=params) + + if response.status_code != 200: + print(f"Failed to fetch clips for {username}.") + return None + + data = response.json() + clips = data['clips'] + + while 'nextCursor' in data: + params['cursor'] = data['nextCursor'] + api_url = f'https://kick.com/api/v2/channels/{username}/clips' + + response = requests.get(api_url, headers=headers, params=params) + + if response.status_code != 200: + print(f"Failed to fetch clips for {username}.") + return None + + data = response.json() + clips.extend(data['clips']) + + return clips + +categories = get_categories() +streams = get_streams() +for stream in streams: + username = stream['username'] + clips = get_clips(username) + parsed_data = parse_clip_data(clips) \ No newline at end of file diff --git a/organize_bunny.py b/organize_bunny.py new file mode 100644 index 0000000..78bf0a8 --- /dev/null +++ b/organize_bunny.py @@ -0,0 +1,78 @@ +import os +import config +import logging + +# Set up logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[ + logging.StreamHandler() # or use logging.FileHandler('script.log') + ] +) + +# Prepare database connection +db, cursor = config.gen_connection() + +# Ensure local temp directory exists +TEMP_DIR = "temp" +os.makedirs(TEMP_DIR, exist_ok=True) + +URL_PREFIX = "https://storysave.b-cdn.net/" + +# Retrieve records from database +query = f""" + SELECT id, date, media_url, platform, username, hash + FROM media + WHERE media_url like '%none%'; +""" +cursor.execute(query) +rows = cursor.fetchall() + +# Initialize Bunny.net Storage (credentials redacted) +obj_storage = config.get_custom_storage() + +count = 0 +total = len(rows) +for row in rows: + count += 1 + pin_id, date, media_url, platform, username, file_hash = row + logging.info(f"[{count}/{total}] Processing screenshot ID: {pin_id}") + + serverPath = media_url.replace(URL_PREFIX, "").split("?")[0] + + filename = os.path.basename(serverPath) + filename = filename.replace("none", file_hash).replace("None", file_hash) + + filepath = os.path.join(TEMP_DIR, filename) + + # 2. Create new path (based on date) + year = date.year + month = str(date.month).zfill(2) + day = str(date.day).zfill(2) + formatted_date = os.path.join(str(year), month, day) + + # Extract the server path (remove domain and query) + newPath = os.path.join("media", "stories", username, filename) + new_media_url = f"{URL_PREFIX}{newPath}" + + # 3. Move file to new path + logging.info(f"Moving screenshot from {serverPath} to {newPath}") + status = obj_storage.MoveFile(serverPath, newPath) + + if status['status'] != 'success': + logging.info(f"Failed to move file {serverPath} to {newPath}. Error: {status['status']}") + continue + + # 4. Update DB + logging.info(f"Updating DB record {pin_id} to new URL\n{new_media_url}\nhttps://altpins.com/pin/{pin_id}") + cursor.execute("UPDATE media SET media_url = %s WHERE id = %s", [new_media_url, pin_id]) + db.commit() + + logging.info(f"Successfully processed screenshot {pin_id}") + + +# Close the DB connection +cursor.close() +db.close() +logging.info("All done!") \ No newline at end of file diff --git a/profile_pic.py b/profile_pic.py new file mode 100644 index 0000000..8f235e2 --- /dev/null +++ b/profile_pic.py @@ -0,0 +1,20 @@ +from storysave_api import get_hd_profile_picture +import config, funcs, os + + +db, cursor = config.gen_connection() + +cursor.execute(f"SELECT DISTINCT username, user_id FROM media WHERE user_id IS NOT NULL AND username IN (SELECT username FROM following WHERE platform = 'instagram');") +usernames = cursor.fetchall() + +for username, user_id in usernames: + profilepicurl = get_hd_profile_picture(user_id=user_id) + if not profilepicurl: + continue + + filename = os.path.basename(profilepicurl).split('?')[0] + user_dir = os.path.join('media', 'instagram', 'profile', username) + filepath = os.path.join(user_dir, filename) + + funcs.download_file(profilepicurl, filepath) + print(f"Downloaded profile picture for {username}.") \ No newline at end of file diff --git a/snapchat_backer.py b/snapchat_backer.py new file mode 100644 index 0000000..b121f21 --- /dev/null +++ b/snapchat_backer.py @@ -0,0 +1,126 @@ +import os +import json +from tqdm import tqdm + +from funcs import get_files +from snapchat import get_stories, get_highlights, get_spotlight_metadata, get_username + +# import config as altpinsConfig +import altpinsConfig + +def get_data(filepath): + try: + with open(filepath, 'r', encoding='utf-8') as f: + return json.load(f) + except: + print(f"Error reading {filepath}") + return None + +def process_story(story, username, story_type, db, cursor): + snap_urls = story.get('snapUrls', {}) + media_url = snap_urls.get('mediaUrl', '').split('?')[0] + media_id = media_url.split('/')[-1].split('.')[0].split('?')[-1] + + if media_id in existing_media_ids: + return False + + media_url = f"https://cf-st.sc-cdn.net/d/{media_url.split('/')[-1]}" + + media_preview_url = snap_urls.get('mediaPreviewUrl', '').get('value', '').split('?')[0] + media_preview_url = f"https://cf-st.sc-cdn.net/d/{media_preview_url.split('/')[-1]}" + + + timestamp = story.get('timestampInSec', {}).get('value', '') + media_type = story.get('snapMediaType') + snap_id = story.get('snapId', {}).get('value', '') + + + query = "INSERT IGNORE INTO snapchat_stories (snapId, mediaUrl, mediaPreviewUrl, timestampInSec, snapMediaType, storyType, username, media_id) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)" + cursor.execute(query, (snap_id, media_url, media_preview_url, timestamp, media_type, story_type, username, media_id)) + db.commit() + + existing_media_ids.add(media_id) + + print_emoji = '✅' if cursor.rowcount else '❌' + print(f"{print_emoji} Inserted story {media_id}") + +def process_json(json_path, db, cursor): + """ + Given a path to a JSON file, parse it and insert relevant data + into the database. + """ + + # Load JSON data + data = get_data(json_path) + username = get_username(data) + + ready_stories = [] + + # Insert stories (regular) + stories = get_stories(data) + for story in stories: + story['storyType'] = 'story' + ready_stories.append(story) + + # Insert stories (highlights) + highlights = get_highlights(data) + highlight_stories = [story for highlight in highlights for story in highlight.get('snapList', [])] + highlight_stories.sort(key=lambda x: x.get('snapIndex'), reverse=True) + for story in highlight_stories: + story['storyType'] = 'highlight' + ready_stories.append(story) + + + for story in ready_stories: + story_type = story.get('storyType') + process_story(story, username, story_type, db, cursor) + + + # Insert spotlight metadata + spotlight_metadata = get_spotlight_metadata(data) + for story in spotlight_metadata: + try: + media_id = story['videoMetadata']['contentUrl'].split('/')[-1].split('.')[0].split('?')[-1] + deepLinkUrl = story['oneLinkParams']['deepLinkUrl'].split('?')[0] + except: + continue + + if not all((media_id, deepLinkUrl)): + continue + + if deepLinkUrl in existing_spotlights: + continue + + deepLinkId = deepLinkUrl.split('/')[-1] + description = story['description'] + + insert_query = "INSERT IGNORE INTO snapchat_metadata (media_id, deepLinkUrl, description, username, deepLinkId) VALUES (%s, %s, %s, %s, %s)" + cursor.execute(insert_query, (media_id, deepLinkUrl, description, username, deepLinkId)) + db.commit() + + existing_spotlights.add(deepLinkUrl) + + print_emoji = '✅' if cursor.rowcount else '❌' + print(f"{print_emoji} Inserted spotlight {media_id}") + + os.remove(json_path) + + +db, cursor = altpinsConfig.gen_connection() + +existing_media_ids = [] +cursor.execute("SELECT media_id FROM snapchat_stories WHERE media_id != '';") +existing_media_ids = {row[0] for row in cursor.fetchall()} + +existing_spotlights = [] +cursor.execute("SELECT deepLinkUrl FROM snapchat_metadata;") +existing_spotlights = {row[0] for row in cursor.fetchall()} + +data_dir = 'data' +files = [f for f in get_files(data_dir) if f.endswith('.json')] + +# Wrap the file list with tqdm to show a progress bar +for filepath in tqdm(files, desc="Processing files", unit="file"): + process_json(filepath, db, cursor) + +db.close() \ No newline at end of file diff --git a/snapchat_master_crawler.py b/snapchat_master_crawler.py new file mode 100644 index 0000000..9299311 --- /dev/null +++ b/snapchat_master_crawler.py @@ -0,0 +1,66 @@ +from snapchat import get_all_users_data, get_stories, get_highlight_stories, get_social_medias, get_related_profiles +import os, config + +snapchat_directory = "snapchat" +media_directory = "media" +temp_directory = ".temp" +data_directory = "data" + +directory = os.path.join(media_directory, snapchat_directory) + +def get_snapchat_stories(usernames): + usernames = usernames[:5] + snapchat_users_data = get_all_users_data(usernames) + snapchat_users_data = dict(sorted(snapchat_users_data.items())) + + ready_stories = [] + + for username, data in snapchat_users_data.items(): + print(f"Getting stories for {username}...") + + data = snapchat_users_data.get(username) + if not data: + print(f"Failed to get data for {username}. Skipping.") + continue + + website_url = get_social_medias(data) + + related_profiles = get_related_profiles(data) + + stories = get_stories(data) + + stories.extend(get_highlight_stories(data)) + + for story in stories: + snap_id = story['snap_id'] + url = story['url'] + timestamp = story['timestamp'] + + # Determine file extension + extension = '.jpg' if story['media_type'] == 'image' else '.mp4' + + filename = f"{username}~{timestamp}~{snap_id}{extension}" + filepath = os.path.join(directory, filename) + + story['media_url'] = url + story['snap_id'] = snap_id + story['filepath'] = filepath + story['username'] = username + story['timestamp'] = timestamp + story['original_snap_id'] = story['original_snap_id'] + + ready_stories.append(story) + + # sort ready_stories by timestamp from oldest to newest + ready_stories.sort(key=lambda x: x['timestamp']) + + return ready_stories + +db, cursor = config.gen_connection() + +cursor.execute("SELECT username FROM following WHERE platform = 'snapchat' ORDER BY id DESC") +usernames = [row[0] for row in cursor.fetchall()] + +stories = get_snapchat_stories(usernames) + + diff --git a/snappy_cleaner.py b/snappy_cleaner.py new file mode 100644 index 0000000..a568350 --- /dev/null +++ b/snappy_cleaner.py @@ -0,0 +1,41 @@ +import config +import requests + +def is_url_accessible(url): + try: + response = requests.head(url, timeout=5) # HEAD request is usually faster and enough to check availability + return response.status_code == 200 + except requests.RequestException: + return False + +media_names = ['mediaUrl', 'mediaPreviewUrl'] + +db, cursor = config.gen_connection() + +for media_type in media_names: + cursor.execute(f"SELECT id, {media_type} FROM snapchat_stories WHERE {media_type} NOT LIKE 'https://cf-st.sc-cdn.net/d/%' AND status != 'inactive'") + + rows = cursor.fetchall() + total = len(rows) + count = 0 + + for row in rows: + count += 1 + record_id, original_url = row + + media_id = original_url.split('/')[-1] + new_url = f'https://cf-st.sc-cdn.net/d/{media_id}' + + if is_url_accessible(new_url): + print(f"✅ [{count} / {total}] {new_url} is accessible (converted from {original_url})") + + cursor.execute(f"UPDATE snapchat_stories SET {media_type} = %s, status = 'updated' WHERE id = %s", (new_url, record_id)) + db.commit() + continue + + print(f"❌ [{count} / {total}] {new_url} is NOT accessible (original: {original_url})") + cursor.execute("""UPDATE snapchat_stories SET status = 'inactive' WHERE id = %s""", (record_id,)) + db.commit() + +cursor.close() +db.close() \ No newline at end of file diff --git a/tiktok_dump.py b/tiktok_dump.py new file mode 100644 index 0000000..255d0c7 --- /dev/null +++ b/tiktok_dump.py @@ -0,0 +1,140 @@ +from datetime import datetime +from uuid import uuid4 +import funcs +import config +import cv2 +import os + +directory = 'processed_tiktoks' + +def UploadMedia(media): + platform = 'TikTok' + username = media['username'] + filepath = media['filepath'] + file_size = os.path.getsize(filepath) + thumbnail_url = None + phash = None + + filename = os.path.basename(filepath) + file_extension = os.path.splitext(filename)[1].lower() + + media_type = funcs.get_media_type(filename) + if not media_type: + print(f'Error determining media type for {filename}. Skipping...') + return False + + post_type = funcs.determine_post_type(filepath) + if not post_type: + print(f'Error determining post type for {filename}. Skipping...') + return False + + file_hash = funcs.calculate_file_hash(filepath) + if file_hash in existing_hashes: + print(f'File {filename} already exists. Skipping...') + return False + + post_date = datetime.now() + + width, height = funcs.get_media_dimensions(filepath) + + duration = funcs.get_video_duration(filepath) + + if media_type == 'image': + phash = funcs.generate_phash(filepath) + elif media_type == 'video': + try: + thumb_path = generate_thumbnail(filepath) + obj_storage.PutFile(thumb_path, f'thumbnails/{file_hash}.jpg') # this might be a problem in case of duplicate hashes + thumbnail_url = f"https://storysave.b-cdn.net/thumbnails/{file_hash}.jpg" + phash = funcs.generate_phash(thumb_path) + os.remove(thumb_path) + except: + print('Error generating thumbnail. Skipping...') + return False + + newFilename = f'{file_hash}{file_extension}' + server_path = f'media/tiktoks/{username}/{newFilename}' + + file_url = f"https://storysave.b-cdn.net/{server_path}" + + obj_storage.PutFile(filepath, server_path) # slow as fuck + + post_type = 'story' if post_type == 'stories' else 'post' + query = "INSERT IGNORE INTO media (username, media_type, media_url, width, height, post_type, date, hash, filename, duration, thumbnail, phash, platform, file_size) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" + values = (username, media_type, file_url, width, height, post_type, post_date, file_hash, filename, duration, thumbnail_url, phash, platform, file_size) + + newCursor.execute(query, values) # slower + newDB.commit() + print(f'[{newCursor.rowcount}] records updated. File {filename} uploaded to {file_url}') + + os.remove(filepath) + + return True + +def generate_thumbnail(filepath): + thumb_path = f'temp/{uuid4()}.jpg' + cap = cv2.VideoCapture(filepath) + ret, frame = cap.read() + cv2.imwrite(thumb_path, frame) + cap.release() + return thumb_path + +def get_media_data(filepath): + filename = os.path.basename(filepath) + parts = filename.split('~') + + if len(parts) == 3: + username, title, tiktok_id = parts + elif len(parts) == 2: + username, title = parts + tiktok_id = None + else: + return False + + data = {'username': username, 'filepath': filepath, 'tiktok_id': tiktok_id, 'title': title} + + return data + +def get_media(folder_path): + medias = [] + + users = os.listdir(folder_path) + for user in users: + user_folder = os.path.join(folder_path, user) + if not os.path.isdir(user_folder): + print(f"Skipping {user}") + continue + + files = os.listdir(user_folder) + for filename in files: + filepath = os.path.join(user_folder, filename) + + data = get_media_data(filepath) + if data: + medias.append(data) + + return medias + +def dump_instagram(folder_path): + medias = get_media(folder_path) + + for media in medias: + UploadMedia(media) + +if __name__ == '__main__': + print('Starting processing...') + + if not os.listdir(directory): + print('No files to process. Exiting...') + exit() + + newDB, newCursor = config.gen_connection() + + obj_storage = config.get_storage() + + newCursor.execute("SELECT hash FROM media WHERE hash IS NOT NULL AND platform = 'TikTok'") + existing_hashes = [row[0] for row in newCursor.fetchall()] + + dump_instagram(directory) + + print("Processing completed.") \ No newline at end of file diff --git a/tiktok_process.py b/tiktok_process.py new file mode 100644 index 0000000..867379e --- /dev/null +++ b/tiktok_process.py @@ -0,0 +1,58 @@ +from uuid import uuid4 +import uuid +import os + +def is_valid_uuid(uuid_to_test, version=4): + try: + uuid_obj = uuid.UUID(uuid_to_test, version=version) + except ValueError: + return False + + return str(uuid_obj) == uuid_to_test + +source_dir = 'tiktoks/' +processed_dir = 'processed_tiktoks' + +os.makedirs(processed_dir, exist_ok=True) + +users = os.listdir(source_dir) + +for user in users: + user_dir = os.path.join(source_dir, user) + if not os.path.isdir(user_dir): + print(f"Skipping {user}") + continue + + for file in os.listdir(user_dir): + filename = os.path.splitext(file)[0] + filepath = os.path.join(user_dir, file) + file_ext = os.path.splitext(file)[1] + + tiktok_id = str(uuid4()) + username = user + + if is_valid_uuid(filename): + title = '' + tiktok_id = filename + elif 'masstik' in file or 'masstiktok' in file: + data = file.split('_') + title = filename.split('_')[-1] + else: + title = filename + + + print("="*100) + title = title.encode('utf-8', 'ignore').decode('utf-8') + print(f"Username: {username}\nTitle: {title}") + + new_filename = f"{username}~{title}~{tiktok_id}{file_ext}" + new_filepath = os.path.join(processed_dir, username, new_filename) + + os.makedirs(os.path.dirname(new_filepath), exist_ok=True) + if not os.path.exists(new_filepath): + os.rename(filepath, new_filepath) + print(f"Renamed {file} to {new_filepath}") + else: + print("File with the same name already exists. Renaming aborted.") + + print("="*100) \ No newline at end of file diff --git a/twitch_downloader.py b/twitch_downloader.py new file mode 100644 index 0000000..78bbea5 --- /dev/null +++ b/twitch_downloader.py @@ -0,0 +1,124 @@ +from selenium.webdriver.common.by import By +import undetected_chromedriver as uc +from bs4 import BeautifulSoup +import requests +import base64 +import re +import os + +def format_url(url): + clean_url = re.sub(r'%[0-9A-F]{2}', '', url) + return clean_url + +def encode_offset(offset_num): + offset_base64 = str(offset_num).encode('utf-8') + offset_base64 = base64.b64encode(offset_base64).decode('utf-8') + return offset_base64 + +def get_clips(username): + url = 'https://gql.twitch.tv/gql' + + offset_num = 20 + offset_base64 = encode_offset(offset_num) + + user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' + + headers = { + 'client-id': 'kimne78kx3ncx6brgo4mv6wki5h1ko', + 'Content-Type': 'text/plain;charset=UTF-8', + 'User-Agent': user_agent + } + + data = { + "operationName":"ClipsCards__User", + "variables":{"login":username,"limit":100,}, + "extensions":{"persistedQuery":{"version":1,"sha256Hash":"4eb8f85fc41a36c481d809e8e99b2a32127fdb7647c336d27743ec4a88c4ea44"}} + } + + response = requests.post(url, headers=headers, json=data) + + clips = response.json() + + clips = clips['data']['user']['clips']['edges'] + + cleaned_clips = parse_clips(clips) + + return cleaned_clips + + +def parse_clips(clips): + """ + clips is a list of dictionaries + """ + + cleaned_clips = [] + for clip in clips: + clip = clip['node'] + + clip_id = clip['id'] + clip_url = clip['url'] + clip_title = clip['title'] + clip_view_count = clip['viewCount'] + clip_duration = clip['durationSeconds'] + + cleaned_clip = { + 'id': clip_id, + 'url': clip_url, + 'title': clip_title, + 'views': clip_view_count, + 'duration': clip_duration + } + + cleaned_clips.append(cleaned_clip) + + return cleaned_clips + +def get_video_url(video_url, driver): + driver.get(video_url) + + # Get the video element + video = driver.find_element(By.TAG_NAME, 'video') + + # Get the video source + video_src = video.get_attribute('src') + + return video_src + +def download_video(video_url, filepath): + if os.path.exists(filepath): + return filepath + + video = requests.get(video_url) + + # Download in chunks + with open(filepath, 'wb') as f: + for chunk in video.iter_content(chunk_size=1024): + f.write(chunk) + + return filepath + + +# Set up an undetected Chrome driver in headless mode +opts = uc.ChromeOptions() +opts.add_argument("--headless") +opts.add_argument("--window-size=1920,1080") + +driver = uc.Chrome(use_subprocess=True, options=opts) + +username = 'didicandy666' +clips = get_clips(username) + +for clip in clips: + clip_url = clip['clip_url'] + + filename = f"{clip['id']}.mp4" + filepath = os.path.join('clips', filename) + + if os.path.exists(filepath): + print(f"Already downloaded {filename}") + continue + + video_url = get_video_url(clip_url, driver) + + download_video(video_url, filepath) + print(f"Downloaded {filename}") \ No newline at end of file