feat: Add recent published section and filter self-interactions
- Add "Publicaciones Recientes" section to dashboard showing published posts - Filter out self-generated interactions in X API (thread auto-replies) - Improve username resolution for X interactions using user expansions - Pass recent_published data to dashboard template Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -73,13 +73,19 @@ async def dashboard_home(request: Request, db: Session = Depends(get_db)):
|
|||||||
Interaction.responded == False
|
Interaction.responded == False
|
||||||
).order_by(Interaction.interaction_at.desc()).limit(5).all()
|
).order_by(Interaction.interaction_at.desc()).limit(5).all()
|
||||||
|
|
||||||
|
# Posts publicados recientemente
|
||||||
|
recent_published = db.query(Post).filter(
|
||||||
|
Post.status == "published"
|
||||||
|
).order_by(Post.published_at.desc()).limit(5).all()
|
||||||
|
|
||||||
return templates.TemplateResponse("index.html", {
|
return templates.TemplateResponse("index.html", {
|
||||||
"request": request,
|
"request": request,
|
||||||
"user": user.to_dict(),
|
"user": user.to_dict(),
|
||||||
"stats": stats,
|
"stats": stats,
|
||||||
"pending_posts": [p.to_dict() for p in pending_posts],
|
"pending_posts": [p.to_dict() for p in pending_posts],
|
||||||
"scheduled_posts": [p.to_dict() for p in scheduled_posts],
|
"scheduled_posts": [p.to_dict() for p in scheduled_posts],
|
||||||
"recent_interactions": [i.to_dict() for i in recent_interactions]
|
"recent_interactions": [i.to_dict() for i in recent_interactions],
|
||||||
|
"recent_published": [p.to_dict() for p in recent_published]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -214,30 +214,45 @@ class XPublisher(BasePublisher):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
async def get_comments(self, post_id: str) -> List[Dict]:
|
async def get_comments(self, post_id: str) -> List[Dict]:
|
||||||
"""Obtener respuestas a un tweet."""
|
"""Obtener respuestas a un tweet (excluyendo las propias)."""
|
||||||
if not self.client:
|
if not self.client:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Obtener ID del usuario autenticado para filtrar auto-respuestas
|
||||||
|
me = self.client.get_me()
|
||||||
|
my_user_id = str(me.data.id) if me.data else None
|
||||||
|
|
||||||
# Buscar respuestas al tweet
|
# Buscar respuestas al tweet
|
||||||
query = f"conversation_id:{post_id}"
|
query = f"conversation_id:{post_id}"
|
||||||
tweets = self.client.search_recent_tweets(
|
tweets = self.client.search_recent_tweets(
|
||||||
query=query,
|
query=query,
|
||||||
max_results=50,
|
max_results=50,
|
||||||
tweet_fields=['created_at', 'author_id', 'in_reply_to_user_id']
|
tweet_fields=['created_at', 'author_id', 'in_reply_to_user_id'],
|
||||||
|
user_fields=['username'],
|
||||||
|
expansions=['author_id']
|
||||||
)
|
)
|
||||||
|
|
||||||
if not tweets.data:
|
if not tweets.data:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
# Crear mapa de usuarios para obtener usernames
|
||||||
|
users_map = {}
|
||||||
|
if tweets.includes and 'users' in tweets.includes:
|
||||||
|
for user in tweets.includes['users']:
|
||||||
|
users_map[str(user.id)] = user.username
|
||||||
|
|
||||||
|
# Filtrar tweets propios (auto-respuestas del hilo)
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"id": str(tweet.id),
|
"id": str(tweet.id),
|
||||||
"text": tweet.text,
|
"text": tweet.text,
|
||||||
"author_id": str(tweet.author_id),
|
"author_id": str(tweet.author_id),
|
||||||
|
"username": users_map.get(str(tweet.author_id), "unknown"),
|
||||||
"created_at": tweet.created_at.isoformat() if tweet.created_at else None
|
"created_at": tweet.created_at.isoformat() if tweet.created_at else None
|
||||||
}
|
}
|
||||||
for tweet in tweets.data
|
for tweet in tweets.data
|
||||||
|
if str(tweet.author_id) != my_user_id # Excluir tweets propios
|
||||||
]
|
]
|
||||||
|
|
||||||
except tweepy.TweepyException:
|
except tweepy.TweepyException:
|
||||||
|
|||||||
@@ -166,6 +166,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Recent Published Posts -->
|
||||||
|
<div class="mt-8">
|
||||||
|
<div class="card rounded-2xl p-6">
|
||||||
|
<div class="flex items-center justify-between mb-6">
|
||||||
|
<h2 class="text-lg font-semibold flex items-center gap-2">
|
||||||
|
<span>✅</span>
|
||||||
|
<span>Publicaciones Recientes</span>
|
||||||
|
</h2>
|
||||||
|
<a href="/posts?status=published" class="text-primary text-sm hover:underline">Ver todas</a>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-4">
|
||||||
|
{% if recent_published %}
|
||||||
|
{% for post in recent_published %}
|
||||||
|
<div class="bg-dark-800/50 rounded-xl p-4 hover:bg-dark-700/50 transition-colors">
|
||||||
|
<div class="flex items-center gap-2 mb-2">
|
||||||
|
{% for platform in post.platforms %}
|
||||||
|
<span class="text-xs bg-green-500/20 text-green-400 px-2 py-1 rounded-full">{{ platform }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-gray-300 line-clamp-3">{{ post.content[:120] }}{% if post.content|length > 120 %}...{% endif %}</p>
|
||||||
|
<div class="flex items-center justify-between mt-3">
|
||||||
|
<span class="text-xs text-gray-500">{{ post.published_at[:10] if post.published_at else '-' }}</span>
|
||||||
|
{% if post.platform_post_ids %}
|
||||||
|
<a href="https://x.com/i/status/{{ post.platform_post_ids.x }}" target="_blank" class="text-xs text-primary hover:underline" {% if not post.platform_post_ids.x %}style="display:none"{% endif %}>
|
||||||
|
Ver en X
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<div class="col-span-full text-center py-8 text-gray-500">
|
||||||
|
<span class="text-4xl mb-2 block">📭</span>
|
||||||
|
<p>No hay publicaciones recientes</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Quick Actions -->
|
<!-- Quick Actions -->
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<h2 class="text-lg font-semibold mb-4">Acciones Rápidas</h2>
|
<h2 class="text-lg font-semibold mb-4">Acciones Rápidas</h2>
|
||||||
|
|||||||
@@ -95,14 +95,25 @@ def fetch_platform_interactions(platform: str):
|
|||||||
).first()
|
).first()
|
||||||
|
|
||||||
if not existing:
|
if not existing:
|
||||||
|
# Obtener username (X usa 'username', Meta usa 'from.id')
|
||||||
|
author_username = comment.get("username")
|
||||||
|
if not author_username:
|
||||||
|
from_data = comment.get("from", {})
|
||||||
|
author_username = from_data.get("id", "unknown") if isinstance(from_data, dict) else "unknown"
|
||||||
|
|
||||||
|
# Obtener nombre del autor
|
||||||
|
author_name = None
|
||||||
|
if isinstance(comment.get("from"), dict):
|
||||||
|
author_name = comment.get("from", {}).get("name")
|
||||||
|
|
||||||
interaction = Interaction(
|
interaction = Interaction(
|
||||||
platform=platform,
|
platform=platform,
|
||||||
interaction_type="comment",
|
interaction_type="comment",
|
||||||
post_id=post.id,
|
post_id=post.id,
|
||||||
external_id=external_id,
|
external_id=external_id,
|
||||||
external_post_id=platform_id,
|
external_post_id=platform_id,
|
||||||
author_username=comment.get("username", comment.get("from", {}).get("id", "unknown")),
|
author_username=author_username,
|
||||||
author_name=comment.get("from", {}).get("name") if isinstance(comment.get("from"), dict) else None,
|
author_name=author_name,
|
||||||
content=comment.get("text", comment.get("message")),
|
content=comment.get("text", comment.get("message")),
|
||||||
interaction_at=datetime.fromisoformat(
|
interaction_at=datetime.fromisoformat(
|
||||||
comment.get("created_at", comment.get("timestamp", comment.get("created_time", datetime.utcnow().isoformat()))).replace("Z", "+00:00")
|
comment.get("created_at", comment.get("timestamp", comment.get("created_time", datetime.utcnow().isoformat()))).replace("Z", "+00:00")
|
||||||
|
|||||||
Reference in New Issue
Block a user