import os import re import json import time from datetime import datetime import requests # --- CONFIGURATION & CREDENTIALS --- USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" # Hardcoded Mistral API key (⚠️ Store this securely in Environment Variables in production!) MISTRAL_API_KEY = "CHTDjXRY5kE0odBx7jG064NccPJT3Cik" # JSON Persistence Configuration HISTORY_FILE = "replied_threads.json" MAX_CACHE_SIZE = 100 def load_history(): """Loads the recently replied thread IDs from a local JSON file.""" if os.path.exists(HISTORY_FILE): try: with open(HISTORY_FILE, "r", encoding="utf-8") as f: data = json.load(f) if isinstance(data, list): print(f"[History] Loaded {len(data)} tracked threads from {HISTORY_FILE}") return data except Exception as e: print(f"[History Error] Could not read cache file, starting fresh: {e}") return [] def save_history(history_list): """Saves the tracking list directly into the local JSON file, keeping it capped.""" if len(history_list) > MAX_CACHE_SIZE: history_list = history_list[-MAX_CACHE_SIZE:] try: with open(HISTORY_FILE, "w", encoding="utf-8") as f: json.dump(history_list, f, indent=4) except Exception as e: print(f"[History Error] Failed to write to JSON file: {e}") def fetch_latest_html(): """Fetches real-time HTML content from the catalog board.""" catalog_url = "https://indiachan.top/boards/b/catalog" headers = { "User-Agent": USER_AGENT, "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } try: response = requests.get(catalog_url, headers=headers, timeout=12) if response.status_code == 200: return response.text else: print(f"[Fetch Warning] Received unexpected status code: {response.status_code}") return "" except Exception as e: print(f"[Fetch Error] Failed to retrieve live catalog text: {e}") return "" def get_zero_reply_thread(html_content, history_set): """Finds the newest thread with exactly 0 replies using robust regex tracking.""" valid_threads = [] container_pattern = re.compile( r']*>.*?\s*', re.DOTALL | re.IGNORECASE ) containers = container_pattern.findall(html_content) for chunk in containers: replies_match = re.search(r'data-replies="(\d+)"', chunk) created_match = re.search(r'data-created="(\d+)"', chunk) pinned_match = re.search(r'data-pinned="(\d+)"', chunk) href_match = re.search(r'class="thread-catalog-link"\s*href="([^"]+)"', chunk) # Guard clause: make sure it has required metadata if not (replies_match and href_match and pinned_match and created_match): continue # Target ONLY threads with exactly 0 replies. Ignore sticky/pinned threads. if pinned_match.group(1) == '1' or replies_match.group(1) != '0': continue href = href_match.group(1) board_id_match = re.search(r'/boards/([^/]+)/thread/(\d+)', href) if board_id_match: board_name = board_id_match.group(1) post_id = board_id_match.group(2) else: continue if post_id in history_set: continue # Extract and clean text snippet safely text_match = re.search(r'
.*?
\s*]*>(.*?)', chunk, re.DOTALL | re.IGNORECASE) raw_text = text_match.group(1) if text_match else "" clean_text = re.sub(r'<[^>]+>', '', raw_text) clean_text = clean_text.replace("'", "'").replace(""", '"').replace("&", "&") clean_text = clean_text.replace(">", ">").replace("<", "<").strip() valid_threads.append({ 'created': int(created_match.group(1)), 'board': board_name, 'post_id': post_id, 'context': clean_text if clean_text else "[no text context/image post]" }) if not valid_threads: return None # Sort threads so that the highest 'created' value (newest epoch) comes first valid_threads.sort(key=lambda x: x['created'], reverse=True) return valid_threads[0] def generate_reply_with_mistral(thread_context, post_id): """Calls Mistral API to generate an explicit ultra-short response style.""" url = "https://api.mistral.ai/v1/chat/completions" prompt = f""" you are an imageboard user browsing https://indiachan.top/ rules deny with facts reply with exactly one short sentence use only lowercase english letters and spaces do not use numbers do not use punctuation do not use emojis do not use special characters do not use line breaks keep responses very short Current Thread Snippet: "{thread_context}" """ payload = { "model": "mistral-large-latest", "messages": [{"role": "user", "content": prompt}], "temperature": 0.8, "stream": False } headers = { "Content-Type": "application/json", "Accept": "application/json", "Authorization": f"Bearer {MISTRAL_API_KEY.strip()}" } try: response = requests.post(url, json=payload, headers=headers, timeout=15) if response.status_code == 200: resp_json = response.json() if 'choices' in resp_json and resp_json['choices']: ai_text = resp_json['choices'][0]['message']['content'].strip() # Enforce exact string sanitation requested ai_text = re.sub(r'[^a-zA-Z\s]', '', ai_text).lower() return f"{ai_text}", ai_text else: print(f"[Mistral Error] Unexpected JSON format layout: {resp_json}") return None, None else: print(f"[Mistral API Error] HTTP Status {response.status_code}: {response.text}") return None, None except Exception as e: print(f"[Mistral Native Error]: {e}") return None, None def submit_reply(board_name, post_id, text_content): """Submits a direct HTTP POST reply back onto the forum engine.""" reply_url = f"https://indiachan.top/boards/{board_name}/thread/{post_id}/reply" payload = { "content": text_content, "delete_password": "apnimaasepuch", "sage": "1" } headers = { "User-Agent": USER_AGENT, "Content-Type": "application/x-www-form-urlencoded" } try: response = requests.post(reply_url, data=payload, headers=headers, timeout=15) # Imageboards redirect to the thread page (302) or show success (200) upon standard posting formats if response.status_code in [200, 302]: print(f"[Post Success] Connection established, response status code: {response.status_code}") return True else: print(f"[Post Failed] Server rejected request with status: {response.status_code}") return False except Exception as e: print(f"[Post Error] Direct submission connection failed: {e}") return False def main(): print("Starting imageboard 0-reply sniper bot...\n") if not MISTRAL_API_KEY.strip(): print("[Critical] MISTRAL_API_KEY string is empty. System stopping.") return replied_history = load_history() while True: print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Checking catalog for fresh content...") try: sample_html = fetch_latest_html() if sample_html: history_set = set(replied_history) target_thread = get_zero_reply_thread(sample_html, history_set) if target_thread: board = target_thread['board'] tid = target_thread['post_id'] context = target_thread['context'] print(f"[Found Target] Newest thread with 0 replies found: /{board}/thread/{tid}") full_reply, raw_ai_text = generate_reply_with_mistral(context, tid) if full_reply: print(f"[Response Generated]\n{full_reply}\n") success = submit_reply(board, tid, full_reply) if success: print(f"[Success] Reply posted successfully to /{board}/.") if tid not in replied_history: replied_history.append(tid) save_history(replied_history) print(f"[Cache Status] Tracked {len(replied_history)} items safely in JSON storage.") else: print("[Error] Forum submission failed.") else: print("[Error] Failed to generate text using Mistral backend API.") else: print(f"[Info] No new unreplied threads found matching condition variables.") else: print("[Warning] Pull catalog function yielded no data.") except Exception as e: print(f"[Error] Execution Loop broken: {e}") print("Sleeping for 5 minutes before checking catalog items...\n") time.sleep(15) if __name__ == "__main__": main()