Files
instagram-bot/tmp_bot_flow.py

1795 lines
87 KiB
Python

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": "<value>", "vibe": "<value>"}'
)
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"