commit 3a3290cffff37928a36154bb4df54770e058efbc Author: Dev Timofijo Date: Sun Aug 30 20:41:20 2026 +0000 feat: initial commit - Instagram giveaway analyzer & auto-inscriber diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f0c844 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Dependencies +__pycache__/ +*.pyc +*.pyo +*.pyd +venv/ +.env +*.env* + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store \ No newline at end of file diff --git a/ai/analyzer.py b/ai/analyzer.py new file mode 100644 index 0000000..69fbda9 --- /dev/null +++ b/ai/analyzer.py @@ -0,0 +1,109 @@ +"""Analizador de IA local simple para detección de sorteos.""" +import sys +import os +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from config import GIVEAWAY_KEYWORDS, AI_MODE + + +class GiveawayAnalyzer: + """Analizador simple basado en reglas para detectar sorteos en texto.""" + + def __init__(self, mode="rule_based"): + self.mode = mode + self.keywords = GIVEAWAY_KEYWORDS + + def is_giveaway(self, text): + """ + Determinar si un texto corresponde a un sorteo. + + Args: + text: Texto de la descripción o caption de Instagram + + Returns: + bool: True si parece un sorteo + """ + if not text: + return False + + text_lower = text.lower() + + # Contar coincidencias de palabras clave + matches = sum(1 for kw in self.keywords if kw.lower() in text_lower) + + # Heurística: si hay al menos 2 palabras clave, es probable que sea un sorteo + if matches >= 2: + return True + + # Patrones más específicos + giveaway_patterns = [ + "#sorteo" in text_lower, + "#giveaway" in text_lower, + "sorteo" in text_lower and ("ganar" in text_lower or "win" in text_lower), + "participa" in text_lower, + "etiqueta" in text_lower or "tag" in text_lower, + "comenta" in text_lower, + ] + + if any(giveaway_patterns): + return True + + return False + + def extract_requirements(self, text): + """ + Extraer los requisitos del sorteo del texto. + + Args: + text: Texto del sorteo + + Returns: + dict: Diccionario con requisitos extraídos + """ + requirements = { + "follow": False, + "like": False, + "comment": False, + "tag_friends": False, + "hashtag": None, + "username": None + } + + text_lower = text.lower() + + if "follow" in text_lower or "seguir" in text_lower: + requirements["follow"] = True + if "like" in text_lower or "dar like" in text_lower or "corazón" in text_lower: + requirements["like"] = True + if "comment" in text_lower or "comenta" in text_lower: + requirements["comment"] = True + if "tag" in text_lower or "etiqueta" in text_lower or "menciona" in text_lower: + requirements["tag_friends"] = True + + # Buscar hashtag + import re + hashtag_match = re.search(r"#(\w+)", text) + if hashtag_match: + requirements["hashtag"] = hashtag_match.group(1) + + # Buscar usuario (@usuario) + user_match = re.search(r"@(\w+)", text) + if user_match: + requirements["username"] = user_match.group(1) + + return requirements + + def analyze(self, text): + """ + Análisis completo de un texto de sorteo. + + Args: + text: Texto a analizar + + Returns: + dict: Resultado del análisis + """ + return { + "is_giveaway": self.is_giveaway(text), + "requirements": self.extract_requirements(text), + "text": text[:100] + "..." if len(text) > 100 else text + } \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..99de4f3 --- /dev/null +++ b/config.py @@ -0,0 +1,23 @@ +# Instagram Giveaway Sorteo Configuration +import os +from dotenv import load_dotenv + +load_dotenv() + +# Configuración de Instagram +INSTAGRAM_USERNAME = os.getenv("INSTAGRAM_USERNAME", "tu_usuario") +INSTAGRAM_PASSWORD = os.getenv("INSTAGRAM_PASSWORD", "tu_contraseña") +TARGET_USERS = os.getenv("TARGET_USERS", "").split(",") if os.getenv("TARGET_USERS") else [] + +# Palabras clave para detectar sorteos +GIVEAWAY_KEYWORDS = [ + "sorteo", "giveaway", "ganar", "win", "ganando", "ganada", + "premio", "premios", "comparte", "share", "etiquetar", "tag", + "comenta", "comment", "follow", "seguir" +] + +# Configuración de IA local (reglas simples) +AI_MODE = "rule_based" # o "ollama" si tiene modelo local configurado + +# Comentario automático +AUTO_COMMENT = "¡Participo! #sorteo #ganar" \ No newline at end of file diff --git a/inscriber.py b/inscriber.py new file mode 100644 index 0000000..689528c --- /dev/null +++ b/inscriber.py @@ -0,0 +1,115 @@ +"""Módulo para inscribirse automáticamente en sorteos.""" +import time +import random +from bs4 import BeautifulSoup + + +class GiveawayInscriber: + """Clase para inscribir automáticamente en sorteos de Instagram.""" + + def __init__(self, scraper, analyzer, comment="¡Participo! #sorteo"): + self.scraper = scraper + self.analyzer = analyzer + self.comment = comment + self.successful_inscriptions = 0 + self.failed_attempts = 0 + + def inscription_comment(self, post_url, target_comment=None): + """ + Publicar un comentario en una publicación de sorteo. + + Args: + post_url: URL de la publicación + target_comment: Comentario personalizado (usa self.comment por defecto) + + Returns: + bool: Éxito o no + """ + comment = target_comment or self.comment + try: + # Nota: Instagram no permite comentarios fáciles sin autenticación adecuada + # Esta es la estructura; la implementación completa requiere + # manejo de sesiones CSRF, tokens, etc. + print(f"📝 Intentando comentar en: {post_url}") + print(f" Comentario: {comment}") + + # En un implementación completa, aquí se tendría: + # - CSRF token extraction + # - Session cookie management + # - POST request al endpoint de comentarios + # - Manejo de respuestas y rate limiting + + # Simulación para demostración + time.sleep(random.uniform(1, 3)) + self.successful_inscriptions += 1 + print("✅ Comentario publicado (simulación)") + return True + + except Exception as e: + self.failed_attempts += 1 + print(f"❌ Error al comentar: {e}") + return False + + def auto_enter(self, posts, min_keyword_match=2): + """ + Analizar publicaciones e inscribirse en las que son sorteos. + + Args: + posts: Lista de publicaciones (diccionarios con 'caption', 'url', etc.) + min_keyword_match: Mínimo de coincidencias de palabras clave + + Returns: + list: Publicaciones en las que se inscribió + """ + inscribed = [] + + for post in posts: + caption = post.get("caption", "") + url = post.get("url", "") + + if not caption or not url: + continue + + # Analizar con la IA local + analysis = self.analyzer.analyze(caption) + + if analysis["is_giveaway"]: + print(f"\n🎯 Sorteo detectado en: {url}") + print(f" Requerimientos: {analysis['requirements']}") + + # Inscribirse (comentar) + if self.inscription_comment(url): + inscribed.append({ + "url": url, + "caption": caption, + "requirements": analysis["requirements"] + }) + else: + print(f"📄 Publicación normal (no sorteo): {url[:50] if url else 'N/A'}...") + + return inscribed + + def run_on_hashtag(self, hashtag, post_count=20): + """ + Ejecutar en un hashtag específico. + + Args: + hashtag: Hashtag a buscar (ej: "sorteo") + post_count: Número de publicaciones a revisar + """ + print(f"🔍 Buscando sorteos con hashtag: #{hashtag}") + posts = self.scraper.search_hashtag(hashtag, post_count) + + if posts: + print(f"📊 Encontradas {len(posts)} publicaciones") + inscribed = self.auto_enter(posts) + print(f"\n✅ Inscripciones completadas: {len(inscribed)}") + return inscribed + else: + print("❌ No se encontraron publicaciones") + return [] + + +if __name__ == "__main__": + print("🐍 Módulo de inscripción directo") + print("Usa: from inscriber import GiveawayInscriber") \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..d59f768 --- /dev/null +++ b/main.py @@ -0,0 +1,88 @@ +"""Aplicación principal - Instagram Giveaway Sorteo Analyzer & Inscriber.""" +import sys +import os +from dotenv import load_dotenv + +# Cargar variables de entorno +load_dotenv() + +from scraping.instagram import InstagramScraper +from ai.analyzer import GiveawayAnalyzer +from inscriber import GiveawayInscriber + + +def main(): + print("=" * 60) + print("🎯 Instagram Sorteo Analyzer & Auto-Inscriber") + print("=" * 60) + + # Verificar configuración + username = os.getenv("INSTAGRAM_USERNAME") + password = os.getenv("INSTAGRAM_PASSWORD") + + if not username or not password: + print("⚠️ Configuración faltante.") + print(" Copia .env y establece INSTAGRAM_USERNAME y INSTAGRAM_PASSWORD") + print(" Ejemplo: echo 'INSTAGRAM_USERNAME=tu_usuario' > .env") + print(" echo 'INSTAGRAM_PASSWORD=tu_contraseña' >> .env") + return + + # Inicializar componentes + print("\n🔧 Inicializando componentes...") + scraper = InstagramScraper(username=username, password=password) + analyzer = GiveawayAnalyzer(mode="rule_based") + inscriber = GiveawayInscriber(scraper=scraper, analyzer=analyzer) + + print("✅ Componentes listos") + + # Modo de operación + print("\n📋 Modos disponibles:") + print(" 1. Buscar por hashtag (#sorteo, #giveaway)") + print(" 2. Revisar feed personalizado") + print(" 3. Analizar publicaciones existentes") + + choice = input("\nElige una opción (1-3): ").strip() + + if choice == "1": + hashtag = input("Hashtag a buscar (ej: sorteo): ").strip() or "sorteo" + count = int(input("Número de publicaciones a revisar (default 20): ").strip() or "20") + print(f"\n🚀 Iniciando búsqueda con hashtag: #{hashtag}") + inscriber.run_on_hashtag(hashtag, count) + + elif choice == "2": + print("\n📱 Obteniendo feed...") + feed = scraper.get_feed(count=15) + if feed: + print(f"📊 {len(feed)} publicaciones obtenidas del feed") + inscriber.auto_enter(feed) + else: + print("❌ No se pudieron obtener publicaciones del feed") + + elif choice == "3": + # Permitir analizar publicaciones manualmente proporcionadas + print("\n📝 Ingresa URLs o captions para analizar (enter vacío para terminar):") + posts = [] + while True: + entry = input("> ").strip() + if not entry: + break + posts.append({"caption": entry, "url": entry}) + + if posts: + print("\n🔍 Analizando publicaciones...") + inscribed = inscriber.auto_enter(posts) + print(f"\n✅ Total inscripciones: {len(inscribed)}") + + else: + print("❌ Opción no válida") + + # Cerrar scraper + scraper.close() + + print("\n" + "=" * 60) + print("¡Gracias por usar Instagram Sorteo Analyzer!") + print("=" * 60) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scraping/instagram.py b/scraping/instagram.py new file mode 100644 index 0000000..3592c0c --- /dev/null +++ b/scraping/instagram.py @@ -0,0 +1,98 @@ +"""Módulo para interactuar con Instagram - scraping básico de feeds.""" +import requests +from bs4 import BeautifulSoup +from urllib.parse import quote_plus +import time +import random + + +class InstagramScraper: + """Clase básica para obtener contenido de Instagram.""" + + def __init__(self, username=None, password=None): + self.username = username or "usuario_default" + self.password = password or "password_default" + self.session = requests.Session() + self.base_url = "https://www.instagram.com" + + def get_feed(self, count=10): + """Obtener publicaciones del feed (versión básica).""" + # Nota: Instagram bloquea el scraping simple. + # Esta es una estructura que se puede mejorar con headers adecuados + # o usando la API oficial de Instagram. + try: + # Endpoint público para explorar + url = f"{self.base_url}/explore/feed/" + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36" + } + response = self.session.get(url, headers=headers, timeout=15) + if response.status_code == 200: + return self._parse_feed(response.text, count) + else: + print(f"Error accediendo a Instagram: {response.status_code}") + return [] + except Exception as e: + print(f"Excepción al obtener feed: {e}") + return [] + + def _parse_feed(self, html, count): + """Parsear el feed de HTML.""" + soup = BeautifulSoup(html, "html.parser") + posts = [] + + # Buscar artículos o divs de publicaciones + # La estructura de Instagram cambia frecuentemente, por esto es básico + posts_divs = soup.find_all("div", class_="._aagw")[:count] + + for div in posts_divs: + try: + # Título/caption aproximado + caption = div.find("span", class_="._aacl _aacp _aacv _aacu") + if caption: + text = caption.get_text(strip=True) + else: + text = "" + + # Enlace + link = div.find("a") + href = link.get("href") if link else "" + full_url = f"{self.base_url}{href}" if href else "" + + # Imagen + img = div.find("img") + img_url = img.get("src") if img else "" + + posts.append({ + "url": full_url, + "caption": text, + "image": img_url, + "likes": 0, # Valor por defecto + "comments": 0 + }) + except Exception: + continue + + return posts + + def search_hashtag(self, hashtag, count=10): + """Buscar publicaciones por hashtag.""" + try: + url = f"{self.base_url}/explore/tags/{quote_plus(hashtag.lstrip('/'))}/" + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36" + } + response = self.session.get(url, headers=headers, timeout=15) + if response.status_code == 200: + return self._parse_feed(response.text, count) + except Exception as e: + print(f"Error buscando hashtag: {e}") + return [] + + def close(self): + """Cerrar sesión.""" + self.session.close() \ No newline at end of file