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()