import asyncio import aiohttp import os import sys import json from typing import Optional, Dict, Any from dotenv import load_dotenv import urllib.parse import re load_dotenv() SKETCHERS_UNITED_SESSION = os.environ.get('SKETCHERS_UNITED_SESSION') REMEMBER_WEB = os.environ.get('REMEMBER_WEB') TARGET_URL = 'https://sketchersunited.org/chats' API_PASSWORD = os.environ.get('API_PASSWORD') XSRF_TOKEN = os.environ.get('XSRF_TOKEN') def get_session_cookie_header() -> Optional[Dict[str, str]]: if not SKETCHERS_UNITED_SESSION or not REMEMBER_WEB: print("Missing required session or remember_web environment variables.", file=sys.stderr) return None cookies_to_send = [] cookies_to_send.append(f"sketchers_united_session={SKETCHERS_UNITED_SESSION}") cookies_to_send.append(f"remember_web_59ba36addc2b2f9401580f014c7f58ea4e30989d={REMEMBER_WEB}") if XSRF_TOKEN: cookies_to_send.append(f"XSRF-TOKEN={XSRF_TOKEN}") cookie_header = "; ".join(cookies_to_send) return { 'Cookie': cookie_header, 'Accept': 'application/json', 'User-Agent': 'usernames chat checking bot', 'Content-Type': 'application/json', } def get_post_headers(xsrf_token: str) -> Dict[str, str]: return { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0', 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'en-US,en;q=0.5', 'X-XSRF-TOKEN': xsrf_token, 'Sec-GPC': '1', 'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'same-origin', 'Priority': 'u=0', } async def make_new_json_request(user_id: int): url = f'http://localhost/api/token/{user_id}' data = { 'password': API_PASSWORD } headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', } async with aiohttp.ClientSession() as session: async with session.post(url, json=data, headers=headers) as response: response_text = await response.text() if(len(response_text) < 14): return response_text else: return False async def post_chat_update(session: aiohttp.ClientSession, chat_object: Dict[str, Any], xsrf_token: str): pattern = r'/(\d+)/' match = re.search(pattern, chat_object.get('icon_url')) token = await make_new_json_request(match.group(1)) form_data = aiohttp.FormData() if(token): form_data.add_field('text', f"""You've signed up for the Secret Sketchers event, your link is http://localhost/profile/{token} - it is private and should only be used by you. \nDon't want to be in it anymore? Go to http://localhost/withdraw/{token} - note that withdrawing is permanent and you cannot re-enter the event.""") else: form_data.add_field('text', f"""Something went wrong, Sorry I will fix it sooner or later... maybe""") headers = get_post_headers(xsrf_token) try: cookie_headers = get_session_cookie_header() if cookie_headers: headers['Cookie'] = cookie_headers['Cookie'] async with session.post( f"{TARGET_URL}/{chat_object.get('id')}/messages", data=form_data, headers=headers ) as response: if response.status not in [200, 201]: text = await response.text() print(f" Status: {response.status}. Response: {text}", file=sys.stderr) except aiohttp.ClientError as e: print(f"-> POST ERROR: Client error during update for chat ID {chat_object.get('id', 'N/A')}: {e}", file=sys.stderr) except Exception as e: print(f"-> POST ERROR: Unexpected error during update for chat ID {chat_object.get('id', 'N/A')}: {e}", file=sys.stderr) async def process_chats(): global XSRF_TOKEN async with aiohttp.ClientSession() as session: headers = get_session_cookie_header() if not headers: return async with session.get(TARGET_URL, headers=headers) as response: if response.status != 200: print(f"failed: {response.status}", file=sys.stderr) return try: chat_data_array = await response.json() if not isinstance(chat_data_array, list): print("not json", file=sys.stderr) return except aiohttp.ContentTypeError: print("also not json", file=sys.stderr) return fresh_xsrf_token = None for cookie in session.cookie_jar: if cookie.key == "XSRF-TOKEN": fresh_xsrf_token = urllib.parse.unquote(cookie.value) break XSRF_TOKEN = fresh_xsrf_token for index, chat_object in enumerate(chat_data_array): if(chat_object.get('type') == 'private'): seen_at_value = chat_object.get('seen_at') is_unread = False try: if isinstance(seen_at_value, dict): is_unread = True # print(f"Chat {index}: Status: DICT (New Chat) -> Unread: True") else: latest_created_at = chat_object.get('latest', {}).get('created_at', 0) seen_at_int = int(seen_at_value) if seen_at_value is not None else 0 is_unread = seen_at_int < latest_created_at # print(f"Chat {index}: Seen at ({seen_at_int}) < Latest message ({latest_created_at}) -> Unread: {is_unread}") except (AttributeError, TypeError, ValueError) as e: is_unread = False if is_unread: await post_chat_update(session, chat_object, fresh_xsrf_token) async def main_loop(): while True: try: await process_chats() await asyncio.sleep(10) except asyncio.CancelledError: print("cancelled normally (ok knowing my luck the error is gonna be this one every single time even tho it never actually is an error like this!) cute puppy: Hiiii") break except Exception as e: print(f"error {e}") await asyncio.sleep(10) if __name__ == "__main__": asyncio.run(main_loop())