import logging import random from datetime import datetime from time import sleep from colorama import Fore, Style from GramAddict.core.account_switcher import verify_and_switch_account from GramAddict.core.active_inference import ActiveInferenceEngine from GramAddict.core.config import Config from GramAddict.core.darwin_engine import DarwinEngine from GramAddict.core.device_facade import create_device, get_device_info from GramAddict.core.diagnostic_dump import dump_ui_state from GramAddict.core.dm_engine import _run_zero_latency_dm_loop from GramAddict.core.dojo_engine import DojoEngine # Cognitive Stack from GramAddict.core.dopamine_engine import DopamineEngine from GramAddict.core.growth_brain import GrowthBrain from GramAddict.core.log import configure_logger from GramAddict.core.perception.feed_analysis import ( FEED_MARKERS, ) from GramAddict.core.perception.feed_analysis import ( extract_post_content as _extract_post_content_impl, ) from GramAddict.core.persistent_list import PersistentList from GramAddict.core.physics.humanized_input import ( humanized_click as _humanized_click_impl, ) from GramAddict.core.physics.humanized_input import ( humanized_horizontal_swipe as _humanized_horizontal_swipe_impl, ) # ── Decomposed Modules (Phase 1 extraction) ── from GramAddict.core.physics.humanized_input import ( humanized_scroll as _humanized_scroll_impl, ) from GramAddict.core.physics.timing import ( align_active_post as _align_active_post_impl, ) from GramAddict.core.physics.timing import ( wait_for_post_loaded as _wait_for_post_loaded_impl, ) from GramAddict.core.physics.timing import ( wait_for_profile_loaded as _wait_for_profile_loaded_impl, ) from GramAddict.core.physics.timing import ( wait_for_story_loaded as _wait_for_story_loaded_impl, ) from GramAddict.core.q_nav_graph import QNavGraph from GramAddict.core.qdrant_memory import ParasocialCRMDB from GramAddict.core.resonance_engine import ResonanceEngine from GramAddict.core.sensors.honeypot_radome import HoneypotRadome from GramAddict.core.session_state import SessionState, SessionStateEncoder from GramAddict.core.swarm_protocol import SwarmProtocol from GramAddict.core.telepathic_engine import TelepathicEngine from GramAddict.core.unfollow_engine import _run_zero_latency_unfollow_loop from GramAddict.core.utils import ( check_if_updated, close_instagram, get_instagram_version, is_ad, open_instagram, random_sleep, set_time_delta, wait_for_next_session, ) from GramAddict.core.zero_latency_engine import ZeroLatencyEngine logger = logging.getLogger(__name__) def start_bot(**kwargs): configs = Config(first_run=True, **kwargs) configure_logger(configs.debug, configs.username) check_if_updated() from GramAddict.core.benchmark_guard import check_model_benchmarks check_model_benchmarks(configs) from GramAddict.core.llm_provider import log_openrouter_burn log_openrouter_burn() # Check for direct execution modes that bypass normal bot state configs.parse_args() try: from GramAddict.core.llm_provider import prewarm_ollama_models prewarm_ollama_models(configs) except Exception as e: logger.debug(f"Prewarm failed: {e}") sessions = PersistentList("sessions", SessionStateEncoder) device = create_device(configs.device_id, configs.app_id, configs.args) # ── Initialize Biomechanical Physics ── from GramAddict.core.physics.biomechanics import PhysicsBody handedness = getattr(configs.args, "handedness", "right") or "right" PhysicsBody.reset() # Clean state for new session PhysicsBody.get_session_instance(device, handedness=handedness) logger.info(f"🦴 [Biomechanics] Session initialized: {handedness}-handed thumb model") # Initialize Cognitive Stack with proper dependencies username = getattr(configs.args, "username", "") or "unknown_user" # Parse persona interests from config (comma-separated string → list) persona_raw = getattr( configs.args, "ai_target_audience", getattr(configs.args, "persona_interests", getattr(configs.args, "target_audience", "")), ) persona_interests = [p.strip() for p in persona_raw.split(",") if p.strip()] if persona_raw else [] dopamine = DopamineEngine() crm_db = ParasocialCRMDB() resonance_oracle = ResonanceEngine(username, persona_interests=persona_interests, crm=crm_db) active_inference = ActiveInferenceEngine(username) # Core Autonomous Engines zero_engine = ZeroLatencyEngine(device) nav_graph = QNavGraph(device) growth_brain = GrowthBrain(username, persona_interests=persona_interests) info = device.get_info() radome = HoneypotRadome(info.get("displayWidth", 1080), info.get("displayHeight", 2400)) swarm = SwarmProtocol(username) darwin = DarwinEngine(username) from GramAddict.core.telepathic_engine import TelepathicEngine telepathic = TelepathicEngine.get_instance() # ── Stage 0: Blank Start (Scorched Earth) ── if getattr(configs.args, "blank_start", False): logger.warning(f"⚠️ [Blank Start] Wiping ALL persistent AI memories for '{username}'...") telepathic.wipe() # Wipe navigation paths too try: from GramAddict.core.goap import PathMemory path_mem = PathMemory(username) path_mem.wipe() logger.info("🗑️ Wiped PathMemory collection.") except Exception as e: logger.warning(f"⚠️ Failed to wipe PathMemory: {e}") cognitive_stack = { "active_inference": active_inference, "dopamine": dopamine, "swarm": swarm, "resonance": resonance_oracle, "growth_brain": growth_brain, "radome": radome, "nav_graph": nav_graph, "zero_engine": zero_engine, "telepathic": telepathic, "darwin": darwin, "crm": crm_db, } from GramAddict.core.behaviors import PluginRegistry from GramAddict.core.behaviors.carousel_browsing import CarouselBrowsingPlugin from GramAddict.core.behaviors.follow import FollowPlugin from GramAddict.core.behaviors.grid_like import GridLikePlugin from GramAddict.core.behaviors.profile_guard import ProfileGuardPlugin from GramAddict.core.behaviors.story_view import StoryViewPlugin PluginRegistry.reset() plugin_registry = PluginRegistry.get_instance() plugin_registry.register(ProfileGuardPlugin()) plugin_registry.register(StoryViewPlugin()) plugin_registry.register(FollowPlugin()) plugin_registry.register(GridLikePlugin()) plugin_registry.register(CarouselBrowsingPlugin()) cognitive_stack["plugin_registry"] = plugin_registry is_first_session = True has_scanned_own_profile = False dojo = DojoEngine.get_instance(device) dojo.start() cognitive_stack["dojo"] = dojo try: while True: set_time_delta(configs.args) inside_working_hours, time_left = SessionState.inside_working_hours( configs.args.working_hours, configs.args.time_delta_session ) if not inside_working_hours: wait_for_next_session(time_left, None, sessions, device) get_device_info(device) session_state = SessionState(configs) session_state.set_limits_session() sessions.append(session_state) device.wake_up() logger.info( "-------- START AGENT SESSION: " + str(session_state.startTime.strftime("%H:%M:%S - %Y/%m/%d")) + " --------", extra={"color": f"{Style.BRIGHT}{Fore.YELLOW}"}, ) if open_instagram(device, force_restart=False): if is_first_session: # Do not blindly assume we are on HomeFeed if the app was already open somewhere else. # QNavGraph will try to dynamically resolve from UNKNOWN using the bottom navigation bar. nav_graph.current_state = "UNKNOWN" logger.info("Initializing Top-Level Graph context...") if not verify_and_switch_account(device, nav_graph, username): logger.error(f"Cannot verify or switch to target account '{username}'. Halting session.") break is_first_session = False try: running_ig_version = get_instagram_version(device) logger.debug(f"Instagram version: {running_ig_version}") except Exception as e: logger.error(f"Error retrieving the IG version: {e}") # ════════════════════════════════════════════════════════════════════════════ # 🤖 AGENT ORCHESTRATOR LOOP # ════════════════════════════════════════════════════════════════════════════ dopamine.reset_session() # Establish Initial Strategy from Config growth_brain.strategy = getattr(configs.args, "agent_strategy", "aggressive_growth") logger.info( f"🧠 [Agent Orchestrator] Session started. Strategy: {growth_brain.strategy} | Persona: {getattr(configs.args, 'agent_persona', 'unknown')}" ) from GramAddict.core.goap import GoalExecutor goap = GoalExecutor.get_instance(device, username) # --- PHASE 0: Autonomous Profile Scanning --- if getattr(configs.args, "ai_learn_own_profile", False) and not has_scanned_own_profile: logger.info( "🧠 [Identity Boot] Autonomous Profile Scanning Triggered: Learning own content...", extra={"color": f"{Fore.MAGENTA}"}, ) success = goap.achieve("learn own profile") if success: sleep(2.0) try: profile_xml = device.dump_hierarchy() all_nodes = telepathic._extract_semantic_nodes(profile_xml) raw_bio_text = [] for node in all_nodes: text = node.get("original_attribs", {}).get("text", "") desc = node.get("original_attribs", {}).get("desc", "") if len(text) > 4: raw_bio_text.append(text) if len(desc) > 4: raw_bio_text.append(desc) # Ensure grid is visible by scrolling down slightly _humanized_scroll(device, is_skip=True) sleep(1.5) # Tap first grid post to learn from actual captions if nav_graph.do("tap first image post in profile grid"): post_loaded = _wait_for_post_loaded(device, timeout=5) if post_loaded: logger.info( "📸 [Identity Boot] Reading recent posts to analyze actual content vibe...", extra={"color": f"{Fore.CYAN}"}, ) for _ in range(3): post_xml = device.dump_hierarchy() if isinstance(post_xml, str): post_data = _extract_post_content(post_xml) if post_data.get("caption"): raw_bio_text.append(post_data["caption"]) elif post_data.get("description"): raw_bio_text.append(post_data["description"]) _humanized_scroll(device, is_skip=False) sleep(2.0) device.press("back") sleep(1.5) # Deduplicate while preserving order unique_texts = list(dict.fromkeys(raw_bio_text)) condensed_profile = " | ".join(unique_texts[:30]) # Take top substantive elements logger.debug(f"Captured Profile Payload: {condensed_profile[:200]}...") prompt = ( "You are an analytical profiling engine. Read the following text ripped straight from an Instagram profile page " "(which contains bio, follower counts, button labels, and recent post descriptions). " "Determine the exact 'persona' (2-3 words) and 'vibe' (3-4 adjectives) that represents THIS specific user.\n\n" f"PROFILE TEXT: {condensed_profile}\n\n" 'Respond ONLY in valid JSON format: {"persona": "", "vibe": ""}' ) from GramAddict.core.llm_provider import query_llm model = getattr(configs.args, "ai_condenser_model", "llama3.2:1b") url = getattr(configs.args, "ai_condenser_url", "http://localhost:11434/api/generate") response_dict = query_llm(url=url, model=model, prompt=prompt, format_json=True, timeout=120) if response_dict and isinstance(response_dict, dict) and "persona" in response_dict: new_persona_raw = response_dict.get("persona", "") new_vibe = response_dict.get("vibe", "") if new_persona_raw and new_vibe: new_persona_list = ( [p.strip() for p in new_persona_raw.split(",") if p.strip()] if "," in new_persona_raw else [new_persona_raw] ) resonance_oracle.update_identity(new_persona_list, new_vibe) growth_brain.persona_interests = new_persona_list # Overwrite config values in-memory setattr(configs.args, "agent_persona", new_persona_raw) setattr(configs.args, "ai_vibe", new_vibe) except Exception as e: logger.error(f"Failed to learn own profile autonomously: {e}") else: logger.warning("🧠 [Identity Boot] Failed to navigate to own profile.") has_scanned_own_profile = True while not dopamine.is_app_session_over(): # 1. Ask the Growth Brain for a Desire current_desire = growth_brain.get_current_desire(dopamine) if current_desire == "ShiftContext": logger.info("🧠 [Free Will] Boredom critical. Forcing app restart to clear context.") device.app_stop(device.app_id) random_sleep(2.0, 4.0) device.app_start(device.app_id, use_monkey=True) random_sleep(4.0, 6.0) dopamine.boredom = max(0.0, dopamine.boredom * 0.2) continue # 2. Map Desire to Sub-Feed target_map = { "DiscoverNewContent": ["ExploreFeed", "ReelsFeed"], "NurtureCommunity": ["HomeFeed", "StoriesFeed"], "SocialReciprocity": ["FollowingList", "MessageInbox"], } import secrets options = target_map.get(current_desire, ["HomeFeed"]) current_target = secrets.choice(options) logger.info(f"🧠 [Agent Orchestrator] Desire '{current_desire}' -> Routed to {current_target}") logger.info(f"⚡ Navigating to {current_target}") success = nav_graph.navigate_to(current_target, zero_engine) if success: if current_target == "ExploreFeed": # [Phase 2] Visual selection of the first post logger.info("📱 [Vision Core] Evaluating explore grid for the most resonant post...") res_eval = telepathic.evaluate_grid_visuals(device, persona_interests) if res_eval: logger.info(f"✨ [Vision Core] Clicking visual match: {res_eval.get('semantic')}") _humanized_click(device, res_eval["x"], res_eval["y"]) else: logger.info("📱 Falling back to default: Opening first explore item from the grid...") nav_graph.do("tap first image in explore grid") # Wait for post to actually load (poll for feed markers) post_loaded = _wait_for_post_loaded(device, nav_graph=nav_graph, timeout=5) if not post_loaded: logger.warning("❌ Post failed to open from grid. Retrying next loop.") continue elif current_target == "StoriesFeed": logger.info("📱 Locating story tray on HomeFeed...") nav_graph.do("tap story ring avatar") post_loaded = _wait_for_story_loaded(device, timeout=5) if not post_loaded: logger.warning("❌ Stories failed to open from HomeFeed. Retrying next loop.") continue if current_target == "StoriesFeed": result = _run_zero_latency_stories_loop(device, configs, session_state, cognitive_stack) elif current_target == "FollowingList": result = _run_zero_latency_unfollow_loop( device, zero_engine, nav_graph, configs, session_state, current_target, cognitive_stack ) elif current_target == "MessageInbox": result = _run_zero_latency_dm_loop( device, zero_engine, nav_graph, configs, session_state, current_target, cognitive_stack ) elif current_target == "SearchFeed": result = _run_zero_latency_search_loop( device, zero_engine, nav_graph, configs, session_state, current_target, cognitive_stack ) else: is_reels = current_target == "ReelsFeed" result = _run_zero_latency_feed_loop( device, zero_engine, nav_graph, configs, session_state, current_target, cognitive_stack, is_reels=is_reels, ) # Evaluate outcome from loop if result in ("BOREDOM_CHANGE_FEED", "FEED_EXHAUSTED"): logger.info(f"🧠 [Free Will] Sub-routine in {current_target} exhausted/bored.") if result == "BOREDOM_CHANGE_FEED": dopamine.reset_boredom() # Reset boredom allowing new desire continue # Loops back to get_current_desire() elif result == "CONTEXT_LOST": logger.warning( f"⚠️ Context was lost in {current_target}. Forcing app restart and returning to HomeFeed to escape softlock." ) device.app_stop(device.app_id) random_sleep(1.0, 2.0) device.app_start(device.app_id, use_monkey=True) random_sleep(3.0, 5.0) nav_graph.current_state = "UNKNOWN" # Force context reset to HomeFeed so we don't repeat the same error loop continue else: logger.info(f"Session concluding due to state: {result}") break # Session over or unhandled state else: logger.error(f"Aborting target {current_target} due to navigation failure.") break logger.info(f"Session complete. Boredom: {dopamine.boredom:.1f}%. Sleeping before next iteration...") close_instagram(device) random_sleep(30, 60) except KeyboardInterrupt: logger.info("🛑 Caught KeyboardInterrupt! Exiting immediately.") raise finally: if "dojo" in locals() and dojo.is_running: dojo.stop() # ❄️ Release VRAM try: from GramAddict.core.llm_provider import unload_ollama_models unload_ollama_models(configs) # Give the thread a tiny bit of time to send the request before process exits sleep(0.5) except Exception as e: logger.debug(f"Failed to trigger VRAM cleanup: {e}") # FEED_MARKERS: imported from GramAddict.core.perception.feed_analysis (see top imports) def _wait_for_post_loaded(device, timeout=5, nav_graph=None): """Delegate to physics.timing. See GramAddict.core.physics.timing.""" return _wait_for_post_loaded_impl(device, timeout=timeout, nav_graph=nav_graph) def _wait_for_story_loaded(device, timeout=5): """Delegate to physics.timing. See GramAddict.core.physics.timing.""" return _wait_for_story_loaded_impl(device, timeout=timeout) def _wait_for_profile_loaded(device, timeout=5): """Delegate to physics.timing. See GramAddict.core.physics.timing.""" return _wait_for_profile_loaded_impl(device, timeout=timeout) def _humanized_scroll(device, is_skip=False, resonance_score=None): """Delegate to physics module. See GramAddict.core.physics.humanized_input.""" _humanized_scroll_impl(device, is_skip=is_skip, resonance_score=resonance_score) def _humanized_click(device, x, y, double=False, sleep_mod=1.0): """Delegate to physics module. See GramAddict.core.physics.humanized_input.""" _humanized_click_impl(device, x, y, double=double, sleep_mod=sleep_mod) def _humanized_horizontal_swipe(device, start_x, end_x, y, duration_ms): """Delegate to physics module. See GramAddict.core.physics.humanized_input.""" _humanized_horizontal_swipe_impl(device, start_x, end_x, y, duration_ms) # has_carousel_in_view: imported from GramAddict.core.perception.feed_analysis (see top imports) def _interact_with_profile(device, configs, username, session_state, sleep_mod, logger, cognitive_stack=None): """Deep interaction on a profile: Stories, Grid Likes, Follows""" import random from colorama import Fore if cognitive_stack is None: cognitive_stack = {} if hasattr(session_state, "my_username") and username == session_state.my_username: logger.info(f"🤝 [Deep Interaction] Skipping own profile @{username} to prevent self-interactions.") return # info = device.get_info() # w, h = info.get("displayWidth", 1080), info.get("displayHeight", 2400) xml_check = device.dump_hierarchy() if not isinstance(xml_check, str): return xml_check_lower = xml_check.lower() # ── 1. Profile Guards (Private / Empty) ── if "this account is private" in xml_check_lower or "konto ist privat" in xml_check_lower: logger.info( f"🔒 [Profile Guard] @{username} is private. Aborting deep interaction.", extra={"color": f"{Fore.YELLOW}"} ) return if "no posts yet" in xml_check_lower or "noch keine beiträge" in xml_check_lower: logger.info( f"📭 [Profile Guard] @{username} has no posts. Aborting deep interaction.", extra={"color": f"{Fore.YELLOW}"}, ) return if getattr(configs.args, "ignore_close_friends", False): if "enge freunde" in xml_check_lower or "close friend" in xml_check_lower: logger.info( f"💚 [Profile Guard] @{username} is a Close Friend. Ignoring completely.", extra={"color": "\\033[32m"} ) return # ── 1.5 Visual Vibe Check (AI Aesthetic Quality Guard) ── vibe_check_pct = float(getattr(configs.args, "visual_vibe_check_percentage", 0)) / 100.0 if vibe_check_pct > 0 and random.random() < vibe_check_pct: from GramAddict.core.telepathic_engine import TelepathicEngine telepathic = cognitive_stack.get("telepathic") or TelepathicEngine.get_instance() persona_interests = cognitive_stack.get("persona_interests", []) if cognitive_stack else [] vibe_result = telepathic.evaluate_profile_vibe(device, persona_interests) if vibe_result: score = vibe_result.get("quality_score", 5) matches_niche = vibe_result.get("matches_niche", True) if score < 5 or not matches_niche: logger.warning( f"🚫 [Vibe Check] Profile @{username} rejected (Score: {score}, Niche: {matches_niche}). Reason: {vibe_result.get('reason')}" ) return else: logger.info( f"✅ [Vibe Check] Profile @{username} approved (Score: {score}). Continuing interaction.", extra={"color": "\\033[36m"}, ) # Profile Scraping (Phase 11: Data Extraction) if getattr(configs.args, "scrape_profiles", False): try: logger.info(f"📊 [Scraping] Extracting metadata for @{username}...", extra={"color": f"{Fore.CYAN}"}) from GramAddict.core.telepathic_engine import TelepathicEngine telepathic = TelepathicEngine.get_instance() crm = cognitive_stack.get("crm") if cognitive_stack else None # Simple heuristic for extraction (followers/following) f_node = telepathic.find_best_node(xml_check, "Followers count text or number", device=device) fg_node = telepathic.find_best_node(xml_check, "Following count text or number", device=device) bio_node = telepathic.find_best_node(xml_check, "User biography or description text", device=device) scraped_data = { "username": username, "followers": f_node.get("text") if f_node else "unknown", "following": fg_node.get("text") if fg_node else "unknown", "bio": bio_node.get("text") if bio_node else "No bio", } logger.info( f"✅ [Scraping] Data acquired: {scraped_data['followers']} followers, {scraped_data['following']} following." ) session_state.add_interaction(source=username, succeed=False, followed=False, scraped=True) if crm: crm.log_interaction(username, "scrape", metadata=scraped_data) except Exception as e: logger.warning(f"⚠️ [Scraping] Error during profiling: {e}") # ── Execute Plugin Registry Behaviors ── from GramAddict.core.behaviors import BehaviorContext, PluginRegistry ctx = BehaviorContext( device=device, configs=configs, session_state=session_state, cognitive_stack=cognitive_stack, context_xml=xml_check, sleep_mod=sleep_mod, username=username, ) registry = PluginRegistry.get_instance() results = registry.execute_all(ctx) # Check if any plugin requested skipping further profile interaction for result in results: if result.executed and result.should_skip: logger.debug("⏭️ Profile interaction aborted early by a plugin.") return # Let the native UI momentum scroll finish just like a human watching the feed sleep(random.uniform(1.2, 2.0)) # Hesitation mistake (humans sometimes pause randomly) if random.randint(1, 100) <= 5: sleep(random.uniform(1.5, 3.5)) def _align_active_post(device): """Delegate to physics.timing. See GramAddict.core.physics.timing.""" return _align_active_post_impl(device) def _extract_post_content(context_xml: str) -> dict: """Delegate to perception module. See GramAddict.core.perception.feed_analysis.""" return _extract_post_content_impl(context_xml) def _run_zero_latency_stories_loop(device, configs, session_state, cognitive_stack): """ Top-Level Stories Bingewatching Loop Mimics a user opening the story tray and endlessly tapping through stories. Relies on DopamineEngine for early exit (boredom). """ import random from time import sleep from colorama import Fore logger.info("🎬 [StoriesFeed] Starting native story binging loop...", extra={"color": f"{Fore.CYAN}"}) dopamine = cognitive_stack.get("dopamine") info = device.get_info() w, h = info.get("displayWidth", 1080), info.get("displayHeight", 2400) sleep_mod = float(getattr(configs.args, "speed_multiplier", 1.0)) stories_arg_str = getattr(configs.args, "stories", "999") or "999" try: min_st, max_st = map(int, stories_arg_str.split("-")) limit = random.randint(min_st, max_st) except Exception: try: limit = int(stories_arg_str) except ValueError: limit = 999 iteration = 0 while not dopamine.is_app_session_over() and iteration < limit: iteration += 1 # Check for boredom if dopamine.wants_to_change_feed(): logger.info("🧠 [Dopamine] Bored. Escaping StoriesFeed to seek new stimuli.") device.press("back") # Attempt to back out to feed sleep(random.uniform(1.0, 2.0) * sleep_mod) return "BOREDOM_CHANGE_FEED" xml_dump = device.dump_hierarchy() if not xml_dump: logger.warning("Failed to dump UI hierarchy in StoriesFeed.") return "CONTEXT_LOST" if getattr(configs.args, "ignore_close_friends", False): if "enge freunde" in xml_dump.lower() or "close friend" in xml_dump.lower(): logger.info( "💚 [Anti-Friend] Story is from a Close Friend. Swiping horizontally to skip User.", extra={"color": "\\033[32m"}, ) _humanized_horizontal_swipe( device, start_x=int(w * 0.8), end_x=int(w * 0.2), y=int(h * 0.5), duration_ms=250 ) sleep(random.uniform(0.5, 1.0) * sleep_mod) continue # Tap right to go next _humanized_click(device, int(w * 0.85), int(h * 0.5), sleep_mod=sleep_mod) sleep(random.uniform(2.0, 5.0) * sleep_mod) logger.info("🎬 [StoriesFeed] Session completed naturally.") device.press("back") return "FEED_EXHAUSTED" def _run_zero_latency_feed_loop( device, zero_engine, nav_graph, configs, session_state, job_target, cognitive_stack, is_reels=False ): """ The ultra-fast autonomous Free Will loop. ALL engines are wired in a closed feedback loop: - ResonanceEngine evaluates content → drives Dopamine + Darwin + Interactions - ActiveInference predicts UI state → modulates caution level - GrowthBrain applies circadian pacing → modulates ALL sleep durations - Darwin is the SOLE dwell controller → no duplicate sleep calls - SwarmProtocol emits pheromones after successful interactions """ logger.info(f"🔄 Entering Zero-Latency Interaction Pool. Feed: {job_target}") dopamine = cognitive_stack.get("dopamine") darwin = cognitive_stack.get("darwin") resonance = cognitive_stack.get("resonance") ai = cognitive_stack.get("active_inference") growth = cognitive_stack.get("growth_brain") swarm = cognitive_stack.get("swarm") # Track interaction outcomes for end-of-session learning session_outcomes = [] consecutive_marker_misses = 0 consecutive_ads = 0 from GramAddict.core.session_state import SessionState iteration = 0 while not dopamine.is_app_session_over(): limit_tuple = session_state.check_limit(SessionState.Limit.ALL) if any(limit_tuple): logger.info("🚧 [Limits] Total interactions limit reached. Ending session.") break iteration += 1 # ── Global Governance (GrowthBrain Strategy Oracle) ── governance_decision = growth.evaluate_governance(dopamine, job_target, is_reels) if growth else "STAY" if governance_decision == "SHIFT_CONTEXT": # Store session learning before leaving if growth: growth.refine_persona(session_outcomes) return "BOREDOM_CHANGE_FEED" elif governance_decision == "CHECK_CURIOSITY": logger.info("👀 [Curiosity] Spontaneously checking DMs / Notifications...") explore_target = random.choice(["MessageInbox", "Notifications"]) if explore_target == "MessageInbox": nav_graph.do("tap direct message icon inbox") sleep(random.uniform(3.0, 7.0)) else: nav_graph.do("tap heart icon notifications") sleep(random.uniform(3.0, 7.0)) _humanized_scroll(device, is_skip=True) sleep(random.uniform(2.0, 4.0)) # Return to feed nav_graph.navigate_to("HomeFeed", zero_engine) sleep(random.uniform(1.0, 2.5)) logger.info("🔙 [Curiosity] Done exploring. Returning to feed.") # ── Circadian Pacing (GrowthBrain) ── circadian = growth.get_circadian_pacing() if growth else 1.0 caution_mod = ai.get_sleep_modifier() if ai else 1.0 sleep_mod = circadian * caution_mod # Combined sleep multiplier if dopamine.wants_to_doomscroll(): logger.info("🏃 [Drive] Doomscrolling engaged. Fast-skipping feed.", extra={"color": f"{Fore.CYAN}"}) # Reverse-flick correction logic is now handled internally by _humanized_scroll _humanized_scroll(device, is_skip=True) sleep(random.uniform(0.1, 0.4) * sleep_mod) continue context_xml = device.dump_hierarchy() if cognitive_stack.get("radome"): context_xml = cognitive_stack.get("radome").sanitize_xml(context_xml) # ── PRE-EMPTIVE AD SKIP (3-Tier Escape Cascade) ── if is_ad(context_xml, cognitive_stack): consecutive_ads += 1 if consecutive_ads >= 6: logger.error( "🚨 [Ad Trap] Stuck on ad for 6+ cycles! Force-navigating to HomeFeed to escape deadlock.", extra={"color": f"{Fore.RED}"}, ) nav_graph.navigate_to("HomeFeed", zero_engine) consecutive_ads = 0 elif consecutive_ads >= 3: logger.warning( "📺 [Anti-Stuck] Stuck on ad! Executing aggressive double-skip.", extra={"color": f"{Fore.RED}"}, ) _humanized_scroll(device, is_skip=True) sleep(0.5) _humanized_scroll(device, is_skip=True) else: logger.info("📺 fast-skipping ad (no AI needed)...") _humanized_scroll(device, is_skip=True) sleep(random.uniform(0.5, 1.0) * sleep_mod) continue consecutive_ads = 0 # ── PRE-EMPTIVE CLOSE FRIENDS SKIP ── if getattr(configs.args, "ignore_close_friends", False): if "enge freunde" in context_xml.lower() or "close friend" in context_xml.lower(): logger.info( "💚 [Anti-Friend] Post is from a Close Friend. Skipping to prevent weird interactions.", extra={"color": "\\033[32m"}, ) _humanized_scroll(device, is_skip=True) sleep(random.uniform(0.5, 1.0) * sleep_mod) continue # ── Zero-Node Recovery (Graceful Degradation) ── telepathic = TelepathicEngine.get_instance() interactive_nodes = telepathic._extract_semantic_nodes(context_xml) if len(interactive_nodes) == 0: logger.warning( "⚠️ [Anomaly Handler] 0 interactive nodes extracted. UI is blind/stuck! Pressing BACK and scrolling...", extra={"color": f"{Fore.YELLOW}"}, ) device.press("back") sleep(0.5) _humanized_scroll(device) sleep(random.uniform(1.0, 2.0) * sleep_mod) continue # ── Context Validation (Is the bot ACTUALLY on a post?) ── has_feed_markers = any(marker in context_xml for marker in FEED_MARKERS) # ── Autonomous Obstacle Detection ── from GramAddict.core.situational_awareness import SituationalAwarenessEngine, SituationType sae = SituationalAwarenessEngine(device) has_obstacle = sae.perceive(context_xml) == SituationType.OBSTACLE_MODAL if has_obstacle: consecutive_marker_misses += 1 if consecutive_marker_misses >= 3: logger.error("❌ Lost context completely. Aborting feed loop to force reset.") sae.unlearn_current_state(context_xml) dump_ui_state(device, "context_lost", {"feed": job_target, "misses": consecutive_marker_misses}) return "CONTEXT_LOST" if consecutive_marker_misses == 2: logger.warning( "⚠️ [Anomaly Handler] Hardware 'Back' button failed to clear obstacle. Engaging VLM to find escape route...", extra={"color": f"{Fore.YELLOW}"}, ) telepathic = TelepathicEngine.get_instance() best_node = telepathic.find_best_node( context_xml, intent_description="Dismiss Obstacle/Modal", device=device ) if best_node: logger.info( f" -> Recovery attempt! Clicking {best_node.get('semantic', 'Dismiss Button')} at ({best_node['x']}, {best_node['y']})" ) device.click(best_node["x"], best_node["y"]) sleep(2.5) consecutive_marker_misses = 0 continue else: logger.warning("⚠️ [Anomaly Handler] No viable escape route found. Forcing scroll...") _humanized_scroll(device) sleep(random.uniform(1.0, 2.0) * sleep_mod) continue logger.warning( "⚠️ [Self-Check] Obstacle (sheet/dialog/keyboard) is blocking the view! Pressing BACK to dismiss...", extra={"color": f"{Fore.YELLOW}"}, ) device.press("back") sleep(0.5) continue elif not has_feed_markers: consecutive_marker_misses += 1 if consecutive_marker_misses >= 3: logger.error("❌ Lost context completely. Aborting feed loop to force reset.") sae.unlearn_current_state(context_xml) dump_ui_state(device, "context_lost", {"feed": job_target, "misses": consecutive_marker_misses}) return "CONTEXT_LOST" if consecutive_marker_misses == 2: logger.warning( "⚠️ [Anomaly Handler] Hardware 'Back' button failed to clear obstacle. Engaging VLM to find escape route...", extra={"color": f"{Fore.YELLOW}"}, ) telepathic = TelepathicEngine.get_instance() best_node = telepathic.find_best_node( context_xml, intent_description="Dismiss Obstacle/Modal", device=device ) if best_node: logger.info( f" -> Recovery attempt! Clicking {best_node.get('semantic', 'Dismiss Button')} at ({best_node['x']}, {best_node['y']})" ) device.click(best_node["x"], best_node["y"]) sleep(2.5) # Verification: Check if markers are now visible post_recovery_xml = device.dump_hierarchy() if any(marker in post_recovery_xml for marker in FEED_MARKERS): logger.info("✅ [Recovery] Obstacle cleared successfully. Learning this button works.") telepathic.confirm_click("Dismiss Obstacle/Modal") consecutive_marker_misses = 0 continue else: logger.warning("⚠️ [Recovery] Click failed to clear obstacle. Learning from failure.") telepathic.reject_click("Dismiss Obstacle/Modal") # Fallback to scroll logger.warning("⚠️ [Anomaly Handler] No viable escape route found. Forcing scroll...") _humanized_scroll(device) sleep(random.uniform(1.0, 2.0) * sleep_mod) continue logger.warning( "⚠️ [Self-Check] Feed markers missing. Mid-scroll or tall post? Scrolling to reveal markers...", extra={"color": f"{Fore.YELLOW}"}, ) # DO NOT press 'back' here as we are just on the timeline. It would trigger a scroll-to-top refresh. _humanized_scroll(device) sleep(random.uniform(1.0, 2.0) * sleep_mod) continue consecutive_marker_misses = 0 # ── Perfect Snapping Enforcer ── # Fixes the issue where UI gets stuck halfway between two posts. if _align_active_post(device): # Update context_xml because the screen just shifted context_xml = device.dump_hierarchy() if cognitive_stack.get("radome"): context_xml = cognitive_stack.get("radome").sanitize_xml(context_xml) # ── Content Extraction (The Bot's Eyes) ── post_data = _extract_post_content(context_xml) # ── Self-Correction: Did extraction actually work? ── has_content = bool(post_data.get("username") or post_data.get("description")) if not has_content: logger.warning( "⚠️ [Self-Check] On a post but content extraction failed. Skipping.", extra={"color": f"{Fore.YELLOW}"} ) dump_ui_state(device, "content_extraction_failed", {"feed": job_target}) _humanized_scroll(device) sleep(random.uniform(0.5, 1.2) * sleep_mod) continue logger.info( f"✅ Post by @{post_data['username'] or '?'}: {post_data['description'][:60]}...", extra={"color": f"{Fore.GREEN}"}, ) # ── Execute Plugin Registry Behaviors (Feed Level) ── from GramAddict.core.behaviors import BehaviorContext, PluginRegistry ctx = BehaviorContext( device=device, configs=configs, session_state=session_state, cognitive_stack=cognitive_stack, context_xml=context_xml, sleep_mod=sleep_mod, post_data=post_data, username=post_data.get("username", ""), ) registry = PluginRegistry.get_instance() plugin_results = registry.execute_all(ctx) skip_feed = False for result in plugin_results: if result.executed and result.should_skip: logger.debug("⏭️ Feed interaction aborted early by a plugin.") skip_feed = True break if skip_feed: _humanized_scroll(device) sleep(random.uniform(0.5, 1.2) * sleep_mod) continue # ── Active Inference: Predict (before action) ── if ai: ai.predict_state(["row_feed", "button_like"]) # ── Ad Check (Structural) ── if is_ad(context_xml, cognitive_stack): consecutive_ads += 1 if consecutive_ads >= 3: logger.warning( "🚩 [Ad Trap] Detected 3 consecutive ads. High density zone. Force scrolling to escape..." ) _humanized_scroll(device) consecutive_ads = 0 else: logger.info("⏭️ [Ad Skip] Detected sponsored content. Skipping interaction.") _humanized_scroll(device) sleep(random.uniform(0.5, 1.2) * sleep_mod) continue consecutive_ads = 0 # ── Resonance Engine (Real AI Content Evaluation) ── res_score = resonance.calculate_resonance(post_data) if resonance else 0.5 # ── Visual Vibe Check for Content (Using LLM More) ── vibe_check_pct = float(getattr(configs.args, "visual_vibe_check_percentage", 0)) / 100.0 if vibe_check_pct > 0 and random.random() < vibe_check_pct: telepathic = cognitive_stack.get("telepathic") persona_interests = cognitive_stack.get("persona_interests", []) if telepathic: vibe_result = telepathic.evaluate_post_vibe(device, persona_interests) if vibe_result: visual_score = vibe_result.get("quality_score", 5) / 10.0 # scale 0-1 # Combine text resonance and visual resonance res_score = (res_score * 0.3) + (visual_score * 0.7) logger.info( f"👁️ [Vision Core] Adjusted Resonance with Visual Score: {res_score:.2f} (Visual: {visual_score:.2f})" ) if not vibe_result.get("matches_niche", True): logger.info("🚫 [Vision Core] Content strictly rejected as out-of-niche.") res_score = 0.1 # Force skip # ── Dopamine Engine (fed with REAL resonance, not random) ── dopamine.process_content( {"score": res_score * 10, "quality": "high" if res_score > 0.7 else "medium" if res_score > 0.4 else "low"} ) # ── Human-like Selective Skipping (Anti-Bot Drip) ── base_skip_prob = 0.85 if res_score < 0.35 else 0.45 if res_score < 0.70 else 0.10 # User defined interact_percentage modulates the skip rate. # Default is 80%, so factor = 1.0. If 100%, factor = 0.0 (never skip). interact_pct_val = float(getattr(configs.args, "interact_percentage", 80)) / 100.0 skip_factor = max(0.0, (1.0 - interact_pct_val) * 5.0) skip_prob = base_skip_prob * skip_factor rnd_skip = random.random() logger.info( f"⚙️ [Decision] Resonance {res_score:.2f} -> Base Skip: {base_skip_prob:.2f}. Config Interact={interact_pct_val*100}% -> Skip Factor: {skip_factor:.2f}. Final Skip Prob: {skip_prob:.2f} (Roll: {rnd_skip:.2f})" ) if rnd_skip < skip_prob: logger.info( f"⏭️ [Resonance Skip] Human-like selective engagement ({skip_prob*100:.0f}% chance). Skipping post." ) session_outcomes.append( {"username": post_data.get("username", ""), "action": "skip", "resonance": res_score} ) _humanized_scroll(device) sleep(random.uniform(0.5, 1.2) * sleep_mod) continue # ── The Rabbit Hole (Deep Dive into high-resonance profiles) ── if res_score >= 0.9 and random.random() < 0.4: logger.info( "💥 [Rabbit Hole] Extreme resonance! Sidetracking into user profile...", extra={"color": f"{Fore.MAGENTA}"}, ) if nav_graph.do("tap post username") is True: sleep(random.uniform(1.2, 2.5) * sleep_mod) _humanized_scroll(device, is_skip=True) sleep(random.uniform(0.5, 1.5) * sleep_mod) logger.info("🔙 [Rabbit Hole] Exiting profile back to main feed.") device.press("back") sleep(random.uniform(0.8, 1.5) * sleep_mod) # ── Darwin: SOLE Dwell Controller (micro-wobble + proof of resonance) ── # Darwin handles ALL viewing time, scrolling, and wobble. No duplicate sleep. if darwin: darwin.execute_micro_wobble(device) darwin.execute_proof_of_resonance( device, res_score, text_length=len(post_data.get("description", "")), nav_graph=nav_graph, zero_engine=zero_engine, configs=configs, resonance_oracle=resonance, username=post_data.get("username", "unknown"), context_xml=context_xml, ) else: # Absolute fallback if Darwin is not available sleep(random.uniform(2.0, 5.0) * sleep_mod) # ── Interaction Engine ── did_interact = False did_comment = False interact_chance = float(getattr(configs.args, "interact_percentage", 80)) / 100.0 profile_context = "" # ── Profile Learning (Before heavy engagement) ── target_user = post_data.get("username", "target") # Pull follow chance early to see if the user explicitly wants high follow rates follow_chance_val = float(getattr(configs.args, "follow_percentage", 30)) / 100.0 if getattr(configs.args, "agent_strategy", "") == "passive_learning": follow_chance_val = 0.0 # Force 0 for dry-runs # If resonance is poor, never engage deeply. rnd_follow = random.random() if res_score < 0.40: will_visit_profile = False else: profile_learning_chance = float(getattr(configs.args, "profile_learning_percentage", 0)) / 100.0 rnd_profile_learn = random.random() will_visit_profile = ( res_score >= 0.8 or (follow_chance_val > 0.0 and rnd_follow < follow_chance_val) or (profile_learning_chance > 0.0 and rnd_profile_learn < profile_learning_chance) ) logger.info( f"⚙️ [Decision] Profile Visit -> Resonance: {res_score:.2f} (>=0.8?), Follow Config: {follow_chance_val*100}% (Roll: {rnd_follow:.2f}) -> Proceed: {will_visit_profile}" ) if will_visit_profile: logger.info( f"🕵️‍♂️ [Profile Learning] Visiting @{target_user}'s profile to learn context or follow...", extra={"color": f"{Fore.CYAN}"}, ) # Navigate to profile via Targeted UX to prevent clicking Ads nav_success = False telepathic = cognitive_stack.get("telepathic") crm = cognitive_stack.get("crm") if telepathic: xml_dump = device.dump_hierarchy() nodes = telepathic._extract_semantic_nodes(xml_dump) # Targeted check for the actual user to avoid hallucinated Ad clicks (e.g. 'raidrpg') for n in nodes: res_id = n.get("resource_id", "").lower() text_lower = (n.get("text", "") or n.get("content_desc", "")).lower() # 🛡️ Hardened Targeted UX: Use strict equality or boundary checks if possible, or exact substring. if target_user.lower() in text_lower.split() or target_user.lower() == text_lower: if ( "profile_name" in res_id or "title" in res_id or "username" in res_id or "avatar" in res_id or not res_id ): if n.get("x") and n.get("y"): logger.info( f"⚡ [Targeted UX] Exact matched username '{target_user}' on screen. Tapping directly." ) device.click(n["x"], n["y"]) nav_success = True break if not nav_success: logger.info( f"⚠️ [Targeted UX] Could not find explicit text for '{target_user}'. Falling back to generalized intent..." ) nav_success = nav_graph.do("tap post username") if nav_success: _wait_for_profile_loaded(device, timeout=5) sleep(random.uniform(0.5, 1.0) * sleep_mod) # Extract context try: if telepathic: # Fetch dump again post-navigation xml_dump = device.dump_hierarchy() nodes = telepathic._extract_semantic_nodes(xml_dump) texts = [] actual_username = None for n in nodes: t = n.get("text", "").strip() or n.get("content_desc", "").strip() res_id = n.get("resource_id", "").lower() # Identify the actual profile we landed on (e.g., from top action bar) if not actual_username and t and len(t) > 2: # 🛡️ Hardened Context Correction: Expand matching IDs for the profile action bar if ( "action_bar" in res_id or "profile_name" in res_id or "username" in res_id or "title" in res_id ): if n.get("y", 999) < 300: # Must be at the top of the screen actual_username = t.split("•")[0].strip() # Ignore small numbers, but keep bio/followers if t and t not in texts and len(t) > 1: texts.append(t) # Correct context if targeted UX failed and we landed on the wrong profile if actual_username and actual_username.lower() != target_user.lower(): logger.warning( f"⚠️ [Context Correction] Visited '{actual_username}' instead of '{target_user}'. Updating target...", extra={"color": f"{Fore.YELLOW}"}, ) target_user = actual_username profile_context = " | ".join(texts[:15]) logger.info( f"🧠 [Profile Learning] Extracted bio/stats: {profile_context[:50]}...", extra={"color": f"{Fore.GREEN}"}, ) if crm and target_user: crm.log_profile_context(target_user, profile_context) except Exception as e: logger.debug(f"Failed to learn profile context: {e}") # Execute Deep Profile Interaction (Likes, Follows, Stories) _interact_with_profile(device, configs, target_user, session_state, sleep_mod, logger, cognitive_stack) # Return to feed logger.info("🔙 [Profile Learning] Returning to main feed.") device.press("back") _wait_for_post_loaded(device, nav_graph=nav_graph) sleep(random.uniform(1.0, 1.5) * sleep_mod) rnd_interact = random.random() logger.info( f"⚙️ [Decision] Sub-Interactions (Likes/Comments) -> Interact Config: {interact_chance*100}% (Roll: {rnd_interact:.2f})" ) if rnd_interact < interact_chance: likes_chance = float(getattr(configs.args, "likes_percentage", 100)) / 100.0 if session_state.check_limit(SessionState.Limit.LIKES): likes_chance = 0.0 # If user explicitly configures likes_chance > 0, we lower the strict AI resonance requirement rnd_like = random.random() needs_like = likes_chance > 0.0 and rnd_like < likes_chance will_like = needs_like or res_score >= 0.35 # Global Override: Passive Learning (Dry Run) if getattr(configs.args, "agent_strategy", "") == "passive_learning": logger.info( "🚫 [Safety Onboarding] Skipping Like action (Agent is learning the UI).", extra={"color": f"{Fore.MAGENTA}"}, ) will_like = False logger.info( f"⚙️ [Decision] Like -> Like Config: {likes_chance*100}% (Roll: {rnd_like:.2f}), Resonance: {res_score:.2f} -> Proceed: {will_like}" ) if will_like: logger.info("❤️ [Interaction] Deciding like method...") xml_check = device.dump_hierarchy() if not isinstance(xml_check, str): xml_check = "" xml_check_lower = xml_check.lower() is_reel_feed = "reel_viewer" in xml_check_lower or "clips_viewer" in xml_check_lower is_liked_feed = ( "gefällt mir nicht mehr" in xml_check_lower or "unlike" in xml_check_lower or 'content-desc="liked"' in xml_check_lower ) use_double_tap = growth.wants_to_double_tap(is_reel=is_reel_feed) if use_double_tap: if is_liked_feed: logger.debug( "Telepathic Like failed or post was unlikable (already liked). Skipping increment." ) else: info = device.get_info() w, h = info.get("displayWidth", 1080), info.get("displayHeight", 2400) offset_x = random.randint(int(w * 0.2), int(w * 0.8)) offset_y = random.randint(int(h * 0.3), int(h * 0.7)) logger.info(f"❤️ [Interaction] Double-Tapping organically at ({offset_x}, {offset_y})") _humanized_click(device, offset_x, offset_y, double=True, sleep_mod=sleep_mod) session_state.totalLikes += 1 sleep(random.uniform(1.2, 2.5) * sleep_mod) did_interact = True else: logger.info("❤️ [Interaction] Liking post via Heart Button...") success = nav_graph.do("tap like button") if success: session_state.totalLikes += 1 sleep(random.uniform(1.2, 2.5) * sleep_mod) did_interact = True else: logger.debug("Telepathic Like failed or post was unlikable. Skipping increment.") # Comment: requires high resonance alignment comment_chance = float(getattr(configs.args, "comment_percentage", 40)) / 100.0 if session_state.check_limit(SessionState.Limit.COMMENTS): comment_chance = 0.0 # If user explicitly configures comment_chance > 0, we lower the strict AI resonance requirement rnd_comment = random.random() needs_comment = comment_chance > 0.0 and rnd_comment < comment_chance will_comment = needs_comment or res_score >= 0.4 # Global Override: Passive Learning (Dry Run) if getattr(configs.args, "agent_strategy", "") == "passive_learning": logger.info( "🚫 [Safety Onboarding] Skipping Comment action (Agent is learning the UI).", extra={"color": f"{Fore.MAGENTA}"}, ) will_comment = False logger.info( f"⚙️ [Decision] Comment -> Comment Config: {comment_chance*100}% (Roll: {rnd_comment:.2f}), Resonance: {res_score:.2f} -> Proceed: {will_comment}" ) if will_comment: logger.info("💬 [Interaction] Entering Comment Sheet for deep engagement...") success = nav_graph.do("tap comment button") if success is True: sleep(random.uniform(2.0, 4.0) * sleep_mod) # 1. Scrape Context from the comment sheet sheet_xml = device.dump_hierarchy() # 🛡️ [Semantic Gate] Verify we are actually in the comment sheet via basic semantic checks if not any(x in sheet_xml.lower() for x in ["comment", "reply", "kommentieren", "antworten"]): logger.warning( "❌ [Ambiguity Guard] Transition reported success, but Comment markers not found in UI. Bailing engagement." ) did_interact = False _humanized_scroll(device) continue existing_comments = [] comment_nodes = [] telepathic = TelepathicEngine.get_instance() try: all_nodes = telepathic._extract_semantic_nodes(sheet_xml) for node in all_nodes: text = node.get("original_attribs", {}).get("text", "") # If it's a substantive string (e.g., > 10 chars) and isn't a UI button if text and len(text) > 10 and not telepathic._is_forbidden_action(node): if not any( k in text.lower() for k in [ "reply", "translate", "view replies", "see translation", "hide replies", "comment", ] ): existing_comments.append(text) comment_nodes.append({"text": text, "semantic_string": node.get("semantic_string")}) except Exception as e: logger.error(f"Failed to extract comments semantically: {e}") # --- Deep Engagement Actions (Liking and Sub-Commenting) --- replying_to = None telepathic = TelepathicEngine.get_instance() try: for idx, c_node in enumerate(comment_nodes): if len(c_node["text"]) > 15: # Filter out short garbage # 40% chance to like a substantive comment if random.random() < 0.4: # Use Telepathic to find the like button for this specific comment text intent = f"Heart like button for comment: '{c_node['text'][:20]}...'" xml_dump = device.dump_hierarchy() like_btn = telepathic.find_best_node(xml_dump, intent, device=device) if like_btn and not like_btn.get("skip"): _humanized_click(device, like_btn["x"], like_btn["y"], sleep_mod=sleep_mod) sleep(random.uniform(0.8, 1.5)) # Verification: Simple XML change check if device.dump_hierarchy() != xml_dump: telepathic.confirm_click(intent) logger.info( f"❤️ [Interaction] Liked user comment: '{c_node['text'][:30]}...'" ) else: telepathic.reject_click(intent) # 20% chance to randomly visit commenter's profile # [Phase 3] Deep engagement decision if resonance.wants_to_deep_engage(res_score): intent = f"Avatar profile picture for commenter: '{c_node['text'][:20]}...'" xml_dump = device.dump_hierarchy() avatar_node = telepathic.find_best_node(xml_dump, intent, device=device) if avatar_node: logger.info( "🦸‍♂️ [Randomization] Navigating to commenter's profile to explore..." ) _humanized_click( device, avatar_node["x"], avatar_node["y"], sleep_mod=sleep_mod ) sleep(random.uniform(2.5, 4.5) * sleep_mod) # Verification: Did we reach a profile? post_xml = device.dump_hierarchy() if "profile" in post_xml.lower() or "button_follow" in post_xml.lower(): telepathic.confirm_click(intent) _interact_with_profile( device, configs, "commenter", session_state, sleep_mod, logger, cognitive_stack, ) logger.info("🔙 [Randomization] Returning to comment sheet.") device.press("back") sleep(random.uniform(1.5, 3.0) * sleep_mod) else: telepathic.reject_click(intent) logger.warning( "⚠️ [Randomization] Failed to reach commenter profile. Learning from failure." ) # 15% chance to Sub-Comment (Reply) # [Phase 3] Reply decision if resonance.wants_to_reply(res_score) and not replying_to: intent = f"Reply button for comment: '{c_node['text'][:20]}...'" xml_dump = device.dump_hierarchy() reply_btn = telepathic.find_best_node(xml_dump, intent, device=device) if reply_btn: _humanized_click(device, reply_btn["x"], reply_btn["y"], sleep_mod=sleep_mod) sleep(random.uniform(1.2, 2.0)) # Verification: Did the screen change or input field appear? if device.dump_hierarchy() != xml_dump: telepathic.confirm_click(intent) replying_to = c_node["text"] logger.info( f"🔁 [Interaction] Replying directly to comment: '{replying_to[:30]}...'" ) else: telepathic.reject_click(intent) except Exception as e: logger.debug(f"[Interaction] Deep engagement parsing failed: {e}") # [Phase 2] Determine Suggested Action based on Resonance + CRM suggested_action = resonance.get_suggested_action(post_data.get("username"), res_score) logger.info( f"🧠 [Governance] CRM/Resonance Suggestion: {suggested_action} (Stage: {resonance.crm.get_relationship_stage(post_data.get('username')).get('stage', 0)})" ) # Decide if we proceed with commenting skip_comment = suggested_action == "SKIP" or (suggested_action == "LIKE" and random.random() < 0.9) if skip_comment: logger.info("🧠 [Governance] Decision: Relationship not warm enough for comment. Skipping.") else: # 2. Contextual Prompting context_str = "\\n- ".join(existing_comments[:3]) vibe = getattr(configs.args, "ai_vibe", "friendly") # Persona & CRM Context injection persona_context = growth.get_persona_context() if growth else "" crm_context = ( resonance.crm.get_conversation_context(post_data.get("username")) if resonance.crm else "" ) if replying_to: prompt = ( f"Reply to this Instagram comment as a '{vibe}' person.\n" f"Context: {persona_context}\n" f"Past history with user: {crm_context}\n" f"Their comment: '{replying_to}'\n" f"Post caption: {post_data.get('description', 'No caption')[:200]}\n\n" "Write a natural reply under 15 words. Max 1 emoji. No generic phrases.\n" "Output ONLY the comment text, nothing else." ) else: prompt = ( f"Write an Instagram comment as a '{vibe}' person.\n" f"Context: {persona_context}\n" f"Past history with user: {crm_context}\n" f"Post by @{post_data.get('username')}: {post_data.get('description', 'No caption')[:200]}\n" f"Other comments: {context_str[:300]}\n\n" "Write a specific, insightful comment under 15 words. Max 1 emoji.\n" "Ask a question or share a specific observation. No generic phrases.\n" "Output ONLY the comment text, nothing else." ) try: from GramAddict.core.llm_provider import query_llm from GramAddict.core.stealth_typing import ghost_type model = getattr(configs.args, "ai_condenser_model", "llama3.2:1b") url = getattr(configs.args, "ai_condenser_url", "http://localhost:11434/api/generate") logger.info(f"🧠 [Comment Gen] Sending prompt to {model} (Timeout: 120s)...") response_dict = query_llm( url=url, model=model, prompt=prompt, format_json=False, timeout=120, max_tokens=60, temperature=0.7, ) if response_dict and "response" in response_dict: clean_comment = response_dict["response"].strip().strip('"').strip("'") if clean_comment and len(clean_comment) > 2: # Tap the Edit Text field to focus keyboard telepathic = cognitive_stack.get("telepathic") if telepathic: comment_box = telepathic.find_best_node( sheet_xml, "Comment input text box editfield", device=device ) if comment_box: is_dry = getattr(configs.args, "dry_run_comments", False) if is_dry: logger.info( f"🚫 [DRY RUN] Generated comment: '{clean_comment}'. Skipping UI injection.", extra={"color": f"{Fore.MAGENTA}"}, ) sleep(1.5) else: device.click(comment_box["x"], comment_box["y"]) sleep(random.uniform(1.2, 2.2)) # Verification: Did the keyboard open or cursor move to box? # We check if the XML changed and focus is on an edittext post_focus_xml = device.dump_hierarchy() if "editText" in post_focus_xml.lower() or post_focus_xml != sheet_xml: telepathic.confirm_click("Comment input text box editfield") else: telepathic.reject_click("Comment input text box editfield") # Inject via Ghost Keyboard ghost_type(device, clean_comment) # Umentscheidung (Change of mind) # Umentscheidung (Change of mind / Hesitation) [Phase 3] if growth.evaluate_hesitation(): logger.info( "🧠 [Umentscheidung] Hesitating. Deciding not to post the comment.", extra={"color": f"{Fore.YELLOW}"}, ) sleep(random.uniform(1.0, 3.0)) if random.random() < 0.5: # Rapid backspace (Manual deletion) for _ in range(len(clean_comment) + 2): device.press("del") sleep(random.uniform(0.01, 0.05)) else: # Press back to trigger Discard popup device.press("back") sleep(1.0) xml_dump = device.dump_hierarchy() discard_btn = telepathic.find_best_node( xml_dump, "Discard or Verwerfen popup button to cancel comment", device=device, ) if discard_btn: device.click(discard_btn["x"], discard_btn["y"]) telepathic.confirm_click( "Discard or Verwerfen popup button to cancel comment" ) logger.info("🔙 [Umentscheidung] Comment successfully aborted.") sleep(2.0) else: # Tap Post sleep(random.uniform(0.5, 1.5)) pre_post_xml = device.dump_hierarchy() post_btn = telepathic.find_best_node( pre_post_xml, "Post submit comment button", device=device ) if post_btn: device.click(post_btn["x"], post_btn["y"]) sleep(random.uniform(2.0, 3.5)) # Verification: Did the button disappear or layout change? post_post_xml = device.dump_hierarchy() # If "Post" button is gone from the area or XML changed significantly if ( "button_post" not in post_post_xml.lower() or post_post_xml != pre_post_xml ): telepathic.confirm_click("Post submit comment button") session_state.totalComments += 1 did_comment = True logger.info( f"✅ [Interaction] Comment deployed successfully: '{clean_comment}'", extra={"color": f"{Fore.GREEN}"}, ) else: telepathic.reject_click("Post submit comment button") logger.warning( "⚠️ [Comment] Post button click didn't seem to work. Learning from failure." ) except Exception as e: logger.error(f"❌ [Interaction] AI Comment deployment failed: {e}") # Safely exit the comment sheet from GramAddict.core.situational_awareness import SituationalAwarenessEngine, SituationType sae = SituationalAwarenessEngine(device) _exit_xml = device.dump_hierarchy() if sae.perceive(_exit_xml) == SituationType.OBSTACLE_MODAL: device.press("back") sleep(1.0) _exit_xml2 = device.dump_hierarchy() if sae.perceive(_exit_xml2) == SituationType.OBSTACLE_MODAL: device.press("back") sleep(1.0) did_interact = True # Repost: requires medium-high resonance alignment [Phase 3] if growth.wants_to_repost(res_score): logger.info("🔁 [Interaction] Reposting highly resonant content...", extra={"color": f"{Fore.CYAN}"}) # Fast Path: Check if Repost button is ALREADY on the screen (Direct Repost for Reels) telepathic = TelepathicEngine.get_instance() current_xml = device.dump_hierarchy() direct_repost = ( telepathic.find_best_node( current_xml, "Repost interaction button with two arrows", device=device, threshold=0.90 ) if is_reels else None ) success = True if direct_repost and not direct_repost.get("skip"): logger.info("⚡ [Fast Path] Found direct Repost button. Skipping share sheet.") repost_btn = direct_repost else: success = nav_graph.do("tap share button") if success is True: sleep(random.uniform(1.8, 3.5) * sleep_mod) xml_dump = device.dump_hierarchy() repost_btn = telepathic.find_best_node( xml_dump, "Repost interaction button with two arrows", device=device ) else: repost_btn = None if success is True and repost_btn and not repost_btn.get("skip"): _humanized_click(device, repost_btn["x"], repost_btn["y"], sleep_mod=sleep_mod) sleep(random.uniform(2.0, 4.0) * sleep_mod) # Verification: Did the share menu close or repost confirmation appear? post_xml = device.dump_hierarchy() repost_success = post_xml != current_xml or "reposted" in post_xml.lower() if repost_success: telepathic.confirm_click("Repost interaction button with two arrows") logger.info( "✅ [Interaction] Content successfully reposted to feed/followers.", extra={"color": f"{Fore.GREEN}"}, ) did_interact = True else: telepathic.reject_click("Repost interaction button with two arrows") logger.warning("⚠️ [Repost] Click failed to trigger repost. Learning from failure.") # Close share menu if still open device.press("back") sleep(random.uniform(1.0, 2.0) * sleep_mod) # ── Parasocial CRM & SwarmProtocol ── crm = cognitive_stack.get("crm") session_state.add_interaction( source=post_data.get("username", "Unknown"), succeed=did_interact, followed=False, scraped=False ) if did_interact: logger.info( f"DEBUG CRM logic: did_interact={did_interact}, crm={bool(crm)}, username='{post_data.get('username')}'" ) if crm and post_data.get("username"): intent_type = "comment_reply" if did_comment else "like" crm.log_interaction(post_data["username"], intent_type) if swarm: post_hash = f"{post_data['username']}_{post_data['description'][:30]}" swarm.emit_pheromone(post_hash, "interacted") # ── Track outcome for GrowthBrain session learning ── session_outcomes.append( { "username": post_data.get("username", ""), "action": "interact" if did_interact else "skip", "resonance": res_score, } ) # ── Active Inference: Evaluate prediction (after action) ── if ai: # Wait for content to settle _wait_for_post_loaded(device, timeout=3, nav_graph=nav_graph) post_action_xml = device.dump_hierarchy() ai.evaluate_prediction(post_action_xml) # ── Advance to next post ── _humanized_scroll(device, resonance_score=res_score) sleep(random.uniform(0.5, 1.2) * sleep_mod) # ── End of session: Store learnings ── if growth: growth.refine_persona(session_outcomes) if darwin: now = datetime.now() duration_minutes = (now - session_state.startTime).total_seconds() / 60.0 followers_gained = sum(session_state.totalFollowed.values()) darwin.evaluate_session_end(duration_minutes, followers_gained) logger.info("🏁 [Drive] Feed loop terminated. Session over.") return "FEED_EXHAUSTED" def _run_zero_latency_search_loop( device, zero_engine, nav_graph, configs, session_state, current_target, cognitive_stack ): """ Executes the autonomous Search & Interact logic. """ logger.info("🧠 [Search Engine] Initiating keyword discovery...", extra={"color": f"{Style.BRIGHT}{Fore.CYAN}"}) import random from GramAddict.core.bot_flow import _humanized_click, sleep from GramAddict.core.stealth_typing import ghost_type # Select search term search_str = getattr(configs.args, "search", "") interests_str = getattr(configs.args, "persona_interests", "") all_terms = [t.strip() for t in (search_str + "," + interests_str).split(",") if t.strip()] if not all_terms: all_terms = ["photography", "travel", "nature"] # Fail-safe keyword = random.choice(all_terms) logger.info(f"🔎 Searching for keyword: '{keyword}'") # 1. Navigation to Search is handled by nav_graph.navigate_to("SearchFeed") or Explore # We assume we are on the Explore tab now (Global Navigation Bar) try: xml = device.dump_hierarchy() telepathic = cognitive_stack.get("telepathic") # Find search bar search_bar = telepathic.find_best_node(xml, "Search edit text box or magnifying glass input", device=device) if search_bar: _humanized_click(device, search_bar["x"], search_bar["y"]) sleep(1.5) ghost_type(device, keyword, speed="fast") device.press("enter") sleep(3.0) # 2. Pick a result (Top, Accounts, or Tags) results_xml = device.dump_hierarchy() target_result = telepathic.find_best_node( results_xml, "First relevant search result (Account or Hashtag)", device=device ) if target_result: logger.info(f"👉 Selecting search result: {target_result.get('semantic', 'Top result')}") _humanized_click(device, target_result["x"], target_result["y"]) sleep(3.0) # 3. If we are on a hashtag feed or account profile, start a mini-feed loop # We reuse _run_zero_latency_feed_loop but with a special boredom multiplier return _run_zero_latency_feed_loop( device, zero_engine, nav_graph, configs, session_state, f"Search:{keyword}", cognitive_stack ) return "BOREDOM_CHANGE_FEED" except Exception as e: logger.error(f"⚠️ [Search Engine] Failed: {e}") return "CONTEXT_LOST"