su-secret-santa/bot/main.py

175 lines
6.3 KiB
Python

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(5)
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(5)
if __name__ == "__main__":
asyncio.run(main_loop())