feat: Add follower tracking to interactions
- Add get_followers() method to X publisher - Track new followers as "follow" interaction type - Update daily reports to show followers separately - Store follower name, username, bio, and profile image Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -277,3 +277,38 @@ class XPublisher(BasePublisher):
|
||||
return True
|
||||
except tweepy.TweepyException:
|
||||
return False
|
||||
|
||||
async def get_followers(self, max_results: int = 100) -> List[Dict]:
|
||||
"""Obtener lista de followers recientes."""
|
||||
if not self.client:
|
||||
return []
|
||||
|
||||
try:
|
||||
# Obtener ID del usuario autenticado
|
||||
me = self.client.get_me()
|
||||
user_id = me.data.id
|
||||
|
||||
# Obtener followers
|
||||
followers = self.client.get_users_followers(
|
||||
id=user_id,
|
||||
max_results=max_results,
|
||||
user_fields=['username', 'name', 'created_at', 'profile_image_url', 'description']
|
||||
)
|
||||
|
||||
if not followers.data:
|
||||
return []
|
||||
|
||||
return [
|
||||
{
|
||||
"id": str(user.id),
|
||||
"username": user.username,
|
||||
"name": user.name,
|
||||
"description": user.description,
|
||||
"profile_image_url": user.profile_image_url,
|
||||
"created_at": user.created_at.isoformat() if user.created_at else None
|
||||
}
|
||||
for user in followers.data
|
||||
]
|
||||
|
||||
except tweepy.TweepyException:
|
||||
return []
|
||||
|
||||
@@ -67,14 +67,30 @@ def morning_report():
|
||||
else:
|
||||
message += " _No hay posts programados para hoy_\n\n"
|
||||
|
||||
# Sección de interacciones
|
||||
# Separar follows de otras interacciones
|
||||
follows = [i for i in new_interactions if i.interaction_type == "follow"]
|
||||
other_interactions = [i for i in new_interactions if i.interaction_type != "follow"]
|
||||
|
||||
# Sección de nuevos followers
|
||||
if follows:
|
||||
message += "━━━━━━━━━━━━━━━━━━━━━\n"
|
||||
message += f"👥 *Nuevos followers ({len(follows)}):*\n\n"
|
||||
for f in follows[:5]:
|
||||
name = f.author_name or f.author_username
|
||||
name = name.replace("_", "\\_").replace("*", "\\*") if name else "Usuario"
|
||||
message += f" • @{f.author_username} ({name})\n"
|
||||
if len(follows) > 5:
|
||||
message += f" _...y {len(follows) - 5} más_\n"
|
||||
message += "\n"
|
||||
|
||||
# Sección de otras interacciones
|
||||
message += "━━━━━━━━━━━━━━━━━━━━━\n"
|
||||
message += "💬 *Nuevas interacciones (24h):*\n\n"
|
||||
|
||||
if new_interactions:
|
||||
if other_interactions:
|
||||
# Agrupar por plataforma
|
||||
by_platform = {}
|
||||
for interaction in new_interactions:
|
||||
for interaction in other_interactions:
|
||||
platform = interaction.platform
|
||||
if platform not in by_platform:
|
||||
by_platform[platform] = []
|
||||
@@ -85,7 +101,8 @@ def morning_report():
|
||||
for i in interactions[:3]: # Mostrar máximo 3 por plataforma
|
||||
content_preview = i.content[:50] + "..." if i.content and len(i.content) > 50 else (i.content or "N/A")
|
||||
content_preview = content_preview.replace("_", "\\_").replace("*", "\\*")
|
||||
message += f" • @{i.author_username}: {content_preview}\n"
|
||||
type_emoji = "💬" if i.interaction_type == "comment" else "📢"
|
||||
message += f" {type_emoji} @{i.author_username}: {content_preview}\n"
|
||||
if len(interactions) > 3:
|
||||
message += f" _...y {len(interactions) - 3} más_\n"
|
||||
message += "\n"
|
||||
@@ -154,13 +171,29 @@ def afternoon_report():
|
||||
message += f" ✅ Publicados hoy: {published_today}\n"
|
||||
message += f" 📅 Programados mañana: {scheduled_tomorrow}\n\n"
|
||||
|
||||
# Separar follows de otras interacciones
|
||||
follows = [i for i in new_interactions if i.interaction_type == "follow"]
|
||||
other_interactions = [i for i in new_interactions if i.interaction_type != "follow"]
|
||||
|
||||
# Nuevos followers del día
|
||||
if follows:
|
||||
message += "━━━━━━━━━━━━━━━━━━━━━\n"
|
||||
message += f"👥 *Nuevos followers hoy ({len(follows)}):*\n\n"
|
||||
for f in follows[:5]:
|
||||
name = f.author_name or f.author_username
|
||||
name = name.replace("_", "\\_").replace("*", "\\*") if name else "Usuario"
|
||||
message += f" • @{f.author_username} ({name})\n"
|
||||
if len(follows) > 5:
|
||||
message += f" _...y {len(follows) - 5} más_\n"
|
||||
message += "\n"
|
||||
|
||||
# Interacciones pendientes
|
||||
message += "━━━━━━━━━━━━━━━━━━━━━\n"
|
||||
message += "💬 *Interacciones pendientes:*\n\n"
|
||||
|
||||
if new_interactions:
|
||||
if other_interactions:
|
||||
by_platform = {}
|
||||
for interaction in new_interactions:
|
||||
for interaction in other_interactions:
|
||||
platform = interaction.platform
|
||||
if platform not in by_platform:
|
||||
by_platform[platform] = []
|
||||
@@ -171,13 +204,14 @@ def afternoon_report():
|
||||
for i in interactions[:5]: # Mostrar más en el reporte de la tarde
|
||||
content_preview = i.content[:50] + "..." if i.content and len(i.content) > 50 else (i.content or "N/A")
|
||||
content_preview = content_preview.replace("_", "\\_").replace("*", "\\*")
|
||||
message += f" • @{i.author_username}: {content_preview}\n"
|
||||
type_emoji = "💬" if i.interaction_type == "comment" else "📢"
|
||||
message += f" {type_emoji} @{i.author_username}: {content_preview}\n"
|
||||
if len(interactions) > 5:
|
||||
message += f" _...y {len(interactions) - 5} más_\n"
|
||||
message += "\n"
|
||||
|
||||
message += "━━━━━━━━━━━━━━━━━━━━━\n"
|
||||
message += f"⚠️ *{len(new_interactions)} interacciones sin responder*"
|
||||
message += f"⚠️ *{len(other_interactions)} interacciones sin responder*"
|
||||
else:
|
||||
message += " ✅ _Todas las interacciones han sido atendidas_\n\n"
|
||||
message += "━━━━━━━━━━━━━━━━━━━━━\n"
|
||||
|
||||
@@ -138,9 +138,44 @@ def fetch_platform_interactions(platform: str):
|
||||
processed_ids.add(external_id)
|
||||
new_comments += 1
|
||||
|
||||
# Obtener nuevos followers (solo para X por ahora)
|
||||
new_follows = 0
|
||||
if platform == "x" and hasattr(publisher, 'get_followers'):
|
||||
followers = run_async(publisher.get_followers(max_results=100))
|
||||
|
||||
for follower in followers:
|
||||
# Usar "follow_" prefix para distinguir de tweet IDs
|
||||
follower_id = follower.get("id")
|
||||
external_id = f"follow_{follower_id}"
|
||||
|
||||
if external_id in processed_ids:
|
||||
continue
|
||||
|
||||
existing = db.query(Interaction).filter(
|
||||
Interaction.external_id == external_id
|
||||
).first()
|
||||
|
||||
if not existing:
|
||||
interaction = Interaction(
|
||||
platform=platform,
|
||||
interaction_type="follow",
|
||||
external_id=external_id,
|
||||
author_username=follower.get("username", "unknown"),
|
||||
author_name=follower.get("name"),
|
||||
author_avatar_url=follower.get("profile_image_url"),
|
||||
content=follower.get("description"), # Bio del usuario
|
||||
interaction_at=datetime.utcnow() # No sabemos cuándo siguió exactamente
|
||||
)
|
||||
db.add(interaction)
|
||||
processed_ids.add(external_id)
|
||||
new_follows += 1
|
||||
|
||||
db.commit()
|
||||
|
||||
return f"{platform}: {new_mentions} menciones, {new_comments} comentarios nuevos"
|
||||
result = f"{platform}: {new_mentions} menciones, {new_comments} comentarios"
|
||||
if new_follows > 0:
|
||||
result += f", {new_follows} follows nuevos"
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
|
||||
Reference in New Issue
Block a user