diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000..32a84f4972 Binary files /dev/null and b/.DS_Store differ diff --git a/.cursor/.DS_Store b/.cursor/.DS_Store index 231b48d052..bd68bf5117 100644 Binary files a/.cursor/.DS_Store and b/.cursor/.DS_Store differ diff --git a/.cursor/rules/backend-technical-stack.mdc b/.cursor/rules/backend-technical-stack.mdc index be546ad575..24eb949885 100644 --- a/.cursor/rules/backend-technical-stack.mdc +++ b/.cursor/rules/backend-technical-stack.mdc @@ -3,30 +3,32 @@ description: Technical Stack Specification for the /backend. globs: backend/* alwaysApply: false --- +# Technical Stack Specification for the /backend + ## 1. Technology Stack Overview -| Component | Technology | Version | Purpose | -|-----------|------------|---------|---------| -| Framework | FastAPI | 0.114.2+ | Web API framework | -| ORM | SQLModel | 0.0.21+ | Database ORM | -| Primary Database | PostgreSQL | 13+ | Relational database | -| Document Database | MongoDB | 6.0+ | Social media content storage | -| In-memory Database | Redis | 7.0+ | Caching and real-time operations | -| Vector Database | Pinecone | Latest | Semantic content analysis | -| Authentication | JWT | 2.8.0+ | User authentication | -| Password Hashing | Passlib + Bcrypt | 1.7.4+ | Secure password storage | -| Dependency Management | uv | 0.5.11+ | Package management | -| Migrations | Alembic | 1.12.1+ | Database schema migrations | -| API Documentation | OpenAPI/Swagger | Built-in | API documentation | -| Error Tracking | Sentry | 1.40.6+ | Error reporting | -| Email Delivery | emails | 0.6+ | Email notifications | -| Testing | pytest | 7.4.3+ | Unit and integration testing | -| Linting | ruff | 0.2.2+ | Code quality | -| Type Checking | mypy | 1.8.0+ | Static type checking | -| Task Queue | Celery | 5.3.0+ | Asynchronous task processing | -| Message Broker | RabbitMQ | 3.12+ | Task distribution | -| Stream Processing | Apache Kafka | 3.4+ | Real-time data streaming | -| NLP Processing | spaCy + Transformers | 3.6+ / 4.28+ | Content analysis | +| Component | Technology | Version | Purpose | MVP Status | +|-----|---|---|---|---| +| Framework | FastAPI | 0.114.2+ | Web API framework | ✅ Included | +| ORM | SQLModel | 0.0.21+ | Database ORM | ✅ Included | +| Primary Database | PostgreSQL | 13+ | Relational database | ✅ Included | +| Document Database | MongoDB | 6.0+ | Social media content storage | ✅ Included | +| In-memory Database | Redis | 7.0+ | Caching and real-time operations | ❌ **NOT in MVP** | +| Vector Database | Pinecone | Latest | Semantic content analysis | ✅ Included | +| Authentication | JWT | 2.8.0+ | User authentication | ✅ Included | +| Password Hashing | Passlib + Bcrypt | 1.7.4+ | Secure password storage | ✅ Included | +| Dependency Management | uv | 0.5.11+ | Package management | ✅ Included | +| Migrations | Alembic | 1.12.1+ | Database schema migrations | ✅ Included | +| API Documentation | OpenAPI/Swagger | Built-in | API documentation | ✅ Included | +| Error Tracking | Sentry | 1.40.6+ | Error reporting | ✅ Included | +| Email Delivery | emails | 0.6+ | Email notifications | ✅ Included | +| Testing | pytest | 7.4.3+ | Unit and integration testing | ✅ Included | +| Linting | ruff | 0.2.2+ | Code quality | ✅ Included | +| Type Checking | mypy | 1.8.0+ | Static type checking | ✅ Included | +| Task Queue | Celery | 5.3.0+ | Asynchronous task processing | ❌ **NOT in MVP** | +| Message Broker | RabbitMQ | 3.12+ | Task distribution | ❌ **NOT in MVP** | +| Stream Processing | Apache Kafka | 3.4+ | Real-time data streaming | ❌ **NOT in MVP** | +| NLP Processing | spaCy + Transformers | 3.6+ / 4.28+ | Content analysis | ✅ Included | ## 2. Architecture @@ -51,7 +53,7 @@ Client Request → API Layer → Service Layer → Repository Layer → Database ### 2.3 Directory Structure ``` -/app +backend/app ├── api/ # API endpoints and routing │ ├── api_v1/ # API version 1 │ │ ├── endpoints/ # Resource endpoints @@ -68,15 +70,13 @@ Client Request → API Layer → Service Layer → Repository Layer → Database ├── schemas/ # Pydantic models for API ├── services/ # Business logic │ └── repositories/ # Data access layer -├── tasks/ # Celery tasks for background processing -│ ├── scraping/ # Social media scraping tasks -│ ├── analysis/ # Content analysis tasks -│ └── notifications/ # Alert and notification tasks +├── tasks/ # Task processing system (MVP version) +│ ├── task_manager.py # In-memory task management +│ ├── task_types.py # Task type definitions +│ └── README.md # Task system documentation ├── processing/ # Data processing components │ ├── models/ # ML model wrappers -│ ├── streams/ # Kafka stream processors │ └── embeddings/ # Vector embedding generators -├── worker.py # Celery worker configuration └── main.py # Application entry point ``` @@ -86,14 +86,14 @@ Client Request → API Layer → Service Layer → Repository Layer → Database The application employs a hybrid database architecture to address the diverse data requirements of political social media analysis: -| Component | Technology | Version | Purpose | -|-----------|------------|---------|---------| -| Relational Database | PostgreSQL | 13+ | Entity data and relationships | -| Document Database | MongoDB | 6.0+ | Social media content and engagement | -| In-memory Database | Redis | 7.0+ | Caching and real-time operations | -| Vector Database | Pinecone | Latest | Semantic similarity analysis | +| Component | Technology | Version | Purpose | MVP Status | +|-----|---|---|---|---| +| Relational Database | PostgreSQL | 13+ | Entity data and relationships | ✅ Included | +| Document Database | MongoDB | 6.0+ | Social media content and engagement | ✅ Included | +| In-memory Database | Redis | 7.0+ | Caching and real-time operations | ❌ **NOT in MVP** | +| Vector Database | Pinecone | Latest | Semantic similarity analysis | ✅ Included | -Refer to `database-architecture.mdc` for detailed implementation specifications. +Refer to `database-architecture.md` for detailed implementation specifications. ### 3.2 Primary Domain Models @@ -112,14 +112,14 @@ Refer to `database-architecture.mdc` for detailed implementation specifications. ### 3.4 Additional Dependencies -| Dependency | Version | Purpose | -|------------|---------|---------| -| motor | 3.2.0+ | Async MongoDB driver | -| redis | 4.6.0+ | Redis client | -| pinecone-client | 2.2.1+ | Pinecone Vector DB client | -| pymongo | 4.5.0+ | MongoDB client | +| Dependency | Version | Purpose | MVP Status | +|---|---|---|---| +| motor | 3.2.0+ | Async MongoDB driver | ✅ Included | +| redis | 4.6.0+ | Redis client | ❌ **NOT in MVP** | +| pinecone-client | 2.2.1+ | Pinecone Vector DB client | ✅ Included | +| pymongo | 4.5.0+ | MongoDB client | ✅ Included | -Refer to `data-processing-architecture.mdc` for details on processing pipelines and analysis components. +Refer to `data-processing-architecture.md` for details on processing pipelines and analysis components. ## 4. API Design @@ -227,4 +227,33 @@ Error responses: - Modular service architecture - Clear separation of concerns -- Version-prefixed API endpoints \ No newline at end of file +- Version-prefixed API endpoints + +## 10. Task Processing System (MVP) + +### 10.1 MVP Implementation + +The MVP version uses a simplified approach for task processing: + +- **TaskManager**: In-memory task management system +- **FastAPI BackgroundTasks**: Used for asynchronous execution +- **Task Status Tracking**: Maintains task state (pending, running, completed, failed) +- **Simple API**: Endpoints for task creation, status checking, and listing + +### 10.2 MVP Limitations + +- **No Persistent Storage**: Tasks stored in memory only, lost on server restart +- **No Distributed Processing**: All tasks run on the same server instance +- **No Scheduled Tasks**: No mechanism for recurring tasks +- **No Task Queue**: Tasks execute in the order they're received +- **Limited Scaling**: Cannot handle high volume of concurrent tasks + +### 10.3 Post-MVP Task Processing + +In future versions beyond MVP, the system will be upgraded to: + +- **Celery**: For robust task queue system +- **Redis**: For task result storage and caching +- **RabbitMQ**: For reliable message broker +- **Scheduled Tasks**: For recurring operations +- **Distributed Processing**: For scalable task execution \ No newline at end of file diff --git a/.cursor/rules/data-processing-architecture.mdc b/.cursor/rules/data-processing-architecture.mdc index 1100dec26c..b6a47b1324 100644 --- a/.cursor/rules/data-processing-architecture.mdc +++ b/.cursor/rules/data-processing-architecture.mdc @@ -1,333 +1,188 @@ --- description: Data Processing Architecture Specification for the Political Social Media Analysis Platform. -globs: backend/tasks/*, backend/processing/* +globs: alwaysApply: false --- -# Data Processing Architecture +# Data Processing Architecture (MVP Version) ## 1. Technology Stack Overview -| Component | Technology | Version | Purpose | -|-----------|------------|---------|---------| -| Task Queue | Celery | 5.3.0+ | Asynchronous task processing | -| Message Broker | RabbitMQ | 3.12+ | Task distribution and messaging | -| Stream Processing | Apache Kafka | 3.4+ | Real-time event streaming | -| Text Processing | spaCy | 3.6+ | NLP and entity recognition | -| Sentiment Analysis | Transformers | 4.28+ | Content sentiment detection | -| Vector Embeddings | sentence-transformers | 2.2.2+ | Text embedding generation | -| Machine Learning | scikit-learn | 1.2+ | Classification and regression | -| Full-Text Search | MongoDB Atlas Search | N/A | Content search capabilities | +| Component | Technology | Version | Purpose | MVP Status | +|-----|---|---|---|---| +| Text Processing | spaCy | 3.6+ | NLP and entity recognition | ✅ Included | +| Sentiment Analysis | Transformers | 4.28+ | Content sentiment detection | ✅ Included | +| Vector Embeddings | sentence-transformers | 2.2.2+ | Text embedding generation | ✅ Included | +| Machine Learning | scikit-learn | 1.2+ | Classification and regression | ✅ Included | +| Full-Text Search | MongoDB Atlas Search | N/A | Content search capabilities | ✅ Included | +| Task Queue | Celery | 5.3.0+ | Asynchronous task processing | ❌ **NOT in MVP** | +| Message Broker | RabbitMQ | 3.12+ | Task distribution and messaging | ❌ **NOT in MVP** | +| Stream Processing | Apache Kafka | 3.4+ | Real-time event streaming | ❌ **NOT in MVP** | ## 2. Processing Pipeline Components ### 2.1 Data Collection Components -- **Platform-specific scrapers**: Modular adapters for each social media platform -- **Rate limiters**: Respects platform API constraints -- **Scheduled collection**: Configurable intervals for data collection -- **Content processors**: Standardizes data from different platforms +#### Platform Collectors +- **Standardized Base Collector**: `BaseCollector` class providing common functionality +- **Platform-Specific Collectors**: + - `InstagramCollector`: Instagram data collection and transformation + - `FacebookCollector`: Facebook data collection and transformation + - `TwitterCollector`: Twitter/X data collection and transformation + - `TikTokCollector`: TikTok data collection and transformation -### 2.2 Analysis Components +#### Collection Features +- **Rate Limiting**: Built-in respect for platform API constraints +- **Error Handling**: Robust error handling and retry mechanisms +- **Data Transformation**: Standardized data structures across platforms +- **Validation**: Schema validation using Pydantic models -- **Sentiment analyzer**: Determines content sentiment -- **Topic modeler**: Identifies content themes and categories -- **Entity recognizer**: Detects mentions of political entities -- **Vector embedder**: Generates semantic representations -- **Relationship mapper**: Builds entity relationship graphs - -### 2.3 Real-time Components - -- **Stream processors**: Kafka consumers for real-time analysis -- **Alert generators**: Triggers based on configurable thresholds -- **Metric calculators**: Real-time engagement statistics -- **Notification services**: Delivery of critical alerts - -## 3. Task Distribution - -### 3.1 Task Queue Design - -Celery task queues with priority-based routing: - -| Queue Name | Priority | Purpose | -|------------|----------|---------| -| scraping | High | Content collection from social platforms | -| analysis | Medium | Content processing and analysis | -| embeddings | Low | Vector embedding generation | -| alerts | Critical | Real-time notification processing | -| reports | Low | Scheduled report generation | - -### 3.2 Task Implementation Pattern - -```python -@app.task(queue="analysis", rate_limit="100/m") -def analyze_sentiment(post_id: str, text: str): - """ - Analyze the sentiment of a social media post. - - Args: - post_id: The MongoDB ID of the post - text: The text content to analyze - - Returns: - Dict containing sentiment scores and emotional classification - """ - # Sentiment analysis implementation - sentiment_score = sentiment_model.predict(text) - - # Update the post in MongoDB with sentiment results - mongodb.posts.update_one( - {"_id": ObjectId(post_id)}, - {"$set": {"analysis.sentiment_score": sentiment_score}} - ) - - # Return result for potential chaining - return { - "post_id": post_id, - "sentiment_score": sentiment_score - } -``` - -## 4. Stream Processing Design - -### 4.1 Kafka Topic Design - -| Topic | Purpose | Retention | Partitioning | -|-------|---------|-----------|--------------| -| social-media-raw | Raw content from platforms | 7 days | By platform and entity | -| entity-mentions | Mentions of tracked entities | 30 days | By mentioned entity | -| sentiment-changes | Significant sentiment shifts | 30 days | By entity | -| engagement-metrics | Real-time engagement updates | 2 days | By entity | - -### 4.2 Stream Processing Pattern +### 2.2 Data Models +#### MongoDB Schema Models ```python -def process_sentiment_stream(): - """ - Process the sentiment stream to detect significant changes. - """ - consumer = KafkaConsumer( - 'social-media-raw', - bootstrap_servers='kafka:9092', - group_id='sentiment-analyzer', - auto_offset_reset='latest' - ) - - for message in consumer: - # Decode message - post = json.loads(message.value) - - # Calculate sentiment - sentiment = analyze_content(post['content']['text']) - - # Check for significant changes - if is_significant_change(post, sentiment): - # Publish to sentiment-changes topic - publish_sentiment_change(post, sentiment) - - # Generate alert if needed - if requires_alert(post, sentiment): - generate_alert(post, sentiment) +class SocialMediaPost(BaseModel): + platform_id: str + platform: str + account_id: UUID + content_type: str + short_code: Optional[str] + url: Optional[HttpUrl] + content: PostContent + metadata: PostMetadata + engagement: PostEngagement + analysis: Optional[PostAnalysis] + child_posts: Optional[List[ChildPost]] + video_data: Optional[VideoData] + vector_id: Optional[str] + +class SocialMediaComment(BaseModel): + platform_id: str + platform: str + post_id: str + post_url: Optional[HttpUrl] + user_id: str + user_name: str + content: CommentContent + metadata: CommentMetadata + engagement: CommentEngagement + replies: List[CommentReply] + analysis: Optional[CommentAnalysis] + user_details: Optional[CommentUserDetails] + vector_id: Optional[str] ``` -## 5. Machine Learning Implementation - -### 5.1 Model Management +### 2.3 Data Processing Flow -- **Model Registry**: Central repository for trained models -- **Versioning**: Tracking model versions and performance -- **A/B Testing**: Framework for evaluating model improvements -- **Automated Retraining**: Scheduled model updates +1. **Collection**: + - Platform collector fetches data from social media API + - Data is transformed to standardized format + - Validation against Pydantic models -### 5.2 Core Models +2. **Storage**: + - Posts and comments stored in MongoDB + - Profile data stored in PostgreSQL + - References maintained between databases -| Model | Purpose | Architecture | Training Data | -|-------|---------|--------------|--------------| -| Sentiment Analyzer | Content sentiment scoring | Fine-tuned transformer | Labeled political content | -| Topic Classifier | Content categorization | Multi-label classification | Domain-specific corpus | -| Entity Relationship | Relationship scoring | Graph neural network | Historical interaction data | -| Audience Segmenter | User clustering | Unsupervised model | Engagement patterns | -| Performance Predictor | Engagement prediction | Gradient boosting | Historical post performance | +3. **Analysis** (Background Processing): + - Content analysis (sentiment, topics) + - Engagement metrics calculation + - Vector embedding generation -### 5.3 Vector Embedding Process +## 3. Task Processing (MVP Implementation) +### 3.1 Task Manager ```python -@app.task(queue="embeddings") -def generate_embedding(content_id: str, content_type: str, text: str): - """ - Generate vector embedding for text content. - - Args: - content_id: MongoDB ID of the content - content_type: Type of content (post, comment) - text: Text to embed - - Returns: - ID of the created vector entry - """ - # Generate embedding - embedding = embedding_model.encode(text) - - # Get metadata from MongoDB - if content_type == "post": - content = mongodb.posts.find_one({"_id": ObjectId(content_id)}) - else: - content = mongodb.comments.find_one({"_id": ObjectId(content_id)}) - - # Create metadata for vector DB - metadata = { - "content_type": content_type, - "source_id": str(content["_id"]), - "entity_id": content.get("account_id"), - "platform": content["platform"], - "created_at": content["metadata"]["created_at"], - "topics": content.get("analysis", {}).get("topics", []), - "sentiment_score": content.get("analysis", {}).get("sentiment_score") - } +class TaskManager: + """Simple in-memory task management system for MVP.""" - # Store in vector database - vector_id = vector_client.upsert( - vectors=[embedding.tolist()], - metadata=metadata, - namespace="social_content" - ) + def __init__(self): + self.tasks = {} + self.status = {} - # Update reference in MongoDB - collection = mongodb.posts if content_type == "post" else mongodb.comments - collection.update_one( - {"_id": ObjectId(content_id)}, - {"$set": {"vector_id": vector_id}} - ) + async def create_task(self, task_type: str, params: dict): + """Create and track a new task.""" + task_id = str(uuid4()) + self.tasks[task_id] = { + "type": task_type, + "params": params, + "status": "pending", + "created_at": datetime.utcnow() + } + return task_id - return vector_id + async def get_task_status(self, task_id: str): + """Get the current status of a task.""" + return self.tasks.get(task_id, {}).get("status", "not_found") ``` -## 6. Search Implementation - -### 6.1 MongoDB Atlas Search Configuration - -```javascript -// Search index configuration -{ - "mappings": { - "dynamic": false, - "fields": { - "content.text": { - "type": "string", - "analyzer": "lucene.standard", - "searchAnalyzer": "lucene.standard" - }, - "metadata.location.country": { - "type": "string" - }, - "metadata.language": { - "type": "string" - }, - "analysis.topics": { - "type": "string" - }, - "analysis.entities_mentioned": { - "type": "string" - } - } - } -} -``` +### 3.2 Background Tasks +- Uses FastAPI's `BackgroundTasks` for asynchronous processing +- Simple task queue with in-memory status tracking +- Basic retry mechanism for failed tasks -### 6.2 Search Implementation +## 4. Data Transformation +### 4.1 Platform-Specific Transformers +Each collector implements transformation methods: ```python -async def search_content(query: str, filters: dict = None): - """ - Search social media content using MongoDB Atlas Search. +def transform_post(self, raw_post: Dict[str, Any], account_id: UUID) -> Dict[str, Any]: + """Transform platform-specific post data to standard format.""" - Args: - query: Text query to search for - filters: Optional filters to apply (topics, entities, etc.) - - Returns: - List of matching documents - """ - search_pipeline = [ - { - "$search": { - "index": "social_content", - "text": { - "query": query, - "path": "content.text" - } - } - } - ] - - # Add filters if provided - if filters: - search_pipeline.append({"$match": filters}) - - # Add projection to limit fields returned - search_pipeline.append({ - "$project": { - "_id": 1, - "content": 1, - "metadata": 1, - "analysis": 1, - "score": {"$meta": "searchScore"} - } - }) +def transform_comment(self, raw_comment: Dict[str, Any], post_id: str) -> Dict[str, Any]: + """Transform platform-specific comment data to standard format.""" - # Execute search - results = await mongodb.posts.aggregate(search_pipeline).to_list(length=50) - return results +def transform_profile(self, raw_profile: Dict[str, Any]) -> Dict[str, Any]: + """Transform platform-specific profile data to standard format.""" ``` -## 7. Performance Considerations - -### 7.1 Resource Allocation - -| Component | CPU Allocation | Memory Allocation | Scaling Trigger | -|-----------|---------------|-------------------|-----------------| -| Scraping Workers | Medium | Low | Queue depth > 1000 tasks | -| Analysis Workers | High | High | Queue depth > 500 tasks | -| Vector Workers | High | Medium | Queue depth > 200 tasks | -| Stream Processors | Medium | High | Consumer lag > 1000 messages | - -### 7.2 Processing Patterns - -- **Real-time processing**: Critical alerts, high-priority entity updates -- **Near real-time processing**: Sentiment analysis, engagement metrics -- **Batch processing**: Vector embedding, relationship analysis, historical trends +### 4.2 Standardization Rules +- Consistent datetime formats +- Normalized engagement metrics +- Platform-agnostic content structure +- Uniform handling of media content -### 7.3 Rate Limiting +## 5. Error Handling and Validation -- Platform-specific API rate limits -- Resource-based rate limits for compute-intensive tasks -- Prioritization of critical entity monitoring +### 5.1 Error Types +- API rate limiting errors +- Network connectivity issues +- Data validation errors +- Transformation errors -## 8. Monitoring and Observability +### 5.2 Validation Strategy +- Schema validation using Pydantic models +- Data type checking and conversion +- Required field verification +- Cross-reference validation -### 8.1 Key Metrics +## 6. Future Enhancements (Post-MVP) -- Task processing rates and success/failure ratios -- Model inference latency and throughput -- Stream processing lag and throughput -- Database operation latency -- Queue depths and processing backlogs +### 6.1 Task Queue System +- Implementation of Celery for robust task processing +- RabbitMQ integration for message queuing +- Distributed task execution +- Task prioritization and scheduling -### 8.2 Implementation Strategy +### 6.2 Stream Processing +- Kafka integration for real-time data streaming +- Event-driven architecture +- Real-time analytics pipeline +- Notification system -- Structured logging with correlation IDs -- Error tracking with Sentry integration -- Performance monitoring with Prometheus -- Worker monitoring with Flower for Celery -- Custom health check endpoints for services +### 6.3 Caching Layer +- Redis integration for caching +- Performance optimization +- Real-time metrics +- Session management -## 9. Additional Dependencies +## 7. Dependencies (MVP) | Dependency | Version | Purpose | -|------------|---------|---------| -| celery | 5.3.0+ | Task queue library | -| kafka-python | 2.0.2+ | Kafka client | +|---|---|---| | spacy | 3.6.0+ | Natural language processing | | transformers | 4.28.0+ | Machine learning models | | scikit-learn | 1.2.0+ | Classical machine learning tools | -| torch | 2.0.0+ | Deep learning framework | -| sentence-transformers | 2.2.2+ | Text embedding generation | \ No newline at end of file +| sentence-transformers | 2.2.2+ | Text embedding generation | +| pydantic | 2.0+ | Data validation and settings management | +| motor | 3.2.0+ | Async MongoDB driver | +| sqlmodel | 0.0.8+ | SQL database ORM | \ No newline at end of file diff --git a/.cursor/rules/database-architecture.mdc b/.cursor/rules/database-architecture.mdc index 2d719956ee..9e6e0c4aa3 100644 --- a/.cursor/rules/database-architecture.mdc +++ b/.cursor/rules/database-architecture.mdc @@ -1,205 +1,441 @@ --- description: Hybrid Database Architecture Specification for the Political Social Media Analysis Platform. -globs: backend/db/* +globs: alwaysApply: false --- # Hybrid Database Architecture ## 1. Database Technologies -| Component | Technology | Version | Purpose | -|-----------|------------|---------|---------| -| Relational Database | PostgreSQL | 13+ | Entity data and relationships | -| Document Database | MongoDB | 6.0+ | Social media content and engagement | -| In-memory Database | Redis | 7.0+ | Caching and real-time operations | -| Vector Database | Pinecone | Latest | Semantic similarity analysis | +| Component | Technology | Version | Purpose | MVP Status | +|-----|---|---|---|---| +| Relational Database | PostgreSQL | 13+ | Entity data and relationships | ✅ Included | +| Document Database | MongoDB | 6.0+ | Social media content and engagement | ✅ Included | +| In-memory Database | Redis | 7.0+ | Caching and real-time operations | ❌ **NOT in MVP** | +| Vector Database | Pinecone | Latest | Semantic content analysis | ✅ Included | -## 2. Relational Database Design +## 2. Relational Database Design (PostgreSQL) ### 2.1 Primary Technology -PostgreSQL with SQLModel ORM integration +PostgreSQL with SQLModel ORM integration. ### 2.2 Key Design Decisions -- **UUID Primary Keys**: All entities use UUID primary keys for security and distributed system compatibility -- **Relationship Management**: Proper foreign key constraints with cascade delete -- **String Field Constraints**: Appropriate length limits on all VARCHAR fields -- **Migration Strategy**: Alembic for version-controlled schema changes +- **UUID Primary Keys**: All entities use UUID primary keys for security and distributed system compatibility. +- **Relationship Management**: Proper foreign key constraints with cascade delete (`ondelete="CASCADE"` in models). +- **String Field Constraints**: Appropriate length limits on VARCHAR fields (e.g., `max_length=255`). +- **Migration Strategy**: Alembic for version-controlled schema changes. -### 2.3 Domain Models +### 2.3 Domain Models (SQLModel) +The SQLModel definitions are located in individual files within the `backend/app/db/models/` directory. + +**`backend/app/db/models/political_entity.py`:** ```python +import uuid +from datetime import datetime +from enum import Enum +from typing import TYPE_CHECKING, List, Optional + +from sqlmodel import Field, Relationship, SQLModel + +if TYPE_CHECKING: + from app.db.models.social_media_account import SocialMediaAccount + from app.db.models.entity_relationship import EntityRelationship + + +class EntityType(str, Enum): + POLITICIAN = "politician" + PARTY = "party" + ORGANIZATION = "organization" + + class PoliticalEntity(SQLModel, table=True): - id: UUID = Field(default_factory=uuid4, primary_key=True) - name: str = Field(index=True) - entity_type: str # politician, party, organization - platforms: List["SocialMediaAccount"] = Relationship(back_populates="entity") - relationships: List["EntityRelationship"] = Relationship(back_populates="source_entity") + """ + PoliticalEntity model for database storage. + + This model represents a political entity (politician, party, organization) + in the system and is stored in PostgreSQL. + """ + id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) + name: str = Field(index=True, max_length=255) + entity_type: EntityType + description: Optional[str] = Field(default=None) + country: Optional[str] = Field(default=None, max_length=100) + region: Optional[str] = Field(default=None, max_length=100) + political_alignment: Optional[str] = Field(default=None, max_length=100) + created_at: datetime = Field(default_factory=datetime.utcnow) + updated_at: datetime = Field(default_factory=datetime.utcnow) + + # Relationships + social_media_accounts: List["SocialMediaAccount"] = Relationship( + back_populates="political_entity", + sa_relationship_kwargs={"cascade": "all, delete-orphan"}, + ) + + # Relationships for EntityRelationship + source_relationships: List["EntityRelationship"] = Relationship( + back_populates="source_entity", + sa_relationship_kwargs={ + "primaryjoin": "PoliticalEntity.id==EntityRelationship.source_entity_id", + "cascade": "all, delete-orphan", + }, + ) + + target_relationships: List["EntityRelationship"] = Relationship( + back_populates="target_entity", + sa_relationship_kwargs={ + "primaryjoin": "PoliticalEntity.id==EntityRelationship.target_entity_id", + "cascade": "all, delete", + }, + ) +``` + +**`backend/app/db/models/social_media_account.py`:** +```python +import uuid +from enum import Enum +from typing import Optional + +from sqlmodel import Field, Relationship, SQLModel + +from app.db.models.political_entity import PoliticalEntity + + +class Platform(str, Enum): + TWITTER = "twitter" + FACEBOOK = "facebook" + INSTAGRAM = "instagram" + LINKEDIN = "linkedin" + YOUTUBE = "youtube" + TIKTOK = "tiktok" + OTHER = "other" + class SocialMediaAccount(SQLModel, table=True): - id: UUID = Field(default_factory=uuid4, primary_key=True) - platform: str # twitter, facebook, instagram, etc. - platform_id: str = Field(index=True) # platform-specific identifier - handle: str - entity_id: UUID = Field(foreign_key="politicalentity.id") - entity: PoliticalEntity = Relationship(back_populates="platforms") + """ + SocialMediaAccount model for database storage. + + This model represents a social media account linked to a political entity + and is stored in PostgreSQL. + """ + id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) + platform: Platform + platform_id: str = Field(index=True, max_length=255) + handle: str = Field(max_length=255) + name: Optional[str] = Field(default=None, max_length=255) + url: Optional[str] = Field(default=None, max_length=2083) + verified: bool = Field(default=False) + follower_count: Optional[int] = Field(default=None) + following_count: Optional[int] = Field(default=None) + + # Foreign key + political_entity_id: uuid.UUID = Field( + foreign_key="politicalentity.id", + nullable=False, + ondelete="CASCADE" + ) + + # Relationship + political_entity: PoliticalEntity = Relationship(back_populates="social_media_accounts") +``` + +**`backend/app/db/models/entity_relationship.py`:** +```python +import uuid +from datetime import datetime +from enum import Enum +from typing import Optional + +from sqlmodel import Field, Relationship, SQLModel + +from app.db.models.political_entity import PoliticalEntity + + +class RelationshipType(str, Enum): + ALLY = "ally" + OPPONENT = "opponent" + NEUTRAL = "neutral" + class EntityRelationship(SQLModel, table=True): - id: UUID = Field(default_factory=uuid4, primary_key=True) - source_entity_id: UUID = Field(foreign_key="politicalentity.id") - target_entity_id: UUID = Field(foreign_key="politicalentity.id") - relationship_type: str # ally, opponent, neutral - strength: float # normalized relationship strength + """ + EntityRelationship model for database storage. + + This model represents a relationship between two political entities + and is stored in PostgreSQL. + """ + id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True) + relationship_type: RelationshipType + strength: float = Field(default=0.5, ge=0.0, le=1.0) last_updated: datetime = Field(default_factory=datetime.utcnow) - source_entity: PoliticalEntity = Relationship(back_populates="relationships") + + # Foreign keys + source_entity_id: uuid.UUID = Field( + foreign_key="politicalentity.id", + nullable=False, + ondelete="CASCADE" + ) + target_entity_id: uuid.UUID = Field( + foreign_key="politicalentity.id", + nullable=False, + ondelete="CASCADE" + ) + + # Relationships + source_entity: PoliticalEntity = Relationship( + back_populates="source_relationships", + sa_relationship_kwargs={"foreign_keys": "EntityRelationship.source_entity_id"} + ) + target_entity: PoliticalEntity = Relationship( + back_populates="target_relationships", + sa_relationship_kwargs={"foreign_keys": "EntityRelationship.target_entity_id"} + ) ``` -## 3. Document Database Design +## 3. Document Database Design (MongoDB) ### 3.1 Primary Technology -MongoDB for flexible document storage and querying +MongoDB for flexible document storage and querying. Asynchronous access via `motor`. ### 3.2 Key Collections -- **posts**: Social media posts from tracked accounts -- **comments**: User comments on tracked posts -- **metrics**: Aggregated engagement statistics -- **topics**: Topic analysis results and trends +- **posts**: Social media posts from tracked accounts (`SocialMediaPost` schema). +- **comments**: User comments on tracked posts (`SocialMediaComment` schema). +- **topics**: Topic definitions and metadata (`TopicAnalysis` schema). +- **topic_occurrences**: Instances where topics are detected in content (`TopicOccurrence` schema). +- **topic_trends**: Aggregated topic analysis over time (`TopicTrend` schema). +- **metrics**: (Potentially) Aggregated engagement statistics (Schema TBD or integrated into other collections). -### 3.3 Schema Patterns +### 3.3 Schema Definitions (Pydantic) -**Post Document Example:** -```javascript -{ - "_id": ObjectId, - "platform_id": String, // Original ID from the platform - "platform": String, // twitter, facebook, etc. - "account_id": String, // Reference to PostgreSQL SocialMediaAccount.id - "content_type": String, // post, story, video, etc. - "content": { - "text": String, - "media": Array, // URLs to media content - "links": Array // External links - }, - "metadata": { - "created_at": Date, - "location": Object, - "language": String, - "client": String - }, - "engagement": { - "likes": Number, - "shares": Number, - "comments": Number, - "engagement_rate": Number - }, - "analysis": { - "sentiment_score": Number, - "topics": Array, - "entities_mentioned": Array, - "key_phrases": Array, - "emotional_tone": String - }, - "vector_id": String // Reference to vector database entry -} +The Pydantic models defining the structure for MongoDB documents are located in: +`backend/app/db/schemas/mongodb.py` + +Key schemas include: + +**`SocialMediaPost`:** +```python +class SocialMediaPost(BaseModel): + """ + Schema for social media posts stored in MongoDB. + + This model represents a post from various social media platforms + including its content, metadata, engagement metrics, and analysis. + """ + platform_id: str = Field(..., description="Original ID from the social media platform") + platform: str = Field(..., description="Social media platform name (e.g., twitter, facebook)") + account_id: UUID = Field(..., description="Reference to PostgreSQL SocialMediaAccount UUID") + content_type: str = Field(..., description="Type of post (e.g., post, sidecar, video)") + short_code: Optional[str] = Field(None, description="Platform shortcode for URL (e.g., Instagram)") + url: Optional[HttpUrl] = Field(None, description="Direct URL to the post") + + content: PostContent # Defined in mongodb.py + metadata: PostMetadata # Defined in mongodb.py + engagement: PostEngagement # Defined in mongodb.py + analysis: Optional[PostAnalysis] = None # Defined in mongodb.py + child_posts: Optional[List[ChildPost]] = None # Defined in mongodb.py + video_data: Optional[VideoData] = None # Defined in mongodb.py + vector_id: Optional[str] = Field(None, description="Reference to vector database entry") + + # Note: _id is handled implicitly by MongoDB/Motor or via repository layer + class Config: + schema_extra = { ... } # Example included in mongodb.py ``` -### 3.4 Indexing Strategy +**`SocialMediaComment`:** +```python +class SocialMediaComment(BaseModel): + """ + Schema for social media comments stored in MongoDB. + + This model represents a comment on a social media post including + its content, metadata, engagement metrics, replies, and analysis. + """ + platform_id: str = Field(..., description="Original ID from the social media platform") + platform: str = Field(..., description="Social media platform name (e.g., twitter, facebook)") + post_id: str = Field(..., description="Reference to MongoDB post ID") + post_url: Optional[HttpUrl] = Field(None, description="URL of the original post") + + user_id: str = Field(..., description="ID of the commenter") + user_name: str = Field(..., description="Username of the commenter") + user_full_name: Optional[str] = Field(None, description="Full display name of the commenter") + user_profile_pic: Optional[HttpUrl] = Field(None, description="Profile picture URL of the commenter") + user_verified: bool = Field(False, description="Whether the user has a verification badge") + user_private: bool = Field(False, description="Whether the user's account is private") + + content: CommentContent # Defined in mongodb.py + metadata: CommentMetadata # Defined in mongodb.py + engagement: CommentEngagement # Defined in mongodb.py + + replies: List[CommentReply] = [] # Defined in mongodb.py + + analysis: Optional[CommentAnalysis] = None # Defined in mongodb.py + user_details: Optional[CommentUserDetails] = None # Defined in mongodb.py + vector_id: Optional[str] = Field(None, description="Reference to vector database entry") + + # Note: _id is handled implicitly by MongoDB/Motor or via repository layer + class Config: + schema_extra = { ... } # Example included in mongodb.py +``` + +**`TopicAnalysis`:** +```python +class TopicAnalysis(BaseModel): + """ + Schema for topic analysis data stored in MongoDB. + + This model represents a topic that can be analyzed across social media content, + including its definition, related keywords, and categorization. + """ + topic_id: str = Field(..., description="Unique identifier for the topic") + name: str = Field(..., description="Descriptive name of the topic") + keywords: List[str] = Field(..., description="List of related keywords or phrases") + description: Optional[str] = Field(None, description="Optional explanation of the topic") + category: str = Field(..., description="Broader category the topic belongs to (e.g., Economy, Healthcare)") + created_at: datetime = Field(default_factory=datetime.utcnow, description="Timestamp when the topic was created") + updated_at: datetime = Field(default_factory=datetime.utcnow, description="Timestamp when the topic was last updated") + + # Note: _id is handled implicitly by MongoDB/Motor or via repository layer + class Config: + schema_extra = { ... } # Example included in mongodb.py +``` + +**(Other schemas like `TopicOccurrence`, `TopicTrend`, and nested sub-models are also defined in `mongodb.py`)** -- Compound index on `platform` and `account_id` -- Compound index on `metadata.created_at` and `account_id` -- Text index on `content.text` for content search -- Single field indexes on `engagement` metrics +### 3.4 Indexing Strategy -## 4. In-memory Database Design +*(This section remains largely conceptual and should guide index creation)* +- **posts collection:** + - Compound index on `platform` and `account_id` + - Compound index on `metadata.created_at` and `account_id` + - Text index on `content.text` for content search + - Index on `platform_id` (unique potentially) + - Index on `short_code` + - Consider indexes on frequently queried `engagement` fields. +- **comments collection:** + - Compound index on `platform` and `post_id` + - Index on `platform_id` (unique potentially) + - Index on `user_id` + - Compound index on `metadata.created_at` and `post_id` +- **topics collection:** + - Index on `topic_id` (unique) + - Index on `name` + - Index on `category` +- **topic_occurrences collection:** + - Compound index on `topic_id` and `content_id` + - Compound index on `topic_id` and `detected_at` +- **topic_trends collection:** + - Compound index on `topic_id`, `time_period`, `start_date` + +*(Actual indexes should be managed via Motor/PyMongo commands or an ODM like Beanie if adopted later)* + +## 4. In-memory Database Design (Redis - NOT in MVP) ### 4.1 Primary Technology -Redis for caching, real-time metrics and messaging +Redis for caching, real-time metrics and messaging - **NOT IMPLEMENTED IN MVP** + +### 4.2 MVP Alternative + +In the MVP version, the application will: +- Use application-level caching where necessary (e.g., in-memory dicts, potentially FastAPI Cache). +- Store metrics directly in MongoDB. +- Use the simple TaskManager system for background tasks. +- Defer real-time notifications to future releases. -### 4.2 Key Data Structures +### 4.3 Post-MVP Implementation +The following Redis features will be implemented after the MVP: - **Hash maps**: Entity and post metrics (`entity:{id}:metrics`) - **Sorted sets**: Trending topics and influencers (`trending:topics:{timeframe}`) - **Lists**: Recent activity streams (`activity:entity:{id}`) - **Pub/Sub channels**: Real-time alerts and notifications -### 4.3 Caching Strategy +### 4.4 Caching Strategy (Post-MVP) -- Time-based expiration for volatile metrics -- LRU eviction policy for cached data -- Write-through cache for critical metrics +- Time-based expiration for volatile metrics. +- LRU eviction policy for cached data. +- Write-through cache for critical metrics. -## 5. Vector Database Design +## 5. Vector Database Design (Pinecone) ### 5.1 Primary Technology -Pinecone or similar vector database for semantic similarity analysis +Pinecone for semantic similarity analysis. Connection managed via `pinecone-client`. ### 5.2 Embedding Strategy -- Text embeddings using sentence-transformers -- 1536-dimension vectors for high-fidelity similarity -- Namespaces separated by content type -- Metadata filtering for efficient queries +- Text embeddings generated (e.g., using `sentence-transformers`). +- Vector dimensionality depends on the chosen embedding model. +- Namespaces likely separated by content type (e.g., `posts`, `comments`). +- Metadata stored alongside vectors for filtering. -### 5.3 Vector Schema +### 5.3 Vector Schema (Conceptual) ```javascript { - "id": String, // Unique identifier - "values": Array, // Embedding vector + "id": String, // Unique identifier (e.g., MongoDB document platform_id) + "values": Array, // Embedding vector (e.g., [0.1, 0.2, ...]) "metadata": { - "content_type": String, // post, comment - "source_id": String, // MongoDB ID of source content - "entity_id": String, // PostgreSQL ID of political entity - "platform": String, - "created_at": Date, - "topics": Array, - "sentiment_score": Number + "content_type": String, // "post" or "comment" + "source_id": String, // MongoDB document platform_id + "account_id": String, // PostgreSQL SocialMediaAccount UUID (as string) + "entity_id": String, // PostgreSQL PoliticalEntity UUID (as string) + "platform": String, // e.g., "twitter", "instagram" + "created_at": ISODate, // Timestamp of original content + "topics": Array, // List of associated topic IDs/labels + "sentiment_score": Number // Sentiment of the source content + // Add other filterable metadata as needed } } ``` +*(Actual interaction via `pinecone-client` library)* ## 6. Cross-Database Integration ### 6.1 Reference Patterns -- PostgreSQL → MongoDB: UUID references stored as strings -- MongoDB → Vector DB: Document IDs linked to vector entries -- All DBs → Redis: Consistent key format for entity references +- **PostgreSQL → MongoDB**: UUIDs from PostgreSQL (`PoliticalEntity.id`, `SocialMediaAccount.id`) are stored as strings within relevant MongoDB documents (e.g., `SocialMediaPost.account_id`). +- **MongoDB → Vector DB**: The `platform_id` (or potentially MongoDB's `_id` as string) of posts/comments is used as the vector `id` in Pinecone. The `vector_id` field in MongoDB schemas stores this Pinecone ID. +- **All DBs → Redis**: **Not applicable in MVP**. (Post-MVP: Relevant IDs will be used as keys in Redis). + +### 6.2 Synchronization Strategy (MVP Version) + +- PostgreSQL is the source of truth for entity and account data. +- MongoDB stores the collected social media content. +- Relationships are established during data ingestion/processing (e.g., when a post is saved, its `account_id` links it to the PostgreSQL `SocialMediaAccount`). +- Updates requiring cross-database consistency rely on application logic (e.g., updating entity info might trigger reprocessing of related content if necessary). -### 6.2 Synchronization Strategy +### 6.3 Post-MVP Synchronization -- PostgreSQL as the source of truth for entity data -- MongoDB change streams for data propagation -- Redis as intermediary for real-time updates -- Periodic reconciliation for data consistency +- Explore MongoDB change streams or event-driven patterns for propagating updates (e.g., entity name change triggers updates in related analysis). +- Use Redis Pub/Sub or a message queue (like RabbitMQ with Celery) for inter-service communication if the application becomes more distributed. -### 6.3 Transaction Management +### 6.4 Transaction Management -- Two-phase commit for critical cross-database operations -- Eventual consistency model for non-critical updates -- Compensating transactions for error recovery +- Standard database transactions used within single database operations (PostgreSQL commit/rollback, MongoDB atomic operations). +- Cross-database operations in MVP are typically not transactional. Design for eventual consistency or use compensating actions in application logic if failures occur mid-process. +- Post-MVP: Consider two-phase commit patterns or Saga pattern for critical cross-database workflows if required. ## 7. Performance Optimization ### 7.1 Query Optimization -- Materialized views for frequent analytical queries -- Denormalization of frequently accessed data -- Query result caching with Redis +- Leverage database indexes effectively (see section 3.4). +- Write efficient queries in repository layers. +- Use projection in MongoDB to fetch only necessary fields. +- Implement pagination for all list endpoints. +- Application-level caching for frequently accessed, rarely changing data. -### 7.2 Sharding Strategy +### 7.2 Sharding Strategy (Post-MVP) -- MongoDB sharded by entity and time period -- Vector database partitioned by content domains -- Redis cluster for horizontal scaling +- Consider sharding MongoDB collections based on high-cardinality keys like `account_id` or time ranges if data volume grows significantly. +- Vector database partitioning/sharding handled by the service provider (Pinecone). ### 7.3 Connection Pooling -- Optimized connection pools for each database -- Connection reuse across related operations -- Graceful handling of connection failures \ No newline at end of file +- Rely on connection pooling provided by database drivers (`psycopg` for SQLModel/PostgreSQL, `motor` for MongoDB). +- Ensure pool sizes are configured appropriately based on expected load and application concurrency (e.g., FastAPI worker count). \ No newline at end of file diff --git a/.cursor/rules/implementation_status.mdc b/.cursor/rules/implementation_status.mdc new file mode 100644 index 0000000000..6c4e08d462 --- /dev/null +++ b/.cursor/rules/implementation_status.mdc @@ -0,0 +1,118 @@ +--- +description: +globs: +alwaysApply: false +--- +# Backend Implementation Status (vs. next-implementations.mdc) + +This document summarizes the current implementation status of the backend project compared to the plan outlined in `next-implementations.mdc`, based on analysis of dependencies (`pyproject.toml`) and directory structure (`backend/app`). + +**Overall Discrepancies Noted:** + +* **Non-MVP Dependencies:** The project includes dependencies for `redis`, `celery`, `pika` (RabbitMQ), and `aiokafka`, which are explicitly marked as **NOT in MVP** in the technical stack documentation. This suggests either the implementation exceeds the MVP scope or the documentation is not fully up-to-date. +* **Missing External Integration Dependencies:** Dependencies for `apify-client` and `anthropic` (planned for data collection and LLM analysis) are missing. The `openai` client is present, which might be used as an alternative for LLM tasks. + +--- + +## Phase 1: Environment and Dependency Setup + +* **Task 1.1: Update requirements.txt (pyproject.toml):** + * **Status:** Partially Implemented / Discrepancy Found + * **Notes:** + * Core MVP dependencies (FastAPI, SQLModel, Pydantic, etc.) are present. + * MVP Database clients (`pymongo`, `motor`) are present. + * Non-MVP dependencies (`redis`, `celery`, `pika`, `aiokafka`) are present. + * External integration dependencies (`apify-client`, `anthropic`) are **missing**. `openai` is present. + * `httpx` and `pydantic` are present. +* **Task 1.2: Update Docker Configuration:** + * **Status:** Needs Verification + * **Notes:** Requires inspection of `docker-compose.yml` to confirm if MongoDB service is included. The `Dockerfile` exists. +* **Task 1.3: Update Configuration Module:** + * **Status:** Needs Verification + * **Notes:** Requires inspection of `backend/app/core/config.py` to confirm settings for MongoDB, external APIs (APIFY, LLM), and task processing. + +## Phase 2: Database Infrastructure Implementation + +* **Task 2.1: Create MongoDB Connection Utilities:** + * **Status:** Needs Verification + * **Notes:** Presence of `motor` suggests async connection utilities might exist, likely within `backend/app/db/` or a utils file. +* **Task 2.2: Implement Database Startup and Shutdown Events:** + * **Status:** Needs Verification + * **Notes:** Requires inspection of `backend/app/main.py` or related application setup files. +* **Task 2.3: Create SQL Database Models:** + * **Status:** Likely Implemented + * **Notes:** The `backend/app/db/` directory exists, and `sqlmodel` is a dependency. Models (`PoliticalEntity`, `SocialMediaAccount`, `EntityRelationship`) are expected within a `models` subdirectory. +* **Task 2.4: Create MongoDB Document Schemas:** + * **Status:** Likely Implemented + * **Notes:** The `backend/app/schemas/` directory exists, and `pydantic` is a dependency. Schemas (`SocialMediaPost`, `SocialMediaComment`, etc.) are expected here. + +## Phase 3: Repository and Service Layer Implementation + +* **Task 3.1: Implement PostgreSQL Repositories:** + * **Status:** Likely Implemented + * **Notes:** The `backend/app/services/` directory exists, often containing repositories. Requires inspection of this directory. +* **Task 3.2: Implement MongoDB Repositories:** + * **Status:** Likely Implemented + * **Notes:** Similar to SQL repositories, these are expected within `backend/app/services/`. Requires inspection. +* **Task 3.4: Implement Search Service:** + * **Status:** Needs Verification + * **Notes:** Requires code inspection, potentially within `backend/app/services/` or `backend/app/api/`. + +## Phase 4: Task Processing Implementation + +* **Task 4.1: Implement Simple Task Processing System:** + * **Status:** Uncertain / Discrepancy Found + * **Notes:** The `tasks/` directory exists, but the presence of `celery` dependency contradicts the plan for a *simple* system using FastAPI background tasks for MVP. It's unclear if the simple system exists or if Celery was implemented instead/in addition. +* **Task 4.2: Implement Data Collection Tasks Using APIFY:** + * **Status:** Not Implemented + * **Notes:** The required `apify-client` dependency is missing. +* **Task 4.3: Implement Content Analysis Tasks Using LLMs:** + * **Status:** Partially Implemented / Needs Verification + * **Notes:** The planned `anthropic` dependency is missing, but `openai` is present. Analysis tasks using OpenAI might be implemented. Requires inspection of `backend/app/processing/` or `backend/app/tasks/`. + +## Phase 5: API Endpoint Implementation + +* **Task 5.1: Implement Entity Management Endpoints:** + * **Status:** Needs Verification + * **Notes:** Requires inspection of `backend/app/api/api_v1/endpoints/`. +* **Task 5.2: Implement Content Collection Endpoints:** + * **Status:** Needs Verification / Likely Partial + * **Notes:** Depends on the implementation of Phase 4.2. Endpoints might exist but lack the APIFY integration. +* **Task 5.3: Implement Content Analysis Endpoints:** + * **Status:** Needs Verification / Likely Partial + * **Notes:** Depends on the implementation of Phase 4.3. Endpoints might exist, possibly using OpenAI. +* **Task 5.4: Implement Content Search Endpoints:** + * **Status:** Needs Verification + * **Notes:** Depends on the implementation of Phase 3.4. + +## Phase 6: Testing and Integration + +* **Task 6.1: Create Unit Tests:** + * **Status:** Partially Implemented / Needs Verification + * **Notes:** `pytest` is installed, and `tests/` or `testing/` directories exist. The extent of coverage is unknown. +* **Task 6.2: Create Integration Tests:** + * **Status:** Needs Verification + * **Notes:** Requires inspection of test files. +* **Task 6.3: Create End-to-End Tests:** + * **Status:** Needs Verification + * **Notes:** Requires inspection of test files. + +## Phase 7: Documentation and Deployment + +* **Task 7.1: Update API Documentation:** + * **Status:** Likely Implemented (Auto-generated) + * **Notes:** FastAPI provides automatic OpenAPI documentation. Manual updates might be needed. +* **Task 7.2: Create Technical Documentation:** + * **Status:** Partially Implemented + * **Notes:** `README.md` exists. Specific docs for architecture, schema, integrations need verification. +* **Task 7.3: Create Deployment Scripts:** + * **Status:** Partially Implemented + * **Notes:** `Dockerfile` exists. Specific scripts for DB initialization/seeding need verification (e.g., `initial_data.py` exists but might be basic). + +--- + +**Next Steps:** + +* Review the "Needs Verification" items by inspecting the relevant code sections. +* Update the project documentation (`backend-technical-stack.mdc`, `database-architecture.mdc`) to reflect the actual implemented stack, especially regarding non-MVP components like Redis and Celery. +* Decide whether to proceed with the missing `next-implementations` tasks (e.g., APIFY integration) or adjust the plan based on the current state. \ No newline at end of file diff --git a/.cursor/rules/next-implementations.mdc b/.cursor/rules/next-implementations.mdc index e5a47c0a7e..c3b1e9e3e5 100644 --- a/.cursor/rules/next-implementations.mdc +++ b/.cursor/rules/next-implementations.mdc @@ -3,62 +3,52 @@ description: Next Implementations globs: alwaysApply: false --- -# Backend Implementation Plan: Political Social Media Analysis Platform +# Backend Implementation Plan: Political Social Media Analysis Platform (MVP) -This implementation plan addresses the gap between the specified technical stack in the documentation and the current implementation. The plan follows a phased approach focusing on implementing the hybrid database architecture and data processing capabilities. +This implementation plan addresses the gap between the specified technical stack in the documentation and the current implementation, with a focus on building a Minimum Viable Product (MVP). The plan follows a phased approach prioritizing essential features while deferring more complex components until after the MVP. ## Phase 1: Environment and Dependency Setup ### Task 1.1: Update requirements.txt -Add the following dependencies to the project: +Add the following MVP-critical dependencies to the project: - **Database Clients** - - `motor>=3.1.1` - MongoDB async driver - - `pymongo>=4.3.3` - MongoDB sync driver - - `redis>=4.5.4` - Redis client - - `pinecone-client>=2.2.1` - Vector database client - -- **Task Processing** - - `celery>=5.3.0` - Task queue - - `kafka-python>=2.0.2` - Kafka client - - `pika>=1.3.1` - RabbitMQ client - -- **ML/NLP** - - `spacy>=3.6.0` - NLP processing - - `transformers>=4.28.0` - Hugging Face transformers - - `sentence-transformers>=2.2.2` - Text embeddings - - `scikit-learn>=1.2.0` - ML utilities - - `torch>=2.0.0` - Deep learning + - `pymongo>=4.3.3` - MongoDB client for document storage + - `motor>=3.1.1` - Async MongoDB client + +- **External Integrations** + - `apify-client>=1.1.0` - Client for APIFY web scraping platform + - `anthropic>=0.5.0` - Client for Claude LLM API + +- **Web/HTTP** + - `httpx>=0.24.0` - Async HTTP client + +- **Data Processing** + - `pydantic>=2.0.0` - Data validation (ensure compatibility with FastAPI) ### Task 1.2: Update Docker Configuration Add the following services to docker-compose.yml: - MongoDB (version 6.0+) -- Redis (version 7.0+) -- RabbitMQ (version 3.12+) -- Apache Kafka (version 3.4+) -- Celery worker and beat services ### Task 1.3: Update Configuration Module Extend the application configuration to include settings for: - MongoDB connection parameters -- Redis connection parameters -- Pinecone API credentials -- Celery broker and backend URLs -- Kafka bootstrap servers -- NLP model settings +- APIFY API credentials and actor IDs +- Claude/Anthropic API credentials +- Task processing settings ## Phase 2: Database Infrastructure Implementation -### Task 2.1: Create Database Connection Utilities -Create connection modules for: -- MongoDB async client -- Redis async client -- Pinecone vector database client +### Task 2.1: Create MongoDB Connection Utilities +Create connection module for MongoDB: +- Implement async client setup +- Create connection and shutdown functions +- Implement database and collection access patterns ### Task 2.2: Implement Database Startup and Shutdown Events Update the FastAPI application to: -- Connect to all databases on startup -- Close all connections on shutdown +- Connect to MongoDB on startup +- Close MongoDB connection on shutdown ### Task 2.3: Create SQL Database Models Implement SQLModel classes for: @@ -70,15 +60,7 @@ Implement SQLModel classes for: Create Pydantic models for MongoDB collections: - SocialMediaPost - SocialMediaComment -- MetricsAggregation -- TopicAnalysis - -### Task 2.5: Redis Data Structure Definitions -Define Redis key patterns and data structures for: -- Entity metrics caching -- Trending topics tracking -- Activity streams -- Real-time alerts +- TopicAnalysis (simplified version for MVP) ## Phase 3: Repository and Service Layer Implementation @@ -92,176 +74,134 @@ Create repositories for SQL models: Create repositories for MongoDB collections: - PostRepository - CommentRepository -- MetricsRepository -- TopicRepository - -### Task 3.3: Implement Redis Service -Create service for Redis operations: -- CacheService - for general caching -- MetricsService - for real-time metrics -- ActivityService - for activity streams - -### Task 3.4: Implement Vector Database Service -Create service for vector database operations: -- VectorEmbeddingService - for creating and managing embeddings -- SimilaritySearchService - for semantic search operations - -## Phase 4: Task Processing Implementation - -### Task 4.1: Set up Celery Infrastructure -Create core Celery configuration: -- Worker setup with queues -- Task routing configuration -- Beat scheduling for periodic tasks - -### Task 4.2: Implement Data Collection Tasks -Create tasks for scraping social media platforms: -- TwitterScraper -- FacebookScraper -- InstagramScraper -- TikTokScraper - -### Task 4.3: Implement Analysis Tasks -Create tasks for content analysis: -- SentimentAnalysisTask -- TopicModelingTask -- EntityRecognitionTask -- RelationshipAnalysisTask - -### Task 4.4: Implement Vector Embedding Tasks -Create tasks for generating embeddings: -- TextEmbeddingTask -- RelationshipEmbeddingTask - -### Task 4.5: Setup Kafka Stream Processors -Implement Kafka producers and consumers: -- RawContentProducer -- EntityMentionConsumer -- SentimentChangeConsumer -- EngagementMetricsConsumer - -## Phase 5: NLP and ML Pipeline Implementation - -### Task 5.1: Set up NLP Models -Initialize and configure NLP models: -- spaCy pipeline for entity recognition -- Transformer models for sentiment analysis -- Sentence transformers for embeddings - -### Task 5.2: Implement Sentiment Analysis -Create sentiment analysis pipeline: -- Text preprocessing -- Sentiment scoring -- Emotional tone classification - -### Task 5.3: Implement Topic Modeling -Create topic modeling pipeline: -- Text preprocessing -- Topic extraction -- Topic categorization - -### Task 5.4: Implement Entity Recognition -Create entity recognition pipeline: -- Named entity recognition -- Entity linking to database -- Relationship extraction - -### Task 5.5: Implement Vector Embedding Generation -Create embedding pipeline: -- Text preprocessing -- Embedding generation -- Vector storage and indexing - -## Phase 6: API Endpoint Implementation - -### Task 6.1: Implement Entity Management Endpoints +- TopicRepository (simplified version for MVP) + +### Task 3.4: Implement Search Service +Create basic search service for content: +- Text-based search across posts and comments +- Filter by political entity, platform, date range +- Sort by relevance or engagement metrics + +## Phase 4: Task Processing Implementation (Simplified for MVP) + +### Task 4.1: Implement Simple Task Processing System +Create a lightweight task processor instead of Celery: +- Design a task manager using FastAPI background tasks +- Implement task status tracking and error handling +- Create task type definitions +- Handle task dependencies and workflows + +### Task 4.2: Implement Data Collection Tasks Using APIFY +Create the data collection system: +- Implement APIFY client wrapper +- Create base collector and platform-specific collectors +- Implement data transformation and storage +- Create collector factory for extensibility + +### Task 4.3: Implement Content Analysis Tasks Using LLMs +Create the content analysis system: +- Implement Claude/Anthropic API client wrapper +- Design prompt templates for different analysis types +- Create analyzers for sentiment, topics, and entities +- Implement result parsing and database updates + +## Phase 5: API Endpoint Implementation + +### Task 5.1: Implement Entity Management Endpoints Create endpoints for entity management: - CRUD operations for political entities - Social media account management - Relationship management -### Task 6.2: Implement Content Search Endpoints +### Task 5.2: Implement Content Collection Endpoints +Create endpoints for data collection: +- Trigger scraping for accounts/platforms +- Retrieve collected content with filtering +- Manage scraping configurations and schedules + +### Task 5.3: Implement Content Analysis Endpoints +Create endpoints for content analysis: +- Trigger analysis for specific content +- Retrieve analysis results with filtering +- Configure analysis parameters + +### Task 5.4: Implement Content Search Endpoints Create endpoints for content search: - Text search across platforms - Advanced filtering options -- Semantic similarity search +- Search by sentiment, topics, or entities -### Task 6.3: Implement Analytics Endpoints -Create endpoints for analytics: -- Sentiment analysis results -- Topic distribution -- Engagement metrics -- Relationship graphs +## Phase 6: Testing and Integration -### Task 6.4: Implement Real-time Monitoring Endpoints -Create endpoints for real-time monitoring: -- Activity streams -- Alert configuration -- Trend detection - -## Phase 7: Testing and Integration - -### Task 7.1: Create Unit Tests +### Task 6.1: Create Unit Tests Develop unit tests for: - Repository layer - Service layer - Task processing -- NLP components +- API endpoints -### Task 7.2: Create Integration Tests +### Task 6.2: Create Integration Tests Develop integration tests for: - Cross-database operations -- Task queue processing -- Stream processing - -### Task 7.3: Create Performance Tests -Develop performance tests for: -- Database query performance -- Task processing throughput -- API endpoint response times +- Data collection flows +- Analysis flows -### Task 7.4: Create End-to-End Tests +### Task 6.3: Create End-to-End Tests Develop end-to-end tests for: -- Complete data processing pipeline +- Complete data collection and analysis pipeline - API endpoint workflows -## Phase 8: Documentation and Deployment +## Phase 7: Documentation and Deployment -### Task 8.1: Update API Documentation +### Task 7.1: Update API Documentation Update OpenAPI documentation for: - New endpoints - Request/response models - Authentication requirements -### Task 8.2: Create Technical Documentation +### Task 7.2: Create Technical Documentation Create documentation for: - Architecture overview - Database schema +- External integrations (APIFY, Claude) - Task processing workflow -- NLP pipeline -### Task 8.3: Create Deployment Scripts +### Task 7.3: Create Deployment Scripts Create scripts for: - Database initialization - Initial data seeding - Environment provisioning -### Task 8.4: Create Monitoring Setup -Configure monitoring for: -- Application performance -- Database health -- Task queue status -- Stream processing lag - ## Implementation Sequence 1. Start with dependency and environment setup (Phase 1) 2. Implement core database infrastructure (Phase 2) 3. Create repository and service layers (Phase 3) -4. Set up task processing framework (Phase 4) -5. Build NLP and ML pipelines (Phase 5) -6. Develop API endpoints (Phase 6) -7. Implement testing (Phase 7) -8. Finalize documentation and deployment (Phase 8) - -By following this implementation plan, the application will align with the specified technical stack in the documentation, including the hybrid database architecture and advanced data processing capabilities required for the Political Social Media Analysis Platform. \ No newline at end of file +4. Set up simplified task processing system (Phase 4.1) +5. Implement data collection with APIFY (Phase 4.2) +6. Implement content analysis with LLMs (Phase 4.3) +7. Develop API endpoints (Phase 5) +8. Implement testing (Phase 6) +9. Finalize documentation and deployment (Phase 7) + +## Components Deferred for Post-MVP Development + +1. **Advanced Database Architecture** + - Redis for caching and real-time operations + - Pinecone or other vector database for semantic search + +2. **Distributed Task Processing** + - Celery for task queues + - RabbitMQ as message broker + - Scheduled task processing with Celery Beat + +3. **Stream Processing** + - Kafka for real-time event streams + - Stream processors for continuous analysis + +4. **Advanced ML/NLP Pipeline** + - Custom trained models for political content + - Self-hosted language models + - Complex vector embeddings and similarity search + +By following this implementation plan, the application will deliver a functional MVP that provides the core features of social media content collection and analysis, while laying the groundwork for more advanced features in future iterations. \ No newline at end of file diff --git a/.cursor/rules/prd.mdc b/.cursor/rules/prd.mdc deleted file mode 100644 index 49807f9631..0000000000 --- a/.cursor/rules/prd.mdc +++ /dev/null @@ -1,316 +0,0 @@ ---- -description: PRD for the actual repository. -globs: -alwaysApply: false ---- -# Product Requirements Document -# Political Social Media Analysis Platform - -## Document History -| Version | Date | Author | Description | -|---------|------|--------|-------------| -| 1.0 | March 12, 2025 | | Initial PRD | - -## Overview -The Political Social Media Analysis Platform is a comprehensive application designed to scrape, analyze, and derive insights from political figures' social media presence. The platform collects posts and audience engagement data across multiple social media platforms, analyzes sentiment and relationships between political entities, and provides actionable intelligence for strategic communication planning. - -## Business Objectives -- Provide comprehensive social media intelligence for political campaigns and figures -- Enable data-driven decision making for future content and messaging strategies -- Track and analyze relationships between political entities (opponents and allies) -- Identify audience sentiment patterns to optimize communication strategies -- Deliver actionable insights to improve engagement and messaging effectiveness - -## Target Users -- Political campaign managers -- Political communications directors -- Policy advisors -- Political analysts -- Public relations specialists - -## User Stories - -### As a Campaign Manager -- I want to track all social media activity of our political figure across platforms -- I want to understand audience sentiment towards specific policy messages -- I want to compare our engagement metrics against political opponents -- I need to identify trending topics our audience cares about - -### As a Communications Director -- I want to see which messaging themes resonate most with our audience -- I need to identify potential PR issues before they escalate -- I want to track the effectiveness of our response to opponent messaging -- I need insights on optimal posting times and content formats - -### As a Political Analyst -- I want to map relationships between political figures based on social interactions -- I need to track evolving narratives on specific policy issues -- I want to identify influential supporters and detractors -- I need to analyze regional variations in audience response - -## Core Features - -### 1. Multi-Platform Data Collection -#### Description -Automated scraping system to collect posts, comments, and engagement metrics from multiple social media platforms. - -#### Requirements -- Support for Instagram, Facebook, TikTok, and Twitter/X -- Collection of posts, videos, comments, reactions, and shares -- Media content archiving (images, videos) -- Metadata extraction (posting time, location tags, mentioned accounts) -- Historical data backfilling capability - -#### Acceptance Criteria -- Successfully collects 99.5%+ of public posts from tracked accounts -- Captures all public comments on monitored posts -- Updates data at configurable intervals (minimum hourly) -- Maintains collection despite platform UI changes -- Properly handles rate limits and access restrictions - -### 2. Political Entity Relationship Mapping -#### Description -System to track, visualize, and analyze relationships between political figures based on mentions, interactions, and content similarity. - -#### Requirements -- Define relationship types (ally, opponent, neutral, evolving) -- Track direct mentions and indirect references -- Quantify relationship strength through interaction frequency -- Visualize network graphs of political relationships -- Track relationship changes over time - -#### Acceptance Criteria -- Accurately identifies relationships between tracked entities -- Updates relationship status based on new interactions -- Provides filterable visualization of relationship networks -- Generates alerts for significant relationship changes -- Supports manual relationship tagging to supplement automated analysis - -### 3. Sentiment Analysis Engine -#### Description -Advanced NLP system to analyze audience sentiment in comments and reactions to political content. - -#### Requirements -- Comment-level sentiment scoring (positive, negative, neutral) -- Emotion classification (anger, support, confusion, etc.) -- Aggregated sentiment metrics by post, topic, and time period -- Automated detection of sentiment shifts -- Topic-specific sentiment breakdowns - -#### Acceptance Criteria -- Sentiment classification with 85%+ accuracy compared to human analysts -- Real-time processing of new comments -- Identification of sentiment trends and anomalies -- Language support for Spanish as primary language, with English as secondary -- Ability to filter toxic or irrelevant comments - -### 4. Topic Modeling & Issue Tracking -#### Description -System to identify, categorize, and track discussion topics across social media content. - -#### Requirements -- Automatic topic extraction from posts and comments -- Classification of content by policy areas -- Tracking topic evolution over time -- Identification of emerging issues -- Comparison of topic engagement across platforms - -#### Acceptance Criteria -- Correctly categorizes 90%+ of content into relevant topics -- Identifies trending topics within 1 hour of emergence -- Tracks topic sentiment independently -- Correlates topics across different political entities -- Supports manual topic tagging and categorization - -### 5. Analysis Dashboard & Reporting -#### Description -Comprehensive visualization interface providing actionable insights from collected data. - -#### Requirements -- Overview dashboard with key performance metrics -- Entity-specific profile dashboards -- Comparative analysis tools -- Customizable report generation -- Data export functionality -- Alert configuration for critical metrics - -#### Acceptance Criteria -- Displays real-time and historical data -- Supports filtering by date range, platform, and entity -- Generates scheduled reports in PDF and Excel formats -- Allows bookmark saving of specific analysis views -- Maintains responsive performance with large datasets - -## Enhanced Features - -### 6. Real-time Monitoring -#### Description -Alert system for tracking sudden changes in sentiment or mentions by influential accounts. - -#### Requirements -- Configurable alert thresholds for sentiment changes -- Notification system for mentions by high-influence accounts -- Real-time monitoring dashboard -- Trend detection algorithms to identify viral potential -- Integration with external notification channels (email, SMS, app) - -#### Acceptance Criteria -- Detects significant sentiment shifts within 15 minutes -- Correctly identifies high-importance mentions with 95%+ accuracy -- Delivers alerts through configured channels within 5 minutes -- Provides context with each alert -- Supports alert customization by user role - -### 7. Campaign Effectiveness Metrics -#### Description -Advanced analytics to measure message resonance, audience growth, and predict content performance. - -#### Requirements -- Message resonance scoring across demographics -- Audience growth attribution models -- Content performance prediction -- A/B testing framework for message variations -- Conversion tracking (from awareness to engagement) - -#### Acceptance Criteria -- Provides quantifiable metrics for message effectiveness -- Tracks audience growth correlated with specific content strategies -- Predicts post performance with 80%+ accuracy -- Generates actionable recommendations for content optimization -- Supports campaign-level grouping and analysis - -### 8. Competitive Intelligence -#### Description -Tools to analyze and compare messaging strategies and effectiveness across political entities. - -#### Requirements -- Share of voice measurement across platforms -- Narrative comparison between entities -- Messaging gap analysis -- Audience overlap identification -- Response timing analysis - -#### Acceptance Criteria -- Accurately measures relative visibility of tracked entities -- Identifies messaging similarities and differences -- Highlights underserved topics with audience interest -- Tracks narrative evolution compared to competitors -- Provides actionable competitive positioning insights - -### 9. Historical Context -#### Description -Timeline views and analysis tools to track messaging evolution and effectiveness over time. - -#### Requirements -- Timeline visualization of messaging -- Crisis response effectiveness tracking -- Message consistency analysis -- Before/after analysis for major events -- Historical trend comparison - -#### Acceptance Criteria -- Displays comprehensive messaging history -- Allows comparison of multiple time periods -- Quantifies messaging consistency and evolution -- Identifies correlations between events and messaging changes -- Supports annotation of significant events - -### 10. Geographic Insights -#### Description -Tools to analyze regional variations in audience response and engagement. - -#### Requirements -- Regional sentiment mapping -- Demographic response analysis -- Location-based messaging effectiveness -- Geographic hot spots for specific topics -- Regional influence tracking - -#### Acceptance Criteria -- Maps engagement and sentiment to geographic regions -- Identifies regional variations in message effectiveness -- Provides insights for location-targeted messaging -- Tracks regional influence of political entities -- Supports filtering and comparison by region - -## Technical Requirements - -### Infrastructure -- Cloud-based deployment with scalability for traffic spikes -- Containerized architecture for consistent deployment -- Fault-tolerant design with redundancy for critical components -- Automated backup and disaster recovery - -### Security -- End-to-end encryption for all data -- Role-based access control -- Audit logging for all system actions -- Secure API authentication -- Regular security assessments - -### Performance -- Dashboard loading time < 3 seconds -- Data collection processing capacity of 10,000+ posts/hour -- Analysis processing of 100,000+ comments/hour -- Support for 100+ concurrent users -- 99.9% system uptime - -### Integration -- API access for external system integration -- Export formats: CSV, Excel, JSON -- Webhook support for real-time data sharing -- Email integration for reports and alerts -- Calendar integration for scheduling - -## Implementation Timeline - -### Phase 1: Foundation (Months 1-3) -- Core data collection infrastructure -- Basic data storage and processing -- Initial entity and relationship models -- Simple dashboard with basic metrics - -### Phase 2: Analysis Capabilities (Months 4-6) -- Sentiment analysis engine -- Topic modeling implementation -- Enhanced dashboard visualizations -- Basic reporting functionality - -### Phase 3: Advanced Features (Months 7-9) -- Relationship mapping visualization -- Campaign effectiveness metrics -- Competitive intelligence tools -- Alert system implementation - -### Phase 4: Refinement (Months 10-12) -- Geographic insights -- Historical context tools -- Performance optimization -- Enhanced reporting - -## Success Metrics -- System consistently captures >99% of relevant social media activity -- Sentiment analysis achieves >85% accuracy against human review -- Users report >50% reduction in time spent on manual social media analysis -- Platform identifies emerging issues 24+ hours before traditional methods -- Strategic recommendations achieve measurable improvement in engagement metrics - -## Assumptions & Constraints -- Public APIs or scraping capabilities remain available for target platforms -- Legal compliance with platform terms of service is maintained -- Processing capacity scales with data volume growth -- User adoption requires minimal training (<2 hours) -- System maintains compliance with relevant data privacy regulations - -## Open Questions -- How will the system handle platform API changes or limitations? -- What is the strategy for platforms that actively prevent scraping? -- How will we validate sentiment analysis accuracy? -- What is the approach for expanding language support beyond Spanish and English? -- How will we determine relationship classifications initially? - -## Appendix -- Glossary of Terms -- User Persona Details -- Competitive Analysis -- Technical Architecture Diagrams \ No newline at end of file diff --git a/.cursor/rules/server-architecture.mdc b/.cursor/rules/server-architecture.mdc index 87ba4d828a..793da6624c 100644 --- a/.cursor/rules/server-architecture.mdc +++ b/.cursor/rules/server-architecture.mdc @@ -3,9 +3,43 @@ description: Server architectur of the project. globs: alwaysApply: false --- +# Server Architecture (MVP and Future Implementation) + ## 1. System Overview -The Political Social Media Analysis Platform follows a modern, containerized microservices architecture designed for scalability, resilience, and maintainable development. This document outlines the overall system architecture, deployment strategy, and service interaction patterns. +The Political Social Media Analysis Platform follows a modern, containerized architecture designed for scalability, resilience, and maintainable development. This document outlines both the MVP system architecture and the target future implementation. + +### 1.1 MVP Architecture + +``` +┌───────────────────────┐ ┌───────────────────────┐ +│ │ │ │ +│ Frontend (React/TS) │◄────┤ Backend (FastAPI) │ +│ │ │ │ +└───────────────────────┘ └───────────┬───────────┘ + │ + ▼ +┌───────────────────────┐ ┌───────────────────────┐ +│ Database Layer │ │ Task Processing │ +│ │ │ (MVP Version) │ +│ ┌─────────────────┐ │ │ │ +│ │ PostgreSQL │ │ │ ┌─────────────────┐ │ +│ │ (Relational) │ │ │ │ FastAPI │ │ +│ └─────────────────┘ │ │ │ BackgroundTasks│ │ +│ │ │ └─────────────────┘ │ +│ ┌─────────────────┐ │ │ │ +│ │ MongoDB │ │ │ ┌─────────────────┐ │ +│ │ (Document) │ │ │ │ In-Memory │ │ +│ └─────────────────┘ │ │ │ TaskManager │ │ +│ ┌─────────────────┐ │ │ └─────────────────┘ │ +│ │ Pinecone │ │ │ │ +│ │ (Vector) │ │ └───────────────────────┘ +│ └─────────────────┘ │ +│ │ +└───────────────────────┘ +``` + +### 1.2 Future Architecture (Post-MVP) ``` ┌───────────────────────┐ ┌───────────────────────┐ @@ -43,18 +77,18 @@ The Political Social Media Analysis Platform follows a modern, containerized mic ## 2. Containerization Strategy -### 2.1 Docker Compose Architecture +### 2.1 Docker Compose Architecture (MVP) -The system uses Docker Compose for container orchestration with a dual-file approach: +The MVP system uses Docker Compose with a simplified service structure: | File | Purpose | Usage | -|------|---------|-------| +|---|---|----| | `docker-compose.yml` | Production-ready base configuration | Primary service definitions | | `docker-compose.override.yml` | Development environment customizations | Automatically merged during development | -### 2.2 Service Organization +### 2.2 Service Organization (MVP) -Services are organized into logical groups: +Services included in the MVP: 1. **Frontend Services** - React frontend application @@ -66,40 +100,34 @@ Services are organized into logical groups: 3. **Database Services** - PostgreSQL (relational data) - MongoDB (document data) - - Redis (caching and real-time operations) - Pinecone (vector embeddings) -4. **Message Processing** - - RabbitMQ (message broker) - - Celery Worker (task execution) - - Celery Beat (task scheduling) - -5. **Stream Processing** - - Kafka (event streaming) - - Zookeeper (Kafka coordination) - -6. **Development Tools** +4. **Development Tools** - Adminer (PostgreSQL management) - MongoDB Express (MongoDB management) - Traefik Proxy (API gateway) - Mailcatcher (email testing) + +### 2.3 Future Services (Post-MVP) + +These services will be added after the MVP phase: + +1. **Message Processing** + - RabbitMQ (message broker) + - Celery Worker (task execution) + - Celery Beat (task scheduling) - Celery Flower (task monitoring) -### 2.3 Development vs. Production +2. **Caching** + - Redis (caching and real-time operations) -| Aspect | Development | Production | -|--------|------------|------------| -| Restart Policy | `restart: "no"` | `restart: always` | -| Port Exposure | Ports exposed to host | Only necessary ports exposed | -| Volume Mounts | Source code mounted | Built artifacts only | -| Network Configuration | Local networks | External Traefik network | -| Health Checks | Simple checks | Comprehensive checks with retries | -| Environment | Development settings | Production settings | -| Logging | Verbose logging | Production logging levels | +3. **Stream Processing** + - Kafka (event streaming) + - Zookeeper (Kafka coordination) ## 3. Network Architecture -### 3.1 Network Configuration +### 3.1 Network Configuration (MVP) ``` ┌─────────────────────────────────────────────────────────────┐ @@ -111,6 +139,21 @@ Services are organized into logical groups: │ │ └─────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────────┐ +│ default │ +│ │ +│ ┌─────────┐ ┌─────────┐ │ +│ │PostgreSQL│ │ MongoDB │ │ +│ └─────────┘ └─────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 3.2 Future Network Configuration (Post-MVP) + +The full network configuration will add these additional services: + +``` ┌─────────────────────────────────────────────────────────────┐ │ default │ │ │ @@ -126,32 +169,20 @@ Services are organized into logical groups: └─────────────────────────────────────────────────────────────┘ ``` -### 3.2 Traefik Integration - -- **Production**: Uses external Traefik network with proper TLS termination -- **Development**: Includes local Traefik instance with insecure dashboard -- Routing follows pattern: `{service}.{domain}` → appropriate container - -### 3.3 HTTPS Configuration - -- Automatic TLS certificate issuance via Let's Encrypt -- HTTP to HTTPS redirection enforced -- Custom middleware for security headers - ## 4. Data Architecture -### 4.1 Hybrid Database Strategy +### 4.1 Hybrid Database Strategy (MVP) -The system employs a polyglot persistence approach using specialized databases: +The MVP employs a polyglot persistence approach with a subset of the full database strategy: -| Database | Purpose | Data Types | -|----------|---------|------------| -| PostgreSQL | Relational data, user accounts, structured entities | Users, political entities, relationships, configuration | -| MongoDB | Document storage, social media content | Posts, comments, media items, engagement metrics | -| Redis | Caching, real-time operations, task management | Session data, counters, leaderboards, task queues | -| Pinecone | Vector embeddings for semantic search | Text embeddings, similarity models | +| Database | Purpose | Data Types | MVP Status | +|----|---|---|---| +| PostgreSQL | Relational data, user accounts, structured entities | Users, political entities, relationships, configuration | ✅ Included | +| MongoDB | Document storage, social media content | Posts, comments, media items, engagement metrics | ✅ Included | +| Pinecone | Vector embeddings for semantic search | Text embeddings, similarity models | ✅ Included | +| Redis | Caching, real-time operations, task management | Session data, counters, leaderboards, task queues | ❌ **Not in MVP** | -### 4.2 Data Flow Patterns +### 4.2 Data Flow Patterns (MVP) ``` ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ @@ -161,30 +192,74 @@ The system employs a polyglot persistence approach using specialized databases: └─────────────┘ └──────┬──────┘ └─────────────┘ │ ▼ -┌─────────────┐ ┌─────────────┐ ┌─────────────┐ -│ │ │ │ │ │ -│ Celery Task │◄────┤ Task Queue │◄────┤ RabbitMQ │ -│ │ │ │ │ │ -└──────┬──────┘ └─────────────┘ └─────────────┘ +┌─────────────┐ ┌─────────────┐ +│ │ │ │ +│ Background │◄────┤ In-Memory │ +│ Task │ │ TaskManager │ +│ │ │ │ +└──────┬──────┘ └─────────────┘ │ ▼ -┌─────────────┐ ┌─────────────┐ ┌─────────────┐ -│ │ │ │ │ │ -│ MongoDB │ │ Redis Cache │ │ Pinecone │ -│ Storage │ │ │ │ Vectors │ -│ │ │ │ │ │ -└─────────────┘ └─────────────┘ └─────────────┘ +┌─────────────┐ ┌─────────────┐ +│ │ │ │ +│ MongoDB │ │ Pinecone │ +│ Storage │ │ Vectors │ +│ │ │ │ +└─────────────┘ └─────────────┘ ``` -### 4.3 Data Persistence +## 5. Task Processing Architecture -- Volume mapping for all databases to ensure data persistence -- Standardized volume naming: `{service-name}_data` -- Consistent backup solutions for each database type +### 5.1 MVP Implementation -## 5. Task Processing Architecture +The MVP uses a simplified task processing system: -### 5.1 Celery Integration +``` +┌─────────────┐ ┌─────────────┐ +│ │ │ │ +│ FastAPI │────►│ BackgroundTasks │ +│ Backend │ │ (Built-in) │ +│ │ │ │ +└─────────────┘ └──────┬──────┘ + │ + ▼ +┌─────────────┐ ┌─────────────┐ +│ │ │ │ +│ TaskManager │────►│ Task │ +│ (In-Memory) │ │ Execution │ +│ │ │ │ +└─────────────┘ └─────────────┘ +``` + +#### 5.1.1 MVP Task Manager + +```python +class TaskManager: + """Simple in-memory task management system for MVP.""" + + def __init__(self): + self.tasks = {} + self.status = {} + + async def create_task(self, task_type: str, params: dict): + """Create and track a new task.""" + task_id = str(uuid4()) + self.tasks[task_id] = { + "type": task_type, + "params": params, + "status": "pending", + "created_at": datetime.utcnow() + } + return task_id + + async def get_task_status(self, task_id: str): + """Get the current status of a task.""" + return self.tasks.get(task_id, {}).get("status", "not_found") +``` + +### 5.2 Future Task Processing (Post-MVP) + +The full Celery-based implementation will be added post-MVP: ``` ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ @@ -210,141 +285,94 @@ The system employs a polyglot persistence approach using specialized databases: └─────────────┘ └─────────────┘ ``` -### 5.2 Task Types - -- **Data Collection Tasks**: Social media scraping, data acquisition -- **Analysis Tasks**: Content analysis, sentiment scoring, entity extraction -- **Reporting Tasks**: Report generation, alert/notification creation -- **Maintenance Tasks**: Database cleanup, analytics generation - -### 5.3 Kafka Stream Processing - -- Event-driven architecture for real-time data streams -- Topic-based segregation of event types -- Consumer groups for scalable processing - ## 6. Security Architecture -### 6.1 Authentication and Authorization +### 6.1 Authentication and Authorization (MVP) - JWT-based authentication with appropriate expiration - Role-based access control (RBAC) - OAuth2 password flow with secure password hashing -### 6.2 Network Security +### 6.2 Network Security (MVP) - Traefik as edge gateway with TLS termination - Internal network isolation - Minimal port exposure -### 6.3 Secret Management - -- Environment variable-based secret injection -- No hardcoded credentials -- Support for container secrets in production - ## 7. Deployment Strategy -### 7.1 Development Workflow +### 7.1 MVP Deployment -``` -Local Development → CI/CD Pipeline → Staging → Production -``` +- **Development**: Docker Compose with override file +- **Production**: Simple Docker Compose deployment +- Scripts for container orchestration and monitoring -- **Local**: Docker Compose with override file -- **CI/CD**: Automated testing and container building -- **Staging**: Production-like environment for validation -- **Production**: Optimized for performance and security +### 7.2 Future Deployment Options (Post-MVP) -### 7.2 Scaling Strategy - -- Horizontal scaling of stateless services -- Vertical scaling of database services -- Load balancing through Traefik +- Kubernetes migration +- CI/CD pipeline integration +- Horizontal scaling with load balancing ## 8. Monitoring and Observability -### 8.1 Logging +### 8.1 MVP Monitoring - Structured logging format -- Log aggregation across services +- Health check endpoints - Sentry integration for error tracking -### 8.2 Metrics +### 8.2 Future Monitoring (Post-MVP) -- Health check endpoints for all services -- Prometheus-compatible metrics endpoints +- Prometheus metrics collection +- Grafana dashboards - Celery Flower for task monitoring +- Log aggregation system -## 9. Resilience Features - -### 9.1 Health Checks +## 9. Development Environment -- Database connectivity checks -- API endpoint checks -- Appropriate retry policies - -### 9.2 Failover Strategy - -- Restart policies for critical services -- Connection retry logic -- Graceful degradation when components are unavailable - -## 10. Development Environment - -### 10.1 Local Setup +### 9.1 Local Setup (MVP) - Simple startup with `docker-compose up` - Hot-reloading for backend and frontend -- Development admin interfaces for all databases - -### 10.2 Testing +- Development admin interfaces for databases +- Standardized environment variables -- Environment-specific testing configuration -- Integration tests with in-memory databases -- E2E testing with Playwright +### 9.2 Testing Infrastructure (MVP) -## 11. Future Considerations +- Unit tests for all components +- Integration tests for core functionality +- Mock data for social media platform collectors -### 11.1 Kubernetes Migration Path - -- Current Docker Compose structure designed for easy K8s migration -- Service definitions align with Kubernetes patterns -- Volume definitions compatible with persistent volume claims - -### 11.2 Service Mesh Integration - -- Prepared for Istio or Linkerd integration -- Service-to-service communication patterns established -- Observability foundations in place - -## Appendix A: Environment Variables +## Appendix A: Environment Variables (MVP) | Variable | Purpose | Example | -|----------|---------|---------| +|----|---|---| | `DOMAIN` | Base domain for all services | `example.com` | | `POSTGRES_*` | PostgreSQL configuration | `POSTGRES_USER=postgres` | | `MONGO_*` | MongoDB configuration | `MONGO_USER=mongo` | -| `RABBITMQ_*` | RabbitMQ configuration | `RABBITMQ_USER=guest` | -| `REDIS_*` | Redis configuration | `REDIS_PORT=6379` | | `SECRET_KEY` | Application encryption key | `supersecretkey` | | `SENTRY_DSN` | Sentry error tracking | `https://...` | +| `APIFY_API_KEY` | APIFY integration for social media collection | `apify_api_key_123` | -## Appendix B: Network Ports +## Appendix B: Network Ports (MVP) | Service | Port | Purpose | -|---------|------|---------| +|---|---|---| | Traefik | 80, 443 | HTTP/HTTPS | | PostgreSQL | 5432 | Database access | | MongoDB | 27017 | Database access | -| Redis | 6379 | Cache access | -| RabbitMQ | 5672, 15672 | AMQP and management | -| Kafka | 9092 | Stream processing | | FastAPI | 8000 | API access | | Frontend | 5173 | Web UI (development) | -## Appendix C: Related Documentation - -- `backend-technical-stack.mdc` - Backend technology details -- `database-architecture.mdc` - Detailed database design -- `data-processing-architecture.mdc` - Data processing pipeline details \ No newline at end of file +## Appendix C: MVP vs Future Implementation Summary + +| Component | MVP Implementation | Future Implementation | +|---|---|---| +| Frontend | React SPA | Enhanced React SPA | +| Backend | FastAPI | FastAPI with expanded capabilities | +| Task Processing | In-memory TaskManager | Celery, RabbitMQ, Redis | +| Data Storage | PostgreSQL, MongoDB, Pinecone | PostgreSQL, MongoDB, Pinecone, Redis | +| Message Queuing | None | RabbitMQ, Kafka | +| Caching | None | Redis | +| Stream Processing | None | Kafka, Zookeeper | +| Monitoring | Basic logging, Sentry | Prometheus, Grafana, Log aggregation | \ No newline at end of file diff --git a/.env b/.env index cea0cf42bd..d6301bb906 100644 --- a/.env +++ b/.env @@ -20,7 +20,7 @@ STACK_NAME=political-analysis-local BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173,http://localhost.tiangolo.com" SECRET_KEY="SII-BEQmcN8arjGWpOdpHhz0kz8PIqONaWRhekIqFDc" FIRST_SUPERUSER=admin@example.com -FIRST_SUPERUSER_PASSWORD=password +FIRST_SUPERUSER_PASSWORD=admin_password # Emails SMTP_HOST= @@ -36,17 +36,37 @@ POSTGRES_SERVER=localhost POSTGRES_PORT=5432 POSTGRES_DB=app POSTGRES_USER=postgres -POSTGRES_PASSWORD=changethis +POSTGRES_PASSWORD=postgres # MongoDB -MONGO_USER=mongouser -MONGO_PASSWORD=mongopassword -MONGO_DB=socialmediadb +MONGODB_SERVER=mongodb +MONGODB_PORT=27017 +MONGODB_DB=political_social_media +MONGODB_USER=mongo +MONGODB_PASSWORD=mongo +MONGODB_AUTH_SOURCE=admin +# This will be used by the docker-compose.override.yml +MONGO_USER=mongo +MONGO_PASSWORD=mongo +MONGO_DB=political_social_media + +# Vector Database +PINECONE_API_KEY=pcsk_JG4CK_GRo1us9gFT69SNoaXZRasDopqhBUqsfo9wAUzKbhUVMigBSWD7y7sqj146qZD52 +PINECONE_ENVIRONMENT=us-east1-gcp +PINECONE_INDEX_NAME=political-content + +# OpenAI +OPENAI_API_KEY=YOUR_OPENAI_API_KEY +OPENAI_MODEL=text-embedding-3-small +OPENAI_EMBEDDING_DIMENSION=1536 # RabbitMQ RABBITMQ_USER=rabbitmquser RABBITMQ_PASSWORD=rabbitmqpassword +# Redis configuration +REDIS_PASSWORD=redispassword + # Celery CELERY_BROKER=amqp://rabbitmquser:rabbitmqpassword@rabbitmq:5672// diff --git a/.github/.DS_Store b/.github/.DS_Store new file mode 100644 index 0000000000..eb488475d8 Binary files /dev/null and b/.github/.DS_Store differ diff --git a/backend/.DS_Store b/backend/.DS_Store new file mode 100644 index 0000000000..fd22ca5af7 Binary files /dev/null and b/backend/.DS_Store differ diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 0000000000..0519ecba6e --- /dev/null +++ b/backend/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/app/README.md b/backend/app/README.md new file mode 100644 index 0000000000..4158efbe79 --- /dev/null +++ b/backend/app/README.md @@ -0,0 +1,79 @@ +# Political Social Media Analysis Platform - Backend + +## Hybrid Database Architecture + +This application implements a hybrid database architecture to address diverse data requirements of social media analysis: + +| Component | Technology | Purpose | Status in MVP | +|-----|---|---|---| +| Relational Database | PostgreSQL | Entity data and relationships | Active | +| Document Database | MongoDB | Social media content and engagement | Active | +| In-memory Database | Redis | Caching and real-time operations | Prepared but Inactive | +| Vector Database | Pinecone | Semantic similarity analysis | Active | + +## MongoDB Component + +The MongoDB component stores social media content and engagement data. Its implementation includes: + +- **Collections**: + - `posts`: Social media posts from tracked accounts + - `comments`: User comments on tracked posts + +- **Schema Structure**: + - Pydantic models with nested subschemas + - Cross-references to PostgreSQL entities + - Flexible content and engagement tracking + +## Redis Component + +The Redis component is prepared but inactive in the MVP. Its implementation includes: + +- **Key Pattern Constants**: + - Standardized naming conventions with namespaces + - Structured patterns for different data types + +- **Data Structures**: + - Hash maps for entity metrics + - Sorted sets for trending topics + - Lists for activity streams + - Pub/Sub for real-time alerts + +- **Feature Flag**: + - `USE_REDIS` flag in settings (default: False) + - Mock Redis client for graceful fallback + +## Post-MVP Redis Implementation Plan + +The Redis functionality will be activated incrementally after the MVP: + +1. **Phase 1**: + - Enable entity metrics caching + - Implement basic alerts + +2. **Phase 2**: + - Add trending topics functionality + - Implement activity streams + +3. **Phase 3**: + - Full pub/sub implementation + - Advanced caching strategies + +## How to Enable Redis + +To enable Redis for development or production: + +1. Set `USE_REDIS=true` in your environment or .env file +2. Ensure Redis server is running and accessible +3. Verify Redis connection in logs during startup + +## Performance Considerations + +The MVP handles data access without Redis through: + +- Optimized MongoDB queries with proper indexing +- Application-level caching where critical +- Scheduled batch processing instead of real-time +- Limited polling instead of push notifications + +When performance metrics indicate a need (high latency, large data volumes), +Redis features can be enabled incrementally. \ No newline at end of file diff --git a/backend/app/api/api_v1/api.py b/backend/app/api/api_v1/api.py index 855c4941e6..dd9e62c2b0 100644 --- a/backend/app/api/api_v1/api.py +++ b/backend/app/api/api_v1/api.py @@ -2,10 +2,11 @@ api_router = APIRouter() -from app.api.api_v1.endpoints import items, login, private, users, utils +from app.api.api_v1.endpoints import items, login, private, users, utils, tasks api_router.include_router(login.router, tags=["login"]) api_router.include_router(users.router, prefix="/users", tags=["users"]) api_router.include_router(utils.router, prefix="/utils", tags=["utils"]) api_router.include_router(items.router, prefix="/items", tags=["items"]) -api_router.include_router(private.router, prefix="/private", tags=["private"]) \ No newline at end of file +api_router.include_router(private.router, prefix="/private", tags=["private"]) +api_router.include_router(tasks.router, prefix="/tasks", tags=["tasks"]) \ No newline at end of file diff --git a/backend/app/api/api_v1/endpoints/tasks.py b/backend/app/api/api_v1/endpoints/tasks.py new file mode 100644 index 0000000000..7e36dc1d55 --- /dev/null +++ b/backend/app/api/api_v1/endpoints/tasks.py @@ -0,0 +1,180 @@ +from typing import Any, Dict, List, Optional + +from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query +from pydantic import UUID4 + +from app.api.deps import CurrentUser, TaskManagerDep +from app.tasks.task_types import TaskStatus + +router = APIRouter() + + +@router.post("/data-collection") +async def create_data_collection_task( + platform: str, + entity_ids: List[str], + background_tasks: BackgroundTasks, + task_manager: TaskManagerDep, + current_user: CurrentUser, +) -> Dict[str, Any]: + """ + Schedule a data collection task for social media platforms. + """ + # Only allow superusers to schedule data collection tasks + if not current_user.is_superuser: + raise HTTPException(status_code=403, detail="Not enough permissions") + + task_id = task_manager.schedule_data_collection( + platform=platform, + entity_ids=entity_ids, + background_tasks=background_tasks, + ) + + return { + "success": True, + "message": f"Data collection task scheduled for {platform}", + "task_id": task_id + } + + +@router.post("/content-analysis") +async def create_content_analysis_task( + content_ids: List[str], + background_tasks: BackgroundTasks, + task_manager: TaskManagerDep, + current_user: CurrentUser, + analysis_types: List[str] = Query(default=["sentiment", "topics", "entities"]), +) -> Dict[str, Any]: + """ + Schedule a content analysis task for collected social media content. + """ + # Only allow superusers to schedule analysis tasks + if not current_user.is_superuser: + raise HTTPException(status_code=403, detail="Not enough permissions") + + task_id = task_manager.schedule_content_analysis( + content_ids=content_ids, + analysis_types=analysis_types, + background_tasks=background_tasks, + ) + + return { + "success": True, + "message": f"Content analysis task scheduled for {len(content_ids)} items", + "task_id": task_id + } + + +@router.post("/relationship-analysis") +async def create_relationship_analysis_task( + entity_ids: List[str], + background_tasks: BackgroundTasks, + task_manager: TaskManagerDep, + current_user: CurrentUser, + time_period: str = "last_30_days", +) -> Dict[str, Any]: + """ + Schedule a relationship analysis task between political entities. + """ + # Only allow superusers to schedule relationship analysis tasks + if not current_user.is_superuser: + raise HTTPException(status_code=403, detail="Not enough permissions") + + task_id = task_manager.schedule_relationship_analysis( + entity_ids=entity_ids, + background_tasks=background_tasks, + time_period=time_period, + ) + + return { + "success": True, + "message": f"Relationship analysis task scheduled for {len(entity_ids)} entities", + "task_id": task_id + } + + +@router.post("/report-generation") +async def create_report_generation_task( + report_type: str, + background_tasks: BackgroundTasks, + task_manager: TaskManagerDep, + current_user: CurrentUser, + entity_ids: Optional[List[str]] = None, + time_period: str = "last_30_days", + format: str = "json", +) -> Dict[str, Any]: + """ + Schedule a report generation task. + """ + # Only allow superusers to schedule report generation tasks + if not current_user.is_superuser: + raise HTTPException(status_code=403, detail="Not enough permissions") + + task_id = task_manager.schedule_report_generation( + report_type=report_type, + background_tasks=background_tasks, + entity_ids=entity_ids, + time_period=time_period, + format=format, + ) + + return { + "success": True, + "message": f"{report_type} report generation task scheduled", + "task_id": task_id + } + + +@router.get("/status/{task_id}") +async def get_task_status( + task_id: str, + task_manager: TaskManagerDep, + current_user: CurrentUser, +) -> Dict[str, Any]: + """ + Get the status of a specific task. + """ + try: + task_status = task_manager.get_task_status(task_id) + return { + "success": True, + "task": task_status + } + except ValueError as e: + raise HTTPException(status_code=404, detail=str(e)) + + +@router.get("/list") +async def list_tasks( + task_manager: TaskManagerDep, + current_user: CurrentUser, + status: Optional[str] = None, + limit: int = 100, + offset: int = 0, +) -> Dict[str, Any]: + """ + List all tasks, optionally filtered by status. + """ + # Convert string status to enum if provided + task_status = None + if status: + try: + task_status = TaskStatus(status) + except ValueError: + raise HTTPException( + status_code=400, + detail=f"Invalid status. Must be one of: {', '.join([s.value for s in TaskStatus])}" + ) + + tasks = task_manager.get_all_tasks( + status=task_status, + limit=limit, + offset=offset + ) + + return { + "success": True, + "tasks": tasks, + "count": len(tasks), + "total": len(task_manager.tasks) + } \ No newline at end of file diff --git a/backend/app/api/deps.py b/backend/app/api/deps.py index 1cdba8dc50..2108afe009 100644 --- a/backend/app/api/deps.py +++ b/backend/app/api/deps.py @@ -13,6 +13,7 @@ from app.db.session import get_session from app.db.models.user import User from app.schemas import TokenPayload +from app.tasks.task_manager import TaskManager, get_task_manager reusable_oauth2 = OAuth2PasswordBearer( tokenUrl=f"{settings.API_V1_STR}/login/access-token" @@ -21,6 +22,7 @@ SessionDep = Annotated[Session, Depends(get_session)] TokenDep = Annotated[str, Depends(reusable_oauth2)] +TaskManagerDep = Annotated[TaskManager, Depends(get_task_manager)] def get_current_user(session: SessionDep, token: TokenDep) -> User: diff --git a/backend/app/backend_pre_start.py b/backend/app/backend_pre_start.py index c2f8e29ae1..7a659ac097 100644 --- a/backend/app/backend_pre_start.py +++ b/backend/app/backend_pre_start.py @@ -1,10 +1,14 @@ import logging +import motor.motor_asyncio +import asyncio +from pymongo.errors import ConnectionFailure, ServerSelectionTimeoutError from sqlalchemy import Engine from sqlmodel import Session, select from tenacity import after_log, before_log, retry, stop_after_attempt, wait_fixed from app.core.db import engine +from app.core.config import settings logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -29,9 +33,50 @@ def init(db_engine: Engine) -> None: raise e +@retry( + stop=stop_after_attempt(max_tries), + wait=wait_fixed(wait_seconds), + before=before_log(logger, logging.INFO), + after=after_log(logger, logging.WARN), +) +async def init_mongodb() -> None: + """Check if MongoDB is awake and ready to accept connections.""" + try: + # Construct the MongoDB URI + auth_part = "" + if settings.MONGODB_USER and settings.MONGODB_PASSWORD: + auth_part = f"{settings.MONGODB_USER}:{settings.MONGODB_PASSWORD}@" + + auth_source = f"?authSource={settings.MONGODB_AUTH_SOURCE}" if auth_part else "" + mongo_uri = f"mongodb://{auth_part}{settings.MONGODB_SERVER}:{settings.MONGODB_PORT}/{settings.MONGODB_DB}{auth_source}" + + # Try to connect to MongoDB + client = motor.motor_asyncio.AsyncIOMotorClient( + mongo_uri, + serverSelectionTimeoutMS=5000 + ) + + # Check the connection + await client.admin.command('ping') + logger.info("MongoDB connection successful") + + # Close the connection + client.close() + except (ConnectionFailure, ServerSelectionTimeoutError) as e: + logger.error(f"MongoDB connection error: {e}") + raise e + + def main() -> None: logger.info("Initializing service") init(engine) + logger.info("PostgreSQL database initialization complete") + + # Check MongoDB connection + logger.info("Checking MongoDB connection...") + asyncio.run(init_mongodb()) + logger.info("MongoDB initialization complete") + logger.info("Service finished initializing") diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 1cbeb52ea9..18c0e78b29 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -39,6 +39,9 @@ class Settings(BaseSettings): FRONTEND_HOST: str = "http://localhost:5173" ENVIRONMENT: Literal["local", "staging", "production"] = "local" + # Feature flags + USE_REDIS: bool = False # Disabled in MVP, enable for post-MVP features + BACKEND_CORS_ORIGINS: Annotated[ list[AnyUrl] | str, BeforeValidator(parse_cors) ] = [] @@ -138,6 +141,17 @@ def celery_result_backend_uri(self) -> str: TRANSFORMER_MODEL_NAME: str = "distilbert-base-uncased" SENTENCE_TRANSFORMER_MODEL_NAME: str = "all-MiniLM-L6-v2" + # OpenAI settings + OPENAI_API_KEY: str = "" + OPENAI_MODEL: str = "text-embedding-3-small" + OPENAI_EMBEDDING_DIMENSION: int = 1536 # Dimension for text-embedding-3-small + + # APIFY settings (MVP) + APIFY_API_KEY: str = "" + + # Anthropic settings (MVP) + ANTHROPIC_API_KEY: str = "" + # Email settings SMTP_TLS: bool = True SMTP_SSL: bool = False @@ -178,6 +192,9 @@ def _enforce_non_default_secrets(self) -> Self: self._check_default_secret("MONGODB_PASSWORD", self.MONGODB_PASSWORD) self._check_default_secret("REDIS_PASSWORD", self.REDIS_PASSWORD) self._check_default_secret("PINECONE_API_KEY", self.PINECONE_API_KEY) + self._check_default_secret("OPENAI_API_KEY", self.OPENAI_API_KEY) + self._check_default_secret("APIFY_API_KEY", self.APIFY_API_KEY) + self._check_default_secret("ANTHROPIC_API_KEY", self.ANTHROPIC_API_KEY) self._check_default_secret("SECRET_KEY", self.SECRET_KEY) self._check_default_secret("FIRST_SUPERUSER_PASSWORD", self.FIRST_SUPERUSER_PASSWORD) return self diff --git a/backend/app/core/db.py b/backend/app/core/db.py index fa62988197..66a96139bd 100644 --- a/backend/app/core/db.py +++ b/backend/app/core/db.py @@ -33,5 +33,5 @@ def init_db(session: Session) -> None: password=settings.FIRST_SUPERUSER_PASSWORD, is_superuser=True, ) - # Updated to use the new repositorxy pattern - user = user_service.create_user(session=session, obj_in=user_in) + # Fixed parameter name to match the service function signature + user = user_service.create_user(session=session, user_create=user_in) diff --git a/backend/app/db/__init__.py b/backend/app/db/__init__.py new file mode 100644 index 0000000000..0519ecba6e --- /dev/null +++ b/backend/app/db/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/app/db/connections.py b/backend/app/db/connections.py index afb92c072b..f6e5f0f2e7 100644 --- a/backend/app/db/connections.py +++ b/backend/app/db/connections.py @@ -3,17 +3,19 @@ This module provides connection utilities for the hybrid database architecture used in the Political Social Media Analysis Platform. It implements connection management for: - MongoDB: For storing social media content and engagement data -- Redis: For caching and real-time operations +- Redis: For caching and real-time operations (not used in MVP) - Pinecone: For vector similarity search and semantic analysis """ from contextlib import asynccontextmanager -from typing import AsyncGenerator, Optional +from typing import AsyncGenerator, Optional, Union, Any from functools import lru_cache +import logging +import importlib +import sys import motor.motor_asyncio -import pinecone -from pinecone import Index +# Import pinecone dynamically only when needed import redis.asyncio as redis from pymongo.errors import ConnectionFailure, ServerSelectionTimeoutError from redis.exceptions import ConnectionError as RedisConnectionError @@ -22,6 +24,9 @@ from app.core.config import settings +logger = logging.getLogger(__name__) + + class MongoDBConnection: """MongoDB connection manager using motor for async operations.""" @@ -83,14 +88,26 @@ async def close(self) -> None: class RedisConnection: - """Redis connection manager for async operations.""" + """ + Redis connection manager for async operations. + + NOTE: Not used in MVP - This implementation is reserved for future releases. + Redis functionality is disabled in the MVP to simplify initial deployment. + """ def __init__(self) -> None: """Initialize Redis connection manager.""" self._client: Optional[redis.Redis] = None async def connect(self) -> None: - """Connect to Redis.""" + """ + Connect to Redis. + + If USE_REDIS is False, this will not actually establish a connection. + """ + if not settings.USE_REDIS: + return + try: self._client = redis.from_url( settings.REDIS_URI, @@ -106,7 +123,7 @@ async def connect(self) -> None: def client(self) -> redis.Redis: """Get the Redis client instance.""" if self._client is None: - raise ConnectionError("Redis connection not initialized") + raise ConnectionError("Redis connection not initialized or Redis is disabled in MVP") return self._client async def close(self) -> None: @@ -116,43 +133,151 @@ async def close(self) -> None: self._client = None +class MockRedisClient: + """Mock Redis client used when Redis is disabled (MVP).""" + + async def get(self, *args, **kwargs): + """Mock get that always returns None.""" + return None + + async def set(self, *args, **kwargs): + """Mock set that does nothing.""" + return True + + async def ping(self, *args, **kwargs): + """Mock ping that always succeeds.""" + return True + + # Add other mock methods as needed for the MVP + + async def close(self): + """Mock close method.""" + pass + + class PineconeConnection: """Pinecone connection manager for vector similarity search.""" def __init__(self) -> None: """Initialize Pinecone connection manager.""" - self._index: Optional[Index] = None + self._index = None + self._pinecone_client = None + self._available = False + self._api_version = None def connect(self) -> None: - """Initialize Pinecone connection and ensure index exists.""" + """ + Initialize Pinecone connection and ensure index exists. + + If Pinecone is not available or API key is not provided, + this will log a warning but not fail. + """ + if not settings.PINECONE_API_KEY: + logger.warning("Skipping Pinecone connection - API key not provided") + return + try: - # Initialize Pinecone client - pinecone.init(api_key=settings.PINECONE_API_KEY) + # Try to import pinecone dynamically to avoid module-level import issues + if 'pinecone' in sys.modules: + del sys.modules['pinecone'] + + pinecone_module = importlib.import_module('pinecone') - # Create index if it doesn't exist - if settings.PINECONE_INDEX_NAME not in pinecone.list_indexes(): - pinecone.create_index( - name=settings.PINECONE_INDEX_NAME, - dimension=384, # Dimension for all-MiniLM-L6-v2 embeddings - metric="cosine" - ) + # First try the new API (pinecone package) + try: + # Check if we have new Pinecone API (v6+) + if hasattr(pinecone_module, 'Pinecone'): + logger.info("Using Pinecone v6+ API") + self._api_version = "v6+" + Pinecone = getattr(pinecone_module, 'Pinecone') + ServerlessSpec = getattr(pinecone_module, 'ServerlessSpec', None) + + client = Pinecone(api_key=settings.PINECONE_API_KEY) + self._pinecone_client = client + + try: + # Try to get the index + self._index = client.Index(settings.PINECONE_INDEX_NAME) + self._available = True + logger.info(f"Connected to Pinecone index: {settings.PINECONE_INDEX_NAME}") + except Exception as e: + logger.info(f"Creating new Pinecone index: {settings.PINECONE_INDEX_NAME}") + # Create a new index + if ServerlessSpec: + client.create_index( + name=settings.PINECONE_INDEX_NAME, + dimension=settings.OPENAI_EMBEDDING_DIMENSION, + metric="cosine", + spec=ServerlessSpec(cloud="aws", region="us-east-1") + ) + else: + client.create_index( + name=settings.PINECONE_INDEX_NAME, + dimension=settings.OPENAI_EMBEDDING_DIMENSION, + metric="cosine" + ) + self._index = client.Index(settings.PINECONE_INDEX_NAME) + self._available = True + + # Fall back to old pinecone-client API + else: + logger.info("Using pinecone-client legacy API") + self._api_version = "legacy" + # Old API style using pinecone.init() + pinecone_module.init(api_key=settings.PINECONE_API_KEY, + environment=settings.PINECONE_ENVIRONMENT) + self._pinecone_client = pinecone_module + + # Create index if it doesn't exist + existing_indexes = pinecone_module.list_indexes() + if settings.PINECONE_INDEX_NAME not in existing_indexes: + logger.info(f"Creating new Pinecone index: {settings.PINECONE_INDEX_NAME}") + pinecone_module.create_index( + name=settings.PINECONE_INDEX_NAME, + dimension=settings.OPENAI_EMBEDDING_DIMENSION, + metric="cosine" + ) + + # Get the index + self._index = pinecone_module.Index(settings.PINECONE_INDEX_NAME) + self._available = True + logger.info(f"Connected to Pinecone index: {settings.PINECONE_INDEX_NAME}") - # Get the index - self._index = pinecone.Index(settings.PINECONE_INDEX_NAME) + except Exception as e: + logger.warning(f"Failed to initialize Pinecone: {str(e)}") + self._available = False + + except ImportError as e: + logger.warning(f"Pinecone package not available: {str(e)}") + self._available = False except Exception as e: - raise ConnectionError(f"Failed to initialize Pinecone: {e}") + logger.warning(f"Failed to initialize Pinecone: {str(e)}") + self._available = False @property def index(self): """Get the Pinecone index instance.""" - if self._index is None: - raise ConnectionError("Pinecone connection not initialized") + if not self._available or self._index is None: + logger.warning("Pinecone connection not initialized or not available") + return None return self._index + + @property + def available(self) -> bool: + """Check if Pinecone is available.""" + return self._available and self._index is not None + + @property + def api_version(self) -> Optional[str]: + """Get the Pinecone API version being used.""" + return self._api_version def close(self) -> None: """Clean up Pinecone resources.""" if self._index is not None: self._index = None + self._pinecone_client = None + self._available = False # Singleton instances @@ -173,8 +298,21 @@ async def get_mongodb() -> AsyncGenerator[motor.motor_asyncio.AsyncIOMotorDataba @asynccontextmanager -async def get_redis() -> AsyncGenerator[redis.Redis, None]: - """Async context manager for getting Redis client instance.""" +async def get_redis() -> AsyncGenerator[Union[redis.Redis, MockRedisClient], None]: + """ + Async context manager for getting Redis client instance. + + If Redis is disabled (MVP), returns a mock client that does nothing. + """ + if not settings.USE_REDIS: + # In MVP, return a mock client that does nothing + mock_client = MockRedisClient() + try: + yield mock_client + finally: + await mock_client.close() + return + if redis_conn._client is None: await redis_conn.connect() try: @@ -185,7 +323,12 @@ async def get_redis() -> AsyncGenerator[redis.Redis, None]: @lru_cache() def get_pinecone(): - """Get Pinecone index instance with caching.""" + """ + Get Pinecone index instance with caching. + + Returns None if Pinecone is not available or not initialized. + This function allows the application to work even when Pinecone is not properly configured. + """ if pinecone_conn._index is None: pinecone_conn.connect() return pinecone_conn.index @@ -194,5 +337,6 @@ def get_pinecone(): async def close_db_connections() -> None: """Close all database connections.""" await mongodb.close() - await redis_conn.close() + if settings.USE_REDIS: + await redis_conn.close() pinecone_conn.close() \ No newline at end of file diff --git a/backend/app/db/mongo_utils.py b/backend/app/db/mongo_utils.py new file mode 100644 index 0000000000..fd6943e125 --- /dev/null +++ b/backend/app/db/mongo_utils.py @@ -0,0 +1,59 @@ +import logging +from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase, AsyncIOMotorCollection +from app.core.config import settings +from typing import Optional + +logger = logging.getLogger(__name__) + +_mongo_client: Optional[AsyncIOMotorClient] = None + +async def get_mongo_client() -> AsyncIOMotorClient: + """ + Returns the MongoDB client instance, initializing it if necessary. + """ + global _mongo_client + if _mongo_client is None: + logger.info("Initializing MongoDB client...") + try: + _mongo_client = AsyncIOMotorClient(settings.MONGODB_URI) + # The ismaster command is cheap and does not require auth. + await _mongo_client.admin.command('ismaster') + logger.info("MongoDB client initialized successfully.") + except Exception as e: + logger.error(f"Failed to initialize MongoDB client: {e}") + _mongo_client = None # Reset on failure + raise + return _mongo_client + +async def close_mongo_connection(): + """ + Closes the MongoDB client connection if it exists. + """ + global _mongo_client + if _mongo_client: + logger.info("Closing MongoDB connection...") + _mongo_client.close() + _mongo_client = None + logger.info("MongoDB connection closed.") + +async def get_mongo_database() -> AsyncIOMotorDatabase: + """ + Returns the MongoDB database instance using the initialized client. + """ + client = await get_mongo_client() + if not settings.MONGODB_DB_NAME: + raise ValueError("MONGODB_DB_NAME must be set in settings") + return client[settings.MONGODB_DB_NAME] + +async def get_mongo_collection(collection_name: str) -> AsyncIOMotorCollection: + """ + Returns a specific MongoDB collection instance from the database. + + Args: + collection_name: The name of the collection to retrieve. + + Returns: + An instance of AsyncIOMotorCollection. + """ + db = await get_mongo_database() + return db[collection_name] \ No newline at end of file diff --git a/backend/app/db/schemas/__init__.py b/backend/app/db/schemas/__init__.py deleted file mode 100644 index 666d98aba9..0000000000 --- a/backend/app/db/schemas/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from app.db.schemas.political_entity import EntityAnalytics, PoliticalEntity -from app.db.schemas.social_post import SocialAnalytics, SocialPost - -__all__ = ["SocialPost", "SocialAnalytics", "PoliticalEntity", "EntityAnalytics"] \ No newline at end of file diff --git a/backend/app/db/schemas/mongodb.py b/backend/app/db/schemas/mongodb.py index 14f4b9fd90..6f8164279d 100644 --- a/backend/app/db/schemas/mongodb.py +++ b/backend/app/db/schemas/mongodb.py @@ -7,7 +7,7 @@ """ from datetime import datetime -from typing import List, Optional, Dict, Any +from typing import List, Optional, Dict, Any, Union from uuid import UUID from pydantic import BaseModel, Field, HttpUrl @@ -24,33 +24,93 @@ class PostContent(BaseModel): class Config: schema_extra = { "example": { - "text": "Excited to announce our new policy on #ClimateChange with @EPA", - "media": ["https://example.com/image1.jpg", "https://example.com/image2.jpg"], - "links": ["https://example.com/policy"], - "hashtags": ["ClimateChange", "GreenFuture"], - "mentions": ["EPA", "WhiteHouse"] + "text": "Hoy tuve el honor de participar en la inauguración del Foro de Alianzas para el Hábitat capítulo Monterrey, un espacio clave para construir la ciudad del futuro.", + "media": ["https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481585399_18485585482052530_5292288465090866871_n.jpg"], + "links": [], + "hashtags": ["AquíSeResuelve"], + "mentions": [] } } +class Dimensions(BaseModel): + """Image or video dimensions.""" + height: int + width: int + + +class LocationInfo(BaseModel): + """Location information for social media posts.""" + name: Optional[str] = None + id: Optional[str] = None + country: Optional[str] = None + state: Optional[str] = None + city: Optional[str] = None + + +class Owner(BaseModel): + """Account owner information.""" + username: str + id: str + verified: bool = False + + +class TaggedUser(BaseModel): + """User tagged in a post.""" + username: str + id: str + full_name: Optional[str] = None + is_verified: bool = False + + class PostMetadata(BaseModel): """Metadata sub-schema for social media posts.""" created_at: datetime language: str - location: Optional[Dict[str, Any]] = None + location: Optional[LocationInfo] = None client: Optional[str] = None is_repost: bool = False is_reply: bool = False + dimensions: Optional[Dimensions] = None + alt_text: Optional[str] = None + product_type: Optional[str] = None + owner: Optional[Owner] = None + tagged_users: List[TaggedUser] = [] class Config: schema_extra = { "example": { - "created_at": "2023-06-15T14:32:19Z", - "language": "en", - "location": {"country": "USA", "state": "DC"}, - "client": "Twitter Web App", + "created_at": "2025-02-26T20:35:33.000Z", + "language": "es", + "location": { + "name": "U-ERRE Universidad Regiomontana", + "id": "1954214947989485", + "country": "Mexico", + "state": "Nuevo León", + "city": "Monterrey" + }, + "client": "Instagram Web", "is_repost": False, - "is_reply": False + "is_reply": False, + "dimensions": { + "height": 717, + "width": 1080 + }, + "alt_text": "Photo by Adrián de la Garza on February 26, 2025. May be an image of 10 people and text.", + "product_type": None, + "owner": { + "username": "adriandelagarzas", + "id": "1483444529", + "verified": True + }, + "tagged_users": [ + { + "username": "userexample", + "id": "123456789", + "full_name": "User Example", + "is_verified": False + } + ] } } @@ -58,19 +118,21 @@ class Config: class PostEngagement(BaseModel): """Engagement metrics sub-schema for social media posts.""" likes_count: int = 0 - shares_count: int = 0 + shares_count: Optional[int] = None comments_count: int = 0 views_count: Optional[int] = None engagement_rate: Optional[float] = None + saves_count: Optional[int] = None class Config: schema_extra = { "example": { - "likes_count": 1245, - "shares_count": 327, - "comments_count": 89, - "views_count": 15720, - "engagement_rate": 3.8 + "likes_count": 153, + "shares_count": None, + "comments_count": 16, + "views_count": None, + "engagement_rate": 0.97, + "saves_count": None } } @@ -86,15 +148,33 @@ class PostAnalysis(BaseModel): class Config: schema_extra = { "example": { - "sentiment_score": 0.64, - "topics": ["climate", "environment", "policy"], - "entities_mentioned": ["EPA", "Climate Change Initiative"], - "key_phrases": ["new policy", "climate action", "sustainable future"], - "emotional_tone": "positive" + "sentiment_score": None, + "topics": [], + "entities_mentioned": [], + "key_phrases": [], + "emotional_tone": None } } +class ChildPost(BaseModel): + """Child post in a carousel/sidecar post.""" + id: str + type: str # Image, Video + url: Optional[HttpUrl] = None + display_url: HttpUrl + dimensions: Optional[Dimensions] = None + alt_text: Optional[str] = None + + +class VideoData(BaseModel): + """Video-specific data for video posts.""" + duration: Optional[float] = None # In seconds + video_url: Optional[HttpUrl] = None + thumbnail_url: Optional[HttpUrl] = None + is_muted: bool = False + + class SocialMediaPost(BaseModel): """ Schema for social media posts stored in MongoDB. @@ -105,51 +185,102 @@ class SocialMediaPost(BaseModel): platform_id: str = Field(..., description="Original ID from the social media platform") platform: str = Field(..., description="Social media platform name (e.g., twitter, facebook)") account_id: UUID = Field(..., description="Reference to PostgreSQL SocialMediaAccount UUID") - content_type: str = Field(..., description="Type of post (e.g., post, story, video)") + content_type: str = Field(..., description="Type of post (e.g., post, sidecar, video)") + short_code: Optional[str] = Field(None, description="Platform shortcode for URL (e.g., Instagram)") + url: Optional[HttpUrl] = Field(None, description="Direct URL to the post") content: PostContent metadata: PostMetadata engagement: PostEngagement analysis: Optional[PostAnalysis] = None + child_posts: Optional[List[ChildPost]] = None + video_data: Optional[VideoData] = None vector_id: Optional[str] = Field(None, description="Reference to vector database entry") class Config: schema_extra = { "example": { - "platform_id": "1458794356725891073", - "platform": "twitter", + "platform_id": "3576752389826611363", + "platform": "instagram", "account_id": "123e4567-e89b-12d3-a456-426614174000", - "content_type": "post", + "content_type": "sidecar", + "short_code": "DGjLVkdJQij", + "url": "https://www.instagram.com/p/DGjLVkdJQij/", "content": { - "text": "Excited to announce our new policy on #ClimateChange with @EPA", - "media": ["https://example.com/image1.jpg"], - "links": ["https://example.com/policy"], - "hashtags": ["ClimateChange", "GreenFuture"], - "mentions": ["EPA", "WhiteHouse"] + "text": "Hoy tuve el honor de participar en la inauguración del Foro de Alianzas para el Hábitat capítulo Monterrey, un espacio clave para construir la ciudad del futuro.", + "media": ["https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481585399_18485585482052530_5292288465090866871_n.jpg"], + "links": [], + "hashtags": ["AquíSeResuelve"], + "mentions": [] }, "metadata": { - "created_at": "2023-06-15T14:32:19Z", - "language": "en", - "location": {"country": "USA", "state": "DC"}, - "client": "Twitter Web App", + "created_at": "2025-02-26T20:35:33.000Z", + "language": "es", + "location": { + "name": "U-ERRE Universidad Regiomontana", + "id": "1954214947989485", + "country": "Mexico", + "state": "Nuevo León", + "city": "Monterrey" + }, + "client": "Instagram Web", "is_repost": False, - "is_reply": False + "is_reply": False, + "dimensions": { + "height": 717, + "width": 1080 + }, + "alt_text": "Photo by Adrián de la Garza on February 26, 2025. May be an image of 10 people and text.", + "product_type": None, + "owner": { + "username": "adriandelagarzas", + "id": "1483444529", + "verified": True + }, + "tagged_users": [ + { + "username": "userexample", + "id": "123456789", + "full_name": "User Example", + "is_verified": False + } + ] }, "engagement": { - "likes_count": 1245, - "shares_count": 327, - "comments_count": 89, - "views_count": 15720, - "engagement_rate": 3.8 + "likes_count": 153, + "shares_count": None, + "comments_count": 16, + "views_count": None, + "engagement_rate": 0.97, + "saves_count": None }, + "child_posts": [ + { + "id": "3576752376539157068", + "type": "Image", + "url": "https://www.instagram.com/p/DGjLVYFJpJM/", + "display_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481585399_18485585482052530_5292288465090866871_n.jpg", + "dimensions": { + "height": 717, + "width": 1080 + }, + "alt_text": "Photo by Adrián de la Garza on February 26, 2025." + } + ], "analysis": { - "sentiment_score": 0.64, - "topics": ["climate", "environment", "policy"], - "entities_mentioned": ["EPA", "Climate Change Initiative"], - "key_phrases": ["new policy", "climate action", "sustainable future"], - "emotional_tone": "positive" + "sentiment_score": None, + "topics": [], + "entities_mentioned": [], + "key_phrases": [], + "emotional_tone": None + }, + "video_data": { + "duration": None, + "video_url": None, + "thumbnail_url": None, + "is_muted": False }, - "vector_id": "vec_123456789" + "vector_id": None } } @@ -157,15 +288,15 @@ class Config: class CommentContent(BaseModel): """Content sub-schema for social media comments.""" text: str - media: Optional[List[HttpUrl]] = None + media: List[HttpUrl] = [] mentions: List[str] = [] class Config: schema_extra = { "example": { - "text": "Great initiative! @GreenOrg should partner on this", - "media": ["https://example.com/comment-img.jpg"], - "mentions": ["GreenOrg"] + "text": "👏👏", + "media": [], + "mentions": [] } } @@ -175,13 +306,17 @@ class CommentMetadata(BaseModel): created_at: datetime language: str location: Optional[Dict[str, Any]] = None + is_reply: bool = False + parent_comment_id: Optional[str] = None class Config: schema_extra = { "example": { - "created_at": "2023-06-15T15:45:22Z", - "language": "en", - "location": {"country": "USA", "state": "CA"} + "created_at": "2025-02-27T01:31:50.000Z", + "language": "es", + "location": None, + "is_reply": False, + "parent_comment_id": None } } @@ -194,8 +329,8 @@ class CommentEngagement(BaseModel): class Config: schema_extra = { "example": { - "likes_count": 45, - "replies_count": 3 + "likes_count": 2, + "replies_count": 1 } } @@ -206,14 +341,66 @@ class CommentAnalysis(BaseModel): emotional_tone: Optional[str] = None toxicity_flag: Optional[bool] = None entities_mentioned: List[str] = [] + language_detected: Optional[str] = None + contains_question: bool = False class Config: schema_extra = { "example": { - "sentiment_score": 0.78, - "emotional_tone": "positive", + "sentiment_score": None, + "emotional_tone": None, "toxicity_flag": False, - "entities_mentioned": ["GreenOrg"] + "entities_mentioned": [], + "language_detected": None, + "contains_question": False + } + } + + +class CommentUserDetails(BaseModel): + """Additional user information for comment authors.""" + fbid_v2: Optional[int] = None + is_mentionable: Optional[bool] = None + latest_reel_media: Optional[int] = None + profile_pic_id: Optional[str] = None + + class Config: + schema_extra = { + "example": { + "fbid_v2": 17841415278525780, + "is_mentionable": True, + "latest_reel_media": 0, + "profile_pic_id": "3422370971825093335" + } + } + + +class CommentReply(BaseModel): + """Reply to a comment.""" + platform_id: str + user_id: str + user_name: str + user_full_name: Optional[str] = None + user_profile_pic: Optional[HttpUrl] = None + user_verified: bool = False + text: str + created_at: datetime + likes_count: int = 0 + replies_count: int = 0 + + class Config: + schema_extra = { + "example": { + "platform_id": "17917498377065887", + "user_id": "1483444529", + "user_name": "adriandelagarzas", + "user_full_name": "Adrián de la Garza", + "user_profile_pic": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg", + "user_verified": True, + "text": "@oscarcuellocoronado 🙌🏼😄", + "created_at": "2025-02-27T01:39:10.000Z", + "likes_count": 0, + "replies_count": 0 } } @@ -223,48 +410,248 @@ class SocialMediaComment(BaseModel): Schema for social media comments stored in MongoDB. This model represents a comment on a social media post including - its content, metadata, engagement metrics, and analysis. + its content, metadata, engagement metrics, replies, and analysis. """ platform_id: str = Field(..., description="Original ID from the social media platform") platform: str = Field(..., description="Social media platform name (e.g., twitter, facebook)") post_id: str = Field(..., description="Reference to MongoDB post ID") - user_id: str = Field(..., description="User ID from the platform") - user_name: str = Field(..., description="User name from the platform") + post_url: Optional[HttpUrl] = Field(None, description="URL of the original post") + + user_id: str = Field(..., description="ID of the commenter") + user_name: str = Field(..., description="Username of the commenter") + user_full_name: Optional[str] = Field(None, description="Full display name of the commenter") + user_profile_pic: Optional[HttpUrl] = Field(None, description="Profile picture URL of the commenter") + user_verified: bool = Field(False, description="Whether the user has a verification badge") + user_private: bool = Field(False, description="Whether the user's account is private") content: CommentContent metadata: CommentMetadata engagement: CommentEngagement + + replies: List[CommentReply] = [] + analysis: Optional[CommentAnalysis] = None + user_details: Optional[CommentUserDetails] = None vector_id: Optional[str] = Field(None, description="Reference to vector database entry") class Config: schema_extra = { "example": { - "platform_id": "1458812639457283072", - "platform": "twitter", - "post_id": "1458794356725891073", - "user_id": "987654321", - "user_name": "EcoAdvocate", + "platform_id": "18021118748474094", + "platform": "instagram", + "post_id": "3576752389826611363", + "post_url": "https://www.instagram.com/p/DGjLVkdJQij/", + + "user_id": "15166237284", + "user_name": "oscarcuellocoronado", + "user_full_name": "Oscar Cuello", + "user_profile_pic": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/453036863_994371468987224_3689516965341685068_n.jpg", + "user_verified": False, + "user_private": False, + "content": { - "text": "Great initiative! @GreenOrg should partner on this", - "media": ["https://example.com/comment-img.jpg"], - "mentions": ["GreenOrg"] + "text": "👏👏", + "media": [], + "mentions": [] }, + "metadata": { - "created_at": "2023-06-15T15:45:22Z", - "language": "en", - "location": {"country": "USA", "state": "CA"} + "created_at": "2025-02-27T01:31:50.000Z", + "language": "es", + "location": None, + "is_reply": False, + "parent_comment_id": None }, + "engagement": { - "likes_count": 45, - "replies_count": 3 + "likes_count": 2, + "replies_count": 1 }, + + "replies": [ + { + "platform_id": "17917498377065887", + "user_id": "1483444529", + "user_name": "adriandelagarzas", + "user_full_name": "Adrián de la Garza", + "user_profile_pic": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg", + "user_verified": True, + "text": "@oscarcuellocoronado 🙌🏼😄", + "created_at": "2025-02-27T01:39:10.000Z", + "likes_count": 0, + "replies_count": 0 + } + ], + "analysis": { - "sentiment_score": 0.78, - "emotional_tone": "positive", + "sentiment_score": None, + "emotional_tone": None, "toxicity_flag": False, - "entities_mentioned": ["GreenOrg"] + "entities_mentioned": [], + "language_detected": None, + "contains_question": False + }, + + "user_details": { + "fbid_v2": 17841415278525780, + "is_mentionable": True, + "latest_reel_media": 0, + "profile_pic_id": "3422370971825093335" + }, + + "vector_id": None + } + } + + +class TopicAnalysis(BaseModel): + """ + Schema for topic analysis data stored in MongoDB. + + This model represents a topic that can be analyzed across social media content, + including its definition, related keywords, and categorization. + """ + topic_id: str = Field(..., description="Unique identifier for the topic") + name: str = Field(..., description="Descriptive name of the topic") + keywords: List[str] = Field(..., description="List of related keywords or phrases") + description: Optional[str] = Field(None, description="Optional explanation of the topic") + category: str = Field(..., description="Broader category the topic belongs to (e.g., Economy, Healthcare)") + created_at: datetime = Field(default_factory=datetime.utcnow, description="Timestamp when the topic was created") + updated_at: datetime = Field(default_factory=datetime.utcnow, description="Timestamp when the topic was last updated") + + class Config: + schema_extra = { + "example": { + "topic_id": "climate_change_2023", + "name": "Climate Change Policy", + "keywords": ["global warming", "carbon emissions", "climate crisis", "net zero", "paris agreement"], + "description": "Political discourse related to climate change policy measures and initiatives", + "category": "Environment", + "created_at": "2023-06-01T12:00:00Z", + "updated_at": "2023-06-15T14:30:00Z" + } + } + + +class TopicOccurrence(BaseModel): + """ + Schema for tracking where topics occur in social media content. + + This model represents an instance where a specific topic was detected + in a post or comment, including detection confidence and sentiment context. + """ + topic_id: str = Field(..., description="Reference to TopicAnalysis topic_id") + content_id: str = Field(..., description="Reference to post or comment where topic was detected") + content_type: str = Field(..., description="Type of content (post or comment)") + confidence_score: float = Field(..., description="Confidence score for topic detection (0.0-1.0)") + sentiment_context: Optional[float] = Field(None, description="Sentiment score specifically for this topic in this content") + detected_at: datetime = Field(default_factory=datetime.utcnow, description="When the topic was identified in this content") + relevant_text_segment: Optional[str] = Field(None, description="The text segment where the topic was identified") + + class Config: + schema_extra = { + "example": { + "topic_id": "climate_change_2023", + "content_id": "60d5ec7a8f3a7c9a1b3e4f5a", + "content_type": "post", + "confidence_score": 0.87, + "sentiment_context": 0.32, + "detected_at": "2023-06-15T15:22:13Z", + "relevant_text_segment": "Our new policy aims to address the climate crisis through significant carbon reduction measures." + } + } + + +class EntityTopicBreakdown(BaseModel): + """Sub-model for entity breakdown in topic trends.""" + entity_id: UUID = Field(..., description="Reference to PoliticalEntity UUID") + entity_name: str = Field(..., description="Name of the political entity") + mention_count: int = Field(..., description="Number of times the entity mentioned this topic") + sentiment_average: Optional[float] = Field(None, description="Average sentiment for this entity on this topic") + + class Config: + schema_extra = { + "example": { + "entity_id": "123e4567-e89b-12d3-a456-426614174000", + "entity_name": "Senator Smith", + "mention_count": 15, + "sentiment_average": 0.45 + } + } + + +class PlatformTopicBreakdown(BaseModel): + """Sub-model for platform breakdown in topic trends.""" + platform: str = Field(..., description="Name of the social media platform") + mention_count: int = Field(..., description="Number of mentions on this platform") + engagement_total: int = Field(..., description="Total engagement count on this platform") + + class Config: + schema_extra = { + "example": { + "platform": "twitter", + "mention_count": 45, + "engagement_total": 12500 + } + } + + +class TopicTrend(BaseModel): + """ + Schema for topic trends analysis over time periods. + + This model represents aggregated data about topic mentions and engagement + over specific time periods, including breakdowns by entity and platform. + """ + topic_id: str = Field(..., description="Reference to TopicAnalysis topic_id") + time_period: str = Field(..., description="Time period for analysis (day, week, month)") + start_date: datetime = Field(..., description="Start date of the time period") + end_date: datetime = Field(..., description="End date of the time period") + frequency: int = Field(..., description="Number of occurrences during time period") + sentiment_average: Optional[float] = Field(None, description="Average sentiment across occurrences") + engagement_metrics: Dict[str, int] = Field(..., description="Engagement metrics related to this topic") + entity_breakdown: List[EntityTopicBreakdown] = Field(default_factory=list, description="Breakdown by political entity") + platform_breakdown: List[PlatformTopicBreakdown] = Field(default_factory=list, description="Breakdown by platform") + + class Config: + schema_extra = { + "example": { + "topic_id": "climate_change_2023", + "time_period": "week", + "start_date": "2023-06-01T00:00:00Z", + "end_date": "2023-06-07T23:59:59Z", + "frequency": 187, + "sentiment_average": 0.28, + "engagement_metrics": { + "likes": 3450, + "shares": 876, + "comments": 532 }, - "vector_id": "vec_987654321" + "entity_breakdown": [ + { + "entity_id": "123e4567-e89b-12d3-a456-426614174000", + "entity_name": "Senator Smith", + "mention_count": 15, + "sentiment_average": 0.45 + }, + { + "entity_id": "223e4567-e89b-12d3-a456-426614174001", + "entity_name": "EPA", + "mention_count": 23, + "sentiment_average": 0.12 + } + ], + "platform_breakdown": [ + { + "platform": "twitter", + "mention_count": 45, + "engagement_total": 12500 + }, + { + "platform": "facebook", + "mention_count": 36, + "engagement_total": 8750 + } + ] } } \ No newline at end of file diff --git a/backend/app/db/schemas/political_entity.py b/backend/app/db/schemas/political_entity.py deleted file mode 100644 index 85cf177c3a..0000000000 --- a/backend/app/db/schemas/political_entity.py +++ /dev/null @@ -1,53 +0,0 @@ -from datetime import datetime -from typing import List, Optional, Any - -from bson import ObjectId -from pydantic import BaseModel, Field - -from app.db.schemas.social_post import PyObjectId - - -class PoliticalEntity(BaseModel): - """ - Schema for political entities stored in MongoDB. - - This model represents a political entity such as a politician, political party, or organization. - """ - id: PyObjectId = Field(default_factory=lambda: str(ObjectId()), alias="_id") - name: str - entity_type: str # "politician", "party", "organization", etc. - description: Optional[str] = None - country: str - social_accounts: List[dict[str, str]] = [] # List of {platform: string, username: string} - political_stance: Optional[str] = None - tags: List[str] = [] - related_entities: List[PyObjectId] = [] - metadata: Optional[dict[str, Any]] = None - created_at: datetime = Field(default_factory=datetime.utcnow) - updated_at: datetime = Field(default_factory=datetime.utcnow) - - class Config: - populate_by_name = True - arbitrary_types_allowed = True - json_encoders = {ObjectId: str} - - -class EntityAnalytics(BaseModel): - """ - Schema for entity analytics stored in MongoDB. - - This model represents analytics for political entities. - """ - id: PyObjectId = Field(default_factory=lambda: str(ObjectId()), alias="_id") - entity_id: PyObjectId - total_mentions: int = 0 - sentiment_distribution: dict[str, float] = {} # e.g., {"positive": 0.3, "neutral": 0.5, "negative": 0.2} - engagement_metrics: dict[str, int] = {} # e.g., {"comments": 1000, "likes": 5000, "shares": 2000} - trending_topics: List[dict[str, Any]] = [] # List of {topic: string, count: number, sentiment: number} - time_period: str # e.g., "last_24h", "last_week", "last_month" - analysis_timestamp: datetime = Field(default_factory=datetime.utcnow) - - class Config: - populate_by_name = True - arbitrary_types_allowed = True - json_encoders = {ObjectId: str} \ No newline at end of file diff --git a/backend/app/db/schemas/redis_schemas.py b/backend/app/db/schemas/redis_schemas.py index bc66220765..260f80392a 100644 --- a/backend/app/db/schemas/redis_schemas.py +++ b/backend/app/db/schemas/redis_schemas.py @@ -3,6 +3,11 @@ This module defines the key patterns and data structures used for Redis caching and real-time operations in the Political Social Media Analysis Platform. + +NOTE: NOT USED IN MVP - This implementation is reserved for future releases. +Redis functionality is disabled in the MVP to simplify initial deployment. +When activating Redis, ensure the appropriate dependencies are installed +and the feature flags are enabled in the application configuration. """ from enum import Enum diff --git a/backend/app/db/schemas/social_post.py b/backend/app/db/schemas/social_post.py deleted file mode 100644 index 7f15e0a6e9..0000000000 --- a/backend/app/db/schemas/social_post.py +++ /dev/null @@ -1,66 +0,0 @@ -from datetime import datetime -from typing import List, Optional, Any - -from bson import ObjectId -from pydantic import BaseModel, Field - - -class PyObjectId(str): - """Custom type for handling MongoDB ObjectId.""" - @classmethod - def __get_validators__(cls): - yield cls.validate - - @classmethod - def validate(cls, v): - if not ObjectId.is_valid(v): - raise ValueError(f"Invalid ObjectId: {v}") - return str(v) - - -class SocialPost(BaseModel): - """ - Schema for social media posts stored in MongoDB. - - This model represents a social media post from various platforms. - """ - id: PyObjectId = Field(default_factory=lambda: str(ObjectId()), alias="_id") - platform: str - content: str - author: str - author_username: str - published_at: datetime - likes: int = 0 - shares: int = 0 - comments: int = 0 - url: Optional[str] = None - media_urls: List[str] = [] - hashtags: List[str] = [] - mentions: List[str] = [] - metadata: Optional[dict[str, Any]] = None - - class Config: - populate_by_name = True - arbitrary_types_allowed = True - json_encoders = {ObjectId: str} - - -class SocialAnalytics(BaseModel): - """ - Schema for social media analytics stored in MongoDB. - - This model represents analytics for social media data. - """ - id: PyObjectId = Field(default_factory=lambda: str(ObjectId()), alias="_id") - post_id: PyObjectId - sentiment_score: float - topic_classification: List[str] = [] - engagement_rate: float - political_leaning: Optional[str] = None - key_entities: List[str] = [] - analysis_timestamp: datetime = Field(default_factory=datetime.utcnow) - - class Config: - populate_by_name = True - arbitrary_types_allowed = True - json_encoders = {ObjectId: str} \ No newline at end of file diff --git a/backend/app/main.py b/backend/app/main.py index cb7c77f2bd..354c12564f 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,3 +1,11 @@ +# Remove temporary debug prints +# import sys +# import os +# print("--- Debug Info Start ---") +# print(f"Current working directory: {os.getcwd()}") +# print(f"sys.path: {sys.path}") +# print("--- Debug Info End ---") + import logging import sentry_sdk from fastapi import FastAPI @@ -8,8 +16,9 @@ from app.api.api_v1.api import api_router from app.core.config import settings from app.core.errors import add_exception_handlers -from app.db.connections import mongodb, redis_conn, pinecone_conn +from app.db.connections import pinecone_conn from app.schemas import StandardResponse +from app.db.mongo_utils import get_mongo_client, close_mongo_connection # Configure logging logging.basicConfig(level=logging.INFO) @@ -60,7 +69,7 @@ async def root(): @app.get("/health", response_model=StandardResponse[Dict[str, str]]) async def health_check(): - """Enhanced health check endpoint that tests all database connections.""" + """Enhanced health check endpoint that tests database connections (MVP).""" health_status = {} all_healthy = True @@ -69,11 +78,6 @@ async def health_check(): health_status["mongodb"] = "healthy" if mongodb_status["connected"] else "unhealthy" all_healthy = all_healthy and mongodb_status["connected"] - # Check Redis - redis_status = await check_redis() - health_status["redis"] = "healthy" if redis_status["connected"] else "unhealthy" - all_healthy = all_healthy and redis_status["connected"] - # Check Pinecone if configured if settings.PINECONE_API_KEY: pinecone_status = check_pinecone() @@ -94,29 +98,38 @@ async def health_check(): async def check_mongodb() -> Dict[str, bool]: """Test MongoDB connection.""" try: - await mongodb.client.admin.command('ping') + # Use the new utility function to get the client + client = await get_mongo_client() + await client.admin.command('ping') return {"connected": True} except Exception as e: logger.error(f"MongoDB health check failed: {e}") return {"connected": False, "error": str(e)} -async def check_redis() -> Dict[str, bool]: - """Test Redis connection.""" - try: - await redis_conn.client.ping() - return {"connected": True} - except Exception as e: - logger.error(f"Redis health check failed: {e}") - return {"connected": False, "error": str(e)} - def check_pinecone() -> Dict[str, bool]: """Test Pinecone connection.""" try: - # Pinecone doesn't have a ping method, so we'll check if the index is initialized - if pinecone_conn._index is not None: - # Try to fetch index stats as a connection test - pinecone_conn.index.describe_index_stats() - return {"connected": True} + # First check if Pinecone is available + logger.info(f"Pinecone available: {pinecone_conn.available}, API version: {pinecone_conn.api_version}") + if not pinecone_conn.available: + api_version = pinecone_conn.api_version or "unknown" + return {"connected": False, "error": f"Pinecone not available (API version: {api_version})"} + + # Consider it connected if available and index is initialized (even if stats can't be fetched) + logger.info(f"Pinecone index initialized: {pinecone_conn.index is not None}") + if pinecone_conn.index: + try: + logger.info("Attempting to fetch Pinecone index stats") + stats = pinecone_conn.index.describe_index_stats() + logger.info(f"Successfully fetched Pinecone index stats: {stats}") + except Exception as e: + logger.warning(f"Failed to fetch Pinecone index stats, but connection appears to be established: {str(e)}") + # Still consider it connected if the index exists but stats can't be fetched + + # Return connected if we reached this point + return {"connected": True, "api_version": pinecone_conn.api_version} + + logger.warning("Pinecone index not initialized") return {"connected": False, "error": "Index not initialized"} except Exception as e: logger.error(f"Pinecone health check failed: {e}") @@ -127,34 +140,32 @@ async def startup_db_client() -> None: """Initialize database connections on application startup.""" # MongoDB connection try: - logger.info("Connecting to MongoDB...") - await mongodb.connect() - logger.info("Successfully connected to MongoDB") + logger.info("Initializing MongoDB connection...") + # Use the new utility function + await get_mongo_client() + # Assuming the utility handles logging success/failure internally now + # logger.info("Successfully connected to MongoDB") # Removed as mongo_utils logs this except Exception as e: - logger.warning(f"Failed to connect to MongoDB: {e}") + # The utility function logs the error, just log a warning here + logger.warning(f"Failed to initialize MongoDB connection during startup: {e}") # Continue without raising the exception - # Redis connection + # Pinecone connection (optional) try: - logger.info("Connecting to Redis...") - await redis_conn.connect() - logger.info("Successfully connected to Redis") + logger.info("Connecting to Pinecone...") + pinecone_conn.connect() + + if pinecone_conn.available: + logger.info(f"Successfully connected to Pinecone (API version: {pinecone_conn.api_version})") + else: + if settings.PINECONE_API_KEY: + logger.warning("Pinecone connection failed despite API key being provided") + else: + logger.warning("Skipping Pinecone connection - API key not provided") except Exception as e: - logger.warning(f"Failed to connect to Redis: {e}") + logger.warning(f"Failed to connect to Pinecone: {e}") # Continue without raising the exception - # Pinecone connection (optional) - if settings.PINECONE_API_KEY: - try: - logger.info("Connecting to Pinecone...") - pinecone_conn.connect() - logger.info("Successfully connected to Pinecone") - except Exception as e: - logger.warning(f"Failed to connect to Pinecone: {e}") - # Continue without raising the exception - else: - logger.warning("Skipping Pinecone connection - API key not provided") - @app.on_event("shutdown") async def shutdown_db_client() -> None: @@ -163,20 +174,17 @@ async def shutdown_db_client() -> None: # Close connections in reverse order of initialization # Close Pinecone (sync) - if it was initialized - if settings.PINECONE_API_KEY: + if settings.PINECONE_API_KEY and pinecone_conn.available: # Added check for availability logger.info("Closing Pinecone connection...") pinecone_conn.close() logger.info("Pinecone connection closed") - # Close Redis (async) - logger.info("Closing Redis connection...") - await redis_conn.close() - logger.info("Redis connection closed") - # Close MongoDB (async) - logger.info("Closing MongoDB connection...") - await mongodb.close() - logger.info("MongoDB connection closed") + # Use the new utility function + await close_mongo_connection() + # logger.info("Closing MongoDB connection...") # Removed as mongo_utils logs this + # await mongodb.close() # Removed old call + # logger.info("MongoDB connection closed") # Removed as mongo_utils logs this except Exception as e: logger.error(f"Error during database shutdown: {e}") diff --git a/backend/app/processing/collection/__init__.py b/backend/app/processing/collection/__init__.py new file mode 100644 index 0000000000..330f9acd63 --- /dev/null +++ b/backend/app/processing/collection/__init__.py @@ -0,0 +1,20 @@ +""" +Social Media Collection Package + +This package provides classes for collecting data from social media platforms +using APIFY actors. +""" + +from app.processing.collection.base import BaseCollector +from app.processing.collection.twitter import TwitterCollector +from app.processing.collection.facebook import FacebookCollector +from app.processing.collection.instagram import InstagramCollector +from app.processing.collection.factory import CollectorFactory + +__all__ = [ + "BaseCollector", + "TwitterCollector", + "FacebookCollector", + "InstagramCollector", + "CollectorFactory" +] \ No newline at end of file diff --git a/backend/app/processing/collection/apify_client.py b/backend/app/processing/collection/apify_client.py new file mode 100644 index 0000000000..d17f5ae70e --- /dev/null +++ b/backend/app/processing/collection/apify_client.py @@ -0,0 +1,306 @@ +""" +APIFY API Client + +This module provides a client for interacting with APIFY API to scrape social media platforms. +""" + +import asyncio +import json +import logging +import time +from typing import Any, Dict, List, Optional, Union + +import aiohttp +from fastapi import HTTPException + +from app.core.config import settings + +logger = logging.getLogger(__name__) + + +class ApifyClient: + """ + Client for interacting with the APIFY API. + + This class provides methods for starting, monitoring, and retrieving results + from APIFY actors. It includes error handling, retries, and rate limiting. + """ + + def __init__(self): + """Initialize the APIFY client with API key from settings.""" + self.api_key = settings.APIFY_API_KEY + self.base_url = "https://api.apify.com/v2" + self.default_headers = { + "Authorization": f"Bearer {self.api_key}", + "Content-Type": "application/json" + } + self.last_request_time = 0 + self.min_request_interval = settings.SCRAPING_MIN_REQUEST_INTERVAL + + async def _enforce_rate_limit(self) -> None: + """Enforce rate limiting between API requests.""" + current_time = time.time() + elapsed = current_time - self.last_request_time + + if elapsed < self.min_request_interval: + await asyncio.sleep(self.min_request_interval - elapsed) + + self.last_request_time = time.time() + + async def _make_request( + self, + method: str, + endpoint: str, + data: Optional[Dict[str, Any]] = None, + params: Optional[Dict[str, Any]] = None, + retries: int = 3, + retry_delay: int = 2 + ) -> Dict[str, Any]: + """ + Make an HTTP request to the APIFY API with retry logic. + + Args: + method: HTTP method (GET, POST, DELETE) + endpoint: API endpoint (relative to base URL) + data: Request data for POST requests + params: Query parameters + retries: Number of retry attempts + retry_delay: Delay between retries in seconds + + Returns: + Parsed JSON response + + Raises: + HTTPException: If the request fails after all retries + """ + url = f"{self.base_url}{endpoint}" + + # Enforce rate limiting + await self._enforce_rate_limit() + + for attempt in range(retries): + try: + async with aiohttp.ClientSession() as session: + async with session.request( + method=method, + url=url, + headers=self.default_headers, + json=data if data else None, + params=params if params else None + ) as response: + if response.status >= 400: + response_text = await response.text() + logger.error( + f"APIFY API error: {response.status}, {response_text}, " + f"Endpoint: {endpoint}, Attempt: {attempt + 1}/{retries}" + ) + + # Raise error on last attempt or for 4xx client errors (except 429) + if attempt == retries - 1 or (400 <= response.status < 500 and response.status != 429): + raise HTTPException( + status_code=response.status, + detail=f"APIFY API error: {response_text}" + ) + + # Exponential backoff delay + delay = retry_delay * (2 ** attempt) + await asyncio.sleep(delay) + continue + + return await response.json() + + except (aiohttp.ClientError, asyncio.TimeoutError) as e: + logger.error(f"APIFY API connection error: {str(e)}, Attempt: {attempt + 1}/{retries}") + + if attempt == retries - 1: + raise HTTPException( + status_code=503, + detail=f"Failed to connect to APIFY API after {retries} attempts: {str(e)}" + ) + + # Exponential backoff delay + delay = retry_delay * (2 ** attempt) + await asyncio.sleep(delay) + + # This code should never be reached, but return an empty dict to satisfy type checking + return {} + + async def start_actor_run( + self, + actor_id: str, + run_input: Dict[str, Any] + ) -> str: + """ + Start an APIFY actor run. + + Args: + actor_id: ID of the APIFY actor to run + run_input: Actor input parameters + + Returns: + Run ID of the actor run + """ + endpoint = f"/acts/{actor_id}/runs" + + logger.info(f"Starting APIFY actor run: {actor_id}") + response = await self._make_request("POST", endpoint, data={"run": run_input}) + + run_id = response.get("data", {}).get("id") + if not run_id: + raise HTTPException( + status_code=500, + detail=f"Failed to start APIFY actor run: {json.dumps(response)}" + ) + + logger.info(f"APIFY actor run started: {run_id}") + return run_id + + async def get_run_status(self, run_id: str) -> Dict[str, Any]: + """ + Get the status of an APIFY actor run. + + Args: + run_id: ID of the actor run + + Returns: + Run status details + """ + endpoint = f"/actor-runs/{run_id}" + return await self._make_request("GET", endpoint) + + async def is_run_finished(self, run_id: str) -> bool: + """ + Check if an APIFY actor run is finished. + + Args: + run_id: ID of the actor run + + Returns: + True if the run is finished, False otherwise + """ + run_info = await self.get_run_status(run_id) + status = run_info.get("data", {}).get("status") + return status in ["SUCCEEDED", "FAILED", "ABORTED", "TIMED-OUT"] + + async def wait_for_run_to_finish( + self, + run_id: str, + check_interval: int = 5, + max_wait_time: int = 600 + ) -> Dict[str, Any]: + """ + Wait for an APIFY actor run to finish. + + Args: + run_id: ID of the actor run + check_interval: Interval between status checks in seconds + max_wait_time: Maximum wait time in seconds + + Returns: + Run details after completion + + Raises: + HTTPException: If the run fails or times out + """ + logger.info(f"Waiting for APIFY actor run to finish: {run_id}") + + start_time = time.time() + + while True: + run_info = await self.get_run_status(run_id) + status = run_info.get("data", {}).get("status") + + if status == "SUCCEEDED": + logger.info(f"APIFY actor run completed successfully: {run_id}") + return run_info + + if status in ["FAILED", "ABORTED", "TIMED-OUT"]: + logger.error(f"APIFY actor run failed: {run_id}, status: {status}") + raise HTTPException( + status_code=500, + detail=f"APIFY actor run failed with status: {status}" + ) + + # Check for timeout + if time.time() - start_time > max_wait_time: + logger.error(f"Timed out waiting for APIFY actor run: {run_id}") + raise HTTPException( + status_code=504, + detail=f"Timed out waiting for APIFY actor run after {max_wait_time} seconds" + ) + + await asyncio.sleep(check_interval) + + async def get_run_results( + self, + run_id: str, + clean: bool = True, + limit: Optional[int] = None + ) -> List[Dict[str, Any]]: + """ + Get the results of an APIFY actor run. + + Args: + run_id: ID of the actor run + clean: Whether to clean the run after retrieving results + limit: Maximum number of results to retrieve + + Returns: + List of results from the actor run + """ + endpoint = f"/actor-runs/{run_id}/dataset/items" + params = {} + + if limit: + params["limit"] = limit + + logger.info(f"Getting results for APIFY actor run: {run_id}") + response = await self._make_request("GET", endpoint, params=params) + + # Clean up the run if requested + if clean: + await self.delete_run(run_id) + + return response.get("data", []) + + async def delete_run(self, run_id: str) -> bool: + """ + Delete an APIFY actor run. + + Args: + run_id: ID of the actor run + + Returns: + True if the run was deleted successfully + """ + endpoint = f"/actor-runs/{run_id}" + + logger.info(f"Deleting APIFY actor run: {run_id}") + await self._make_request("DELETE", endpoint) + + return True + + async def start_and_wait_for_results( + self, + actor_id: str, + run_input: Dict[str, Any], + limit: Optional[int] = None, + check_interval: int = 5, + max_wait_time: int = 600 + ) -> List[Dict[str, Any]]: + """ + Helper method to start an actor run, wait for it to finish, and get results. + + Args: + actor_id: ID of the APIFY actor to run + run_input: Actor input parameters + limit: Maximum number of results to retrieve + check_interval: Interval between status checks in seconds + max_wait_time: Maximum wait time in seconds + + Returns: + List of results from the actor run + """ + run_id = await self.start_actor_run(actor_id, run_input) + await self.wait_for_run_to_finish(run_id, check_interval, max_wait_time) + return await self.get_run_results(run_id, clean=True, limit=limit) \ No newline at end of file diff --git a/backend/app/processing/collection/base.py b/backend/app/processing/collection/base.py new file mode 100644 index 0000000000..8a3c468afa --- /dev/null +++ b/backend/app/processing/collection/base.py @@ -0,0 +1,367 @@ +""" +Base Collector for Social Media Platforms + +This module provides a base abstract class that defines the interface +and common functionality for all platform-specific social media collectors. +""" + +import abc +import logging +from datetime import datetime, timedelta +from typing import Any, Dict, List, Optional, Union +from uuid import UUID + +from app.core.config import settings +from app.processing.collection.apify_client import ApifyClient +from app.services.repositories.post_repository import PostRepository +from app.services.repositories.comment_repository import CommentRepository + +logger = logging.getLogger(__name__) + + +class BaseCollector(abc.ABC): + """ + Abstract base class for social media data collectors. + + This class defines the interface for platform-specific collectors + and provides common functionality for interacting with APIFY + and transforming collected data. + """ + + def __init__( + self, + apify_client: Optional[ApifyClient] = None, + post_repository: Optional[PostRepository] = None, + comment_repository: Optional[CommentRepository] = None + ): + """ + Initialize the collector with required dependencies. + + Args: + apify_client: Optional APIFY client (will be created if not provided) + post_repository: Optional post repository (will be created if not provided) + comment_repository: Optional comment repository (will be created if not provided) + """ + self.apify_client = apify_client or ApifyClient() + self.post_repository = post_repository or PostRepository() + self.comment_repository = comment_repository or CommentRepository() + + # Each platform must set these values + self.platform_name: str = "" + self.actor_id: str = "" + self.default_run_options: Dict[str, Any] = { + "maxItems": settings.SCRAPING_MAX_POSTS + } + + @property + def max_items(self) -> int: + """Get the maximum number of items to collect.""" + return settings.SCRAPING_MAX_POSTS + + @property + def max_comments(self) -> int: + """Get the maximum number of comments to collect.""" + return settings.SCRAPING_MAX_COMMENTS + + def get_default_date_range( + self, + days_back: int = None + ) -> tuple[datetime, datetime]: + """ + Get default date range for data collection. + + Args: + days_back: Number of days to look back (defaults to SCRAPING_DEFAULT_DAYS_BACK) + + Returns: + Tuple of (start_date, end_date) + """ + days = days_back or settings.SCRAPING_DEFAULT_DAYS_BACK + end_date = datetime.utcnow() + start_date = end_date - timedelta(days=days) + return start_date, end_date + + def prepare_run_input(self, **kwargs) -> Dict[str, Any]: + """ + Prepare the run input for APIFY actor. + + Base implementation returns default run options, + platforms should override to add platform-specific options. + + Args: + **kwargs: Additional parameters to include in the run input + + Returns: + Dictionary with run input for APIFY actor + """ + run_input = self.default_run_options.copy() + run_input.update(kwargs) + return run_input + + @abc.abstractmethod + async def collect_posts( + self, + account_id: Union[UUID, str], + count: int = None, + since_date: datetime = None + ) -> List[Dict[str, Any]]: + """ + Collect posts from a social media account. + + Args: + account_id: UUID of the social media account to collect from + count: Maximum number of posts to collect (defaults to SCRAPING_MAX_POSTS) + since_date: Only collect posts after this date (defaults to default date range) + + Returns: + List of collected and transformed posts + """ + pass + + @abc.abstractmethod + async def collect_comments( + self, + post_id: str, + count: int = None + ) -> List[Dict[str, Any]]: + """ + Collect comments for a social media post. + + Args: + post_id: MongoDB ID of the post to collect comments for + count: Maximum number of comments to collect (defaults to SCRAPING_MAX_COMMENTS) + + Returns: + List of collected and transformed comments + """ + pass + + @abc.abstractmethod + async def collect_profile( + self, + account_id: Union[UUID, str] + ) -> Dict[str, Any]: + """ + Collect profile information for a social media account. + + Args: + account_id: UUID of the social media account to collect profile for + + Returns: + Dictionary with account profile information + """ + pass + + @abc.abstractmethod + async def update_metrics( + self, + account_id: Union[UUID, str] + ) -> Dict[str, Any]: + """ + Update engagement metrics for a social media account. + + Args: + account_id: UUID of the social media account to update metrics for + + Returns: + Dictionary with updated metrics + """ + pass + + @abc.abstractmethod + def transform_post(self, raw_post: Dict[str, Any], account_id: Union[UUID, str]) -> Dict[str, Any]: + """ + Transform a raw post from APIFY into the format expected by the repository. + + Args: + raw_post: Raw post data from APIFY + account_id: UUID of the social media account + + Returns: + Transformed post data + """ + pass + + @abc.abstractmethod + def transform_comment(self, raw_comment: Dict[str, Any], post_id: str) -> Dict[str, Any]: + """ + Transform a raw comment from APIFY into the format expected by the repository. + + Args: + raw_comment: Raw comment data from APIFY + post_id: MongoDB ID of the parent post + + Returns: + Transformed comment data + """ + pass + + @abc.abstractmethod + def transform_profile(self, raw_profile: Dict[str, Any]) -> Dict[str, Any]: + """ + Transform a raw profile from APIFY into the format expected by the repository. + + Args: + raw_profile: Raw profile data from APIFY + + Returns: + Transformed profile data + """ + pass + + async def save_posts( + self, + posts: List[Dict[str, Any]], + account_id: Union[UUID, str] + ) -> List[str]: + """ + Save collected posts to the repository. + + Args: + posts: List of raw posts from APIFY + account_id: UUID of the social media account + + Returns: + List of MongoDB IDs for the saved posts + """ + post_ids = [] + + for raw_post in posts: + try: + # Check if post already exists + existing_post = await self.post_repository.get_by_platform_id( + platform=self.platform_name, + platform_id=raw_post.get("id", "") + ) + + if existing_post: + # Update engagement metrics + post_data = self.transform_post(raw_post, account_id) + await self.post_repository.update_engagement_metrics( + post_id=str(existing_post["_id"]), + metrics=post_data["engagement"] + ) + post_ids.append(str(existing_post["_id"])) + else: + # Create new post + post_data = self.transform_post(raw_post, account_id) + post_id = await self.post_repository.create(post_data) + post_ids.append(post_id) + + except Exception as e: + logger.error(f"Error saving post: {str(e)}", exc_info=True) + + return post_ids + + async def save_comments( + self, + comments: List[Dict[str, Any]], + post_id: str + ) -> List[str]: + """ + Save collected comments to the repository. + + Args: + comments: List of raw comments from APIFY + post_id: MongoDB ID of the parent post + + Returns: + List of MongoDB IDs for the saved comments + """ + comment_ids = [] + + for raw_comment in comments: + try: + # Check if comment already exists + existing_comment = await self.comment_repository.get_by_platform_id( + platform=self.platform_name, + platform_id=raw_comment.get("id", "") + ) + + if existing_comment: + # Update engagement metrics + comment_data = self.transform_comment(raw_comment, post_id) + await self.comment_repository.update_engagement_metrics( + comment_id=str(existing_comment["_id"]), + metrics=comment_data["engagement"] + ) + comment_ids.append(str(existing_comment["_id"])) + else: + # Create new comment + comment_data = self.transform_comment(raw_comment, post_id) + comment_id = await self.comment_repository.create(comment_data) + comment_ids.append(comment_id) + + except Exception as e: + logger.error(f"Error saving comment: {str(e)}", exc_info=True) + + return comment_ids + + def extract_hashtags(self, text: str) -> List[str]: + """ + Extract hashtags from text. + + Args: + text: Text to extract hashtags from + + Returns: + List of hashtags without the # symbol + """ + if not text: + return [] + + hashtags = [] + for word in text.split(): + if word.startswith('#'): + # Remove the # and any trailing punctuation + tag = word[1:].strip().rstrip('.,:;!?') + if tag: + hashtags.append(tag) + + return hashtags + + def extract_mentions(self, text: str) -> List[str]: + """ + Extract mentions from text. + + Args: + text: Text to extract mentions from + + Returns: + List of mentions without the @ symbol + """ + if not text: + return [] + + mentions = [] + for word in text.split(): + if word.startswith('@'): + # Remove the @ and any trailing punctuation + mention = word[1:].strip().rstrip('.,:;!?') + if mention: + mentions.append(mention) + + return mentions + + def extract_links(self, text: str) -> List[str]: + """ + Extract links from text (simple implementation). + + Args: + text: Text to extract links from + + Returns: + List of extracted links + """ + if not text: + return [] + + links = [] + for word in text.split(): + if word.startswith(('http://', 'https://')): + # Remove any trailing punctuation + link = word.strip().rstrip('.,:;!?') + if link: + links.append(link) + + return links \ No newline at end of file diff --git a/backend/app/processing/collection/facebook.py b/backend/app/processing/collection/facebook.py new file mode 100644 index 0000000000..35a677fff0 --- /dev/null +++ b/backend/app/processing/collection/facebook.py @@ -0,0 +1,591 @@ +""" +Facebook Data Collector + +This module provides a collector for Facebook data using APIFY's Facebook Scraper actor. +""" + +import logging +from datetime import datetime +from typing import Any, Dict, List, Optional, Union +from urllib.parse import urlparse +from uuid import UUID + +from app.core.config import settings +from app.processing.collection.base import BaseCollector +from app.services.repositories.social_media_account import SocialMediaAccountRepository + +logger = logging.getLogger(__name__) + + +class FacebookCollector(BaseCollector): + """ + Facebook data collector using APIFY's Facebook Scraper actor. + + This collector handles collecting posts, comments, and profile information + from Facebook accounts via APIFY, and transforms the data into the format + expected by the application's repositories. + """ + + def __init__(self, *args, **kwargs): + """Initialize the Facebook collector.""" + super().__init__(*args, **kwargs) + self.platform_name = "facebook" + self.actor_id = settings.APIFY_FACEBOOK_ACTOR_ID + self.account_repository = SocialMediaAccountRepository() + + # Facebook-specific default options + self.default_run_options = { + "maxPosts": settings.SCRAPING_MAX_POSTS, + "maxPostComments": 0, # Don't collect comments during post collection + "maxPostReactions": 1000, # Collect reaction counts + "commentsMode": "NONE", # Don't collect comments during post collection + "includeNestedComments": False, + "reactionsMode": "SUMMARY", + "addMessageTimestamps": True + } + + async def _get_account_info(self, account_id: Union[UUID, str]) -> Dict[str, Any]: + """ + Get Facebook account information for a given account ID. + + Args: + account_id: UUID of the social media account + + Returns: + Dictionary with account information + + Raises: + ValueError: If the account is not found + """ + account = await self.account_repository.get(account_id) + + if not account: + raise ValueError(f"Social media account not found: {account_id}") + + profile_url = account.url + handle = account.handle + + # We need at least one of these + if not (profile_url or handle): + raise ValueError(f"Account {account_id} has no Facebook URL or handle") + + return { + "url": profile_url, + "handle": handle + } + + async def collect_posts( + self, + account_id: Union[UUID, str], + count: int = None, + since_date: datetime = None + ) -> List[Dict[str, Any]]: + """ + Collect posts from a Facebook account. + + Args: + account_id: UUID of the social media account to collect from + count: Maximum number of posts to collect (defaults to settings.SCRAPING_MAX_POSTS) + since_date: Only collect posts after this date (defaults to default date range) + + Returns: + List of MongoDB IDs for the collected posts + """ + account_info = await self._get_account_info(account_id) + + max_count = count or self.max_items + start_date, _ = self.get_default_date_range() if not since_date else (since_date, datetime.utcnow()) + + # Determine the best way to identify the page + profile_url = account_info.get("url") + profile_name = account_info.get("handle") + + # First prefer the full URL, then the handle + target = profile_url if profile_url else f"https://www.facebook.com/{profile_name}" + + logger.info(f"Collecting posts for Facebook account {target} (max: {max_count}, since: {start_date})") + + # Configure Facebook scraper input + run_input = self.prepare_run_input( + startUrls=[{"url": target}], + maxPosts=max_count, + startDate=start_date.strftime("%Y-%m-%d") + ) + + # Run actor and get results + results = await self.apify_client.start_and_wait_for_results( + actor_id=self.actor_id, + run_input=run_input, + limit=max_count + ) + + # Filter for posts only + posts = [item for item in results if item.get("type") == "post"] + + logger.info(f"Collected {len(posts)} posts for Facebook account {target}") + + # Save posts to MongoDB + return await self.save_posts(posts, account_id) + + async def collect_comments( + self, + post_id: str, + count: int = None + ) -> List[Dict[str, Any]]: + """ + Collect comments for a Facebook post. + + Args: + post_id: MongoDB ID of the post to collect comments for + count: Maximum number of comments to collect (defaults to settings.SCRAPING_MAX_COMMENTS) + + Returns: + List of MongoDB IDs for the collected comments + """ + post = await self.post_repository.get(post_id) + + if not post: + raise ValueError(f"Post not found: {post_id}") + + fb_post_id = post.get("platform_id") + if not fb_post_id: + raise ValueError(f"Invalid post platform ID for {post_id}") + + max_count = count or self.max_comments + + logger.info(f"Collecting comments for Facebook post {fb_post_id} (max: {max_count})") + + # For Facebook, we need the post URL + post_url = f"https://www.facebook.com/permalink.php?id={fb_post_id}" + if "links" in post.get("content", {}) and post["content"]["links"]: + post_url = post["content"]["links"][0] # Use the first link if available + + # Configure Facebook scraper for comments + run_input = self.prepare_run_input( + startUrls=[{"url": post_url}], + maxPosts=1, # Just get the original post + maxPostComments=max_count, + maxCommentReplies=10, + commentsMode="DETAILED", + includeNestedComments=True + ) + + # Run actor and get results + results = await self.apify_client.start_and_wait_for_results( + actor_id=self.actor_id, + run_input=run_input + ) + + # Extract comments from results + comments = [] + for item in results: + if item.get("type") == "post" and "comments" in item: + comments.extend(item.get("comments", [])) + + logger.info(f"Collected {len(comments)} comments for Facebook post {fb_post_id}") + + # Save comments to MongoDB + return await self.save_comments(comments, post_id) + + async def collect_profile( + self, + account_id: Union[UUID, str] + ) -> Dict[str, Any]: + """ + Collect profile information for a Facebook account. + + Args: + account_id: UUID of the social media account to collect profile for + + Returns: + Updated account information + """ + account_info = await self._get_account_info(account_id) + + # Determine the best way to identify the page + profile_url = account_info.get("url") + profile_name = account_info.get("handle") + + # First prefer the full URL, then the handle + target = profile_url if profile_url else f"https://www.facebook.com/{profile_name}" + + logger.info(f"Collecting profile information for Facebook account {target}") + + # Configure Facebook scraper for profile + run_input = self.prepare_run_input( + startUrls=[{"url": target}], + maxPosts=1, # Just need basic page info + includePageMetadata=True + ) + + # Run actor and get results + results = await self.apify_client.start_and_wait_for_results( + actor_id=self.actor_id, + run_input=run_input, + limit=2 # Might return page info separately + ) + + if not results: + logger.warning(f"No profile information returned for Facebook account {target}") + return {} + + # Find the page info object + page_info = None + for item in results: + if item.get("type") == "page" or item.get("type") == "profile": + page_info = item + break + + if not page_info and len(results) > 0: + # If no specific page object, try to extract from the first post + page_info = results[0].get("page", {}) + + if page_info: + # Transform and update account + account_data = self.transform_profile(page_info) + await self.account_repository.update(account_id, account_data) + + logger.info(f"Updated profile information for Facebook account {target}") + return account_data + + return {} + + async def update_metrics( + self, + account_id: Union[UUID, str] + ) -> Dict[str, Any]: + """ + Update engagement metrics for a Facebook account. + + This performs the same function as collect_profile but is named separately + to match the interface requirements. + + Args: + account_id: UUID of the social media account to update metrics for + + Returns: + Updated account metrics + """ + # For Facebook, updating metrics is the same as collecting profile + return await self.collect_profile(account_id) + + def transform_post( + self, + raw_post: Dict[str, Any], + account_id: Union[UUID, str] + ) -> Dict[str, Any]: + """ + Transform a raw Facebook post from APIFY into the format expected by the repository. + + Args: + raw_post: Raw post data from APIFY + account_id: UUID of the social media account + + Returns: + Transformed post data + """ + # Extract basic information + post_id = raw_post.get("postId", raw_post.get("id", "")) + text = raw_post.get("text", "") + + # Handle created_at (Facebook format can vary) + created_at = datetime.utcnow() + if "timestamp" in raw_post: + try: + created_at = datetime.fromtimestamp(raw_post["timestamp"] / 1000) + except (ValueError, TypeError): + pass + elif "createdAt" in raw_post: + try: + created_at = datetime.strptime( + raw_post.get("createdAt", "").split("+")[0], + "%Y-%m-%dT%H:%M:%S" + ) + except (ValueError, TypeError): + pass + + # Extract media and links + media_urls = [] + if "attachments" in raw_post: + for attachment in raw_post.get("attachments", []): + if "url" in attachment: + media_urls.append(attachment["url"]) + + # Extract link to the post + post_url = raw_post.get("postUrl", "") + links = [post_url] if post_url else [] + + # Add any links from the text + links.extend(self.extract_links(text)) + + # Extract engagement metrics + reactions = raw_post.get("reactionsCount", {}) + engagement = { + "likes_count": reactions.get("like", 0) + reactions.get("love", 0) + reactions.get("care", 0), + "shares_count": raw_post.get("sharesCount", 0), + "comments_count": raw_post.get("commentsCount", 0), + "views_count": None, + "engagement_rate": None # Calculate if needed + } + + # Determine the content type + content_type = "post" + if "sharingPostUrl" in raw_post or "sharingText" in raw_post: + content_type = "share" + elif raw_post.get("type") == "photo": + content_type = "photo" + elif raw_post.get("type") == "video": + content_type = "video" + elif raw_post.get("type") == "event": + content_type = "event" + + # Transform to application post format + return { + "platform_id": post_id, + "platform": self.platform_name, + "account_id": str(account_id), + "content_type": content_type, + "short_code": raw_post.get("postId", ""), # Short code for URL (platform's identifier) + "url": post_url, # Top-level URL field + "content": { + "text": text, + "media": media_urls, + "links": links, + "hashtags": self.extract_hashtags(text), + "mentions": self.extract_mentions(text) + }, + "metadata": { + "created_at": created_at, + "language": raw_post.get("languageCode", "unknown"), + "location": raw_post.get("location", None), + "client": "Facebook", + "is_repost": content_type == "share", + "is_reply": False, + "dimensions": None, # Facebook API doesn't consistently provide this + "alt_text": raw_post.get("imageText", None), # Alt text if available + "tagged_users": [] # Tagged users if available + }, + "engagement": { + "likes_count": reactions.get("like", 0) + reactions.get("love", 0) + reactions.get("care", 0), + "shares_count": raw_post.get("sharesCount", 0), + "comments_count": raw_post.get("commentsCount", 0), + "views_count": raw_post.get("videoViewCount", None), + "engagement_rate": None, # Calculate if needed + "saves_count": None # Facebook doesn't provide this + }, + "child_posts": self._extract_child_posts(raw_post) if content_type == "carousel" else None, + "video_data": self._extract_video_data(raw_post) if content_type == "video" else None, + "analysis": None # Will be populated by analysis pipelines + } + + def transform_comment( + self, + raw_comment: Dict[str, Any], + post_id: str + ) -> Dict[str, Any]: + """ + Transform a raw Facebook comment from APIFY into the format expected by the repository. + + Args: + raw_comment: Raw comment data from APIFY + post_id: MongoDB ID of the parent post + + Returns: + Transformed comment data + """ + # Extract basic information + comment_id = raw_comment.get("commentId", raw_comment.get("id", "")) + text = raw_comment.get("text", "") + + # Extract user info + user_name = raw_comment.get("name", "") + user_id = raw_comment.get("authorId", "") + + # Handle created_at (Facebook format can vary) + created_at = datetime.utcnow() + if "timestamp" in raw_comment: + try: + created_at = datetime.fromtimestamp(raw_comment["timestamp"] / 1000) + except (ValueError, TypeError): + pass + + # Extract media + media_urls = [] + if "attachments" in raw_comment: + for attachment in raw_comment.get("attachments", []): + if "url" in attachment: + media_urls.append(attachment["url"]) + + # Extract engagement metrics + reactions = raw_comment.get("reactionsCount", {}) + if isinstance(reactions, dict): + likes_count = sum(reactions.values()) + else: + likes_count = reactions + + engagement = { + "likes_count": likes_count, + "replies_count": len(raw_comment.get("replies", [])) + } + + # Fetch post URL from post_repository if available + post_url = raw_comment.get("postUrl", raw_comment.get("facebookUrl", "")) + + # Extract additional user fields + user_full_name = raw_comment.get("profileName", "") + user_profile_pic = raw_comment.get("profilePicture", "") + user_verified = False # Facebook API doesn't consistently provide this + user_private = False # Facebook API doesn't consistently provide this + + # Process replies if available + replies = [] + if "replies" in raw_comment and isinstance(raw_comment["replies"], list): + for reply in raw_comment["replies"]: + reply_obj = { + "platform_id": reply.get("id", ""), + "user_id": reply.get("profileId", ""), + "user_name": reply.get("name", ""), + "user_full_name": reply.get("profileName", ""), + "user_profile_pic": reply.get("profilePicture", ""), + "user_verified": False, # Facebook API doesn't provide this consistently + "text": reply.get("text", ""), + "created_at": datetime.utcnow(), # Default if not available + "likes_count": int(reply.get("likesCount", 0)) if isinstance(reply.get("likesCount"), str) else reply.get("likesCount", 0), + "replies_count": 0 + } + + # Parse created_at if available + if "date" in reply: + try: + reply_obj["created_at"] = datetime.fromisoformat(reply["date"].replace('Z', '+00:00')) + except (ValueError, TypeError): + pass + + replies.append(reply_obj) + + # Determine if this is a reply + is_reply = raw_comment.get("threadingDepth", 0) > 0 + parent_comment_id = raw_comment.get("parentCommentId", None) + + # Build user_details + user_details = { + "fbid_v2": raw_comment.get("feedbackId"), + "is_mentionable": True, + "profile_pic_id": None + } + + # Transform to application comment format + return { + "platform_id": comment_id, + "platform": self.platform_name, + "post_id": post_id, + "post_url": post_url, + "user_id": user_id, + "user_name": user_name, + "user_full_name": user_full_name, + "user_profile_pic": user_profile_pic, + "user_verified": user_verified, + "user_private": user_private, + "content": { + "text": text, + "media": media_urls, + "mentions": self.extract_mentions(text) + }, + "metadata": { + "created_at": created_at, + "language": raw_comment.get("languageCode", "unknown"), + "is_reply": is_reply, + "parent_comment_id": parent_comment_id + }, + "engagement": engagement, + "replies": replies, + "user_details": user_details, + "analysis": None # Will be populated by analysis pipelines + } + + def transform_profile( + self, + raw_profile: Dict[str, Any] + ) -> Dict[str, Any]: + """ + Transform a raw Facebook profile from APIFY into the format expected by the repository. + + Args: + raw_profile: Raw profile data from APIFY + + Returns: + Transformed profile data + """ + # Extract the page ID and handle + page_id = raw_profile.get("pageId", raw_profile.get("id", "")) + page_url = raw_profile.get("url", "") + handle = "" + + # Try to extract handle from URL + if page_url: + try: + path = urlparse(page_url).path + if path and path != "/": + handle = path.strip("/").split("/")[0] + except Exception: + pass + + # Use name if handle extraction failed + if not handle: + handle = raw_profile.get("name", "").lower().replace(" ", "") + + # Transform to application account format (for PostgreSQL update) + return { + "platform_id": page_id, + "handle": handle, + "name": raw_profile.get("name", ""), + "url": page_url, + "verified": raw_profile.get("verified", False), + "follower_count": raw_profile.get("followersCount", raw_profile.get("likes", 0)), + "following_count": None # Facebook often doesn't provide this + } + + def _extract_child_posts(self, raw_post: Dict[str, Any]) -> List[Dict[str, Any]]: + """Extract child posts from a carousel/sidecar post.""" + child_posts = [] + + if "attachments" in raw_post and isinstance(raw_post["attachments"], list): + for i, attachment in enumerate(raw_post["attachments"]): + if isinstance(attachment, dict) and "url" in attachment: + child_post = { + "id": f"{raw_post.get('postId', '')}_child_{i}", + "type": attachment.get("type", "Image"), + "url": attachment.get("url"), + "display_url": attachment.get("url") + } + + # Add dimensions if available + if "width" in attachment and "height" in attachment: + child_post["dimensions"] = { + "width": attachment["width"], + "height": attachment["height"] + } + + child_posts.append(child_post) + + return child_posts if child_posts else None + + def _extract_video_data(self, raw_post: Dict[str, Any]) -> Dict[str, Any]: + """Extract video data from a video post.""" + video_data = { + "duration": raw_post.get("videoDuration"), + "video_url": None, + "thumbnail_url": None, + "is_muted": False + } + + # Try to get video URL and thumbnail URL + if "attachments" in raw_post and isinstance(raw_post["attachments"], list): + for attachment in raw_post["attachments"]: + if isinstance(attachment, dict): + if attachment.get("type") == "video" and "url" in attachment: + video_data["video_url"] = attachment["url"] + if "thumbnailUrl" in attachment: + video_data["thumbnail_url"] = attachment["thumbnailUrl"] + + return video_data \ No newline at end of file diff --git a/backend/app/processing/collection/factory.py b/backend/app/processing/collection/factory.py new file mode 100644 index 0000000000..a480f7559c --- /dev/null +++ b/backend/app/processing/collection/factory.py @@ -0,0 +1,94 @@ +""" +Collector Factory + +This module provides a factory for creating platform-specific collectors. +""" + +import logging +from typing import Dict, Optional, Type, Union + +from app.processing.collection.base import BaseCollector +from app.processing.collection.twitter import TwitterCollector +from app.processing.collection.facebook import FacebookCollector +from app.processing.collection.instagram import InstagramCollector + +logger = logging.getLogger(__name__) + + +class CollectorFactory: + """ + Factory for creating platform-specific social media collectors. + + This class manages the registry of available collectors and + provides methods to get the appropriate collector for a given platform. + """ + + _registry: Dict[str, Type[BaseCollector]] = { + "twitter": TwitterCollector, + "facebook": FacebookCollector, + "instagram": InstagramCollector + } + + @classmethod + def register_collector(cls, platform: str, collector_class: Type[BaseCollector]) -> None: + """ + Register a new collector for a platform. + + Args: + platform: Name of the platform (e.g., "twitter", "facebook") + collector_class: Collector class to register + + Raises: + ValueError: If the collector class is not a subclass of BaseCollector + """ + if not issubclass(collector_class, BaseCollector): + raise ValueError(f"Collector class must be a subclass of BaseCollector, got {collector_class}") + + cls._registry[platform.lower()] = collector_class + logger.info(f"Registered collector for platform: {platform}") + + @classmethod + def get_collector(cls, platform: str) -> BaseCollector: + """ + Get a collector instance for the specified platform. + + Args: + platform: Name of the platform (e.g., "twitter", "facebook") + + Returns: + An instance of the appropriate collector + + Raises: + ValueError: If no collector is registered for the platform + """ + platform = platform.lower() + + if platform not in cls._registry: + supported = ", ".join(cls._registry.keys()) + raise ValueError(f"No collector registered for platform: {platform}. Supported platforms: {supported}") + + collector_class = cls._registry[platform] + return collector_class() + + @classmethod + def list_supported_platforms(cls) -> list[str]: + """ + Get a list of all supported platforms. + + Returns: + List of platform names + """ + return list(cls._registry.keys()) + + @classmethod + def is_platform_supported(cls, platform: str) -> bool: + """ + Check if a platform is supported. + + Args: + platform: Name of the platform to check + + Returns: + True if the platform is supported, False otherwise + """ + return platform.lower() in cls._registry \ No newline at end of file diff --git a/backend/app/processing/collection/instagram.py b/backend/app/processing/collection/instagram.py new file mode 100644 index 0000000000..9c62d935c3 --- /dev/null +++ b/backend/app/processing/collection/instagram.py @@ -0,0 +1,642 @@ +""" +Instagram Data Collector + +This module provides a collector for Instagram data using APIFY's Instagram Scraper actor. +""" + +import logging +from datetime import datetime +from typing import Any, Dict, List, Optional, Union +from uuid import UUID + +from app.core.config import settings +from app.db.models.social_media_account import Platform +from app.processing.collection.base import BaseCollector +from app.services.repositories.social_media_account import SocialMediaAccountRepository + +logger = logging.getLogger(__name__) + + +class InstagramCollector(BaseCollector): + """ + Instagram data collector using APIFY's Instagram Scraper actor. + + This collector handles collecting posts, comments, and profile information + from Instagram accounts via APIFY, and transforms the data into the format + expected by the application's repositories. + """ + + def __init__(self, *args, **kwargs): + """Initialize the Instagram collector.""" + super().__init__(*args, **kwargs) + self.platform_name = "instagram" + self.actor_id = settings.APIFY_INSTAGRAM_ACTOR_ID + self.account_repository = SocialMediaAccountRepository() + + # Instagram-specific default options + self.default_run_options = { + "maxPosts": settings.SCRAPING_MAX_POSTS, + "resultsType": "posts", + "addParentData": True, + "includeComments": False, # Will fetch separately + "scrapePostsUntilDate": None, # Will be set in collect_posts + } + + async def _get_account_handle(self, account_id: Union[UUID, str]) -> str: + """ + Get the Instagram handle for a given account ID. + + Args: + account_id: UUID of the social media account + + Returns: + Instagram handle + + Raises: + ValueError: If the account is not found or has no handle + """ + account = await self.account_repository.get(account_id) + + if not account: + raise ValueError(f"Social media account not found: {account_id}") + + if not account.handle: + raise ValueError(f"Account {account_id} has no Instagram handle") + + return account.handle + + def get_post_sync(self, post_id: str) -> Optional[Dict[str, Any]]: + """ + Get a post by ID using a synchronous approach. + + Args: + post_id: The ID of the post to retrieve + + Returns: + The post data if found, None otherwise + """ + import pymongo + from bson import ObjectId + from app.core.config import settings + + # Connect to MongoDB directly (synchronous) + client = pymongo.MongoClient(settings.MONGODB_URI) + db = client.get_database(settings.MONGODB_DATABASE) + collection = db.posts + + try: + # Try to find the post + post = collection.find_one({"_id": ObjectId(post_id)}) + return post + except Exception as e: + logger.error(f"Error getting post synchronously: {e}") + return None + finally: + client.close() + + async def collect_posts( + self, + account_id: Union[UUID, str], + count: int = None, + since_date: datetime = None + ) -> List[Dict[str, Any]]: + """ + Collect posts from an Instagram account. + + Args: + account_id: UUID of the social media account to collect from + count: Maximum number of posts to collect (defaults to settings.SCRAPING_MAX_POSTS) + since_date: Only collect posts after this date (defaults to default date range) + + Returns: + List of MongoDB IDs for the collected posts + """ + handle = await self._get_account_handle(account_id) + + max_count = count or self.max_items + start_date, _ = self.get_default_date_range() if not since_date else (since_date, datetime.utcnow()) + + logger.info(f"Collecting posts for Instagram account {handle} (max: {max_count}, since: {start_date})") + + # Configure Instagram scraper input + run_input = self.prepare_run_input( + usernames=[handle], + maxPosts=max_count, + resultsType="posts", + scrapePostsUntilDate=start_date.strftime("%Y-%m-%d") + ) + + # Run actor and get results + results = await self.apify_client.start_and_wait_for_results( + actor_id=self.actor_id, + run_input=run_input, + limit=max_count + ) + + # Filter for post objects only + posts = [] + for item in results: + # Instagram APIFY actor sometimes nests posts inside profile objects + if "type" in item and item["type"] == "user": + if "latestPosts" in item: + posts.extend(item["latestPosts"]) + else: + # Assume it's a post object directly + posts.append(item) + + logger.info(f"Collected {len(posts)} posts for Instagram account {handle}") + + # Save posts to MongoDB + return await self.save_posts(posts, account_id) + + async def collect_comments( + self, + post_id: str, + count: int = None + ) -> List[Dict[str, Any]]: + """ + Collect comments for an Instagram post. + + Args: + post_id: MongoDB ID of the post to collect comments for + count: Maximum number of comments to collect (defaults to settings.SCRAPING_MAX_COMMENTS) + + Returns: + List of MongoDB IDs for the collected comments + """ + post = await self.post_repository.get(post_id) + + if not post: + raise ValueError(f"Post not found: {post_id}") + + ig_post_id = post.get("platform_id") + if not ig_post_id: + raise ValueError(f"Invalid post platform ID for {post_id}") + + # For Instagram, we need post URL + post_url = None + if "links" in post.get("content", {}) and post["content"]["links"]: + for link in post["content"]["links"]: + if "instagram.com/p/" in link: + post_url = link + break + + if not post_url: + # Try to construct from shortcode if available + shortcode = post.get("metadata", {}).get("shortcode") + if shortcode: + post_url = f"https://www.instagram.com/p/{shortcode}/" + else: + raise ValueError(f"Could not determine Instagram post URL for {post_id}") + + max_count = count or self.max_comments + + logger.info(f"Collecting comments for Instagram post {ig_post_id} (max: {max_count})") + + # Configure Instagram scraper for comments + run_input = self.prepare_run_input( + directUrls=[post_url], + resultsType="comments", + maxComments=max_count + ) + + # Run actor and get results + results = await self.apify_client.start_and_wait_for_results( + actor_id=self.actor_id, + run_input=run_input + ) + + # Extract comments from results + comments = [] + for item in results: + if "type" in item and item["type"] == "post": + if "comments" in item: + comments.extend(item["comments"]) + elif "id" in item and "ownerUsername" in item: + # This is likely a comment object directly + comments.append(item) + + logger.info(f"Collected {len(comments)} comments for Instagram post {ig_post_id}") + + # Save comments to MongoDB + return await self.save_comments(comments, post_id) + + async def collect_profile( + self, + account_id: Union[UUID, str] + ) -> Dict[str, Any]: + """ + Collect profile information for an Instagram account. + + Args: + account_id: UUID of the social media account to collect profile for + + Returns: + Updated account information + """ + handle = await self._get_account_handle(account_id) + + logger.info(f"Collecting profile information for Instagram account {handle}") + + # Configure Instagram scraper for profile + run_input = self.prepare_run_input( + usernames=[handle], + resultsType="details", + maxPosts=0 # Don't need posts for profile info + ) + + # Run actor and get results + results = await self.apify_client.start_and_wait_for_results( + actor_id=self.actor_id, + run_input=run_input, + limit=1 + ) + + # Find the profile info object + profile_info = None + for item in results: + if "type" in item and item["type"] == "user": + profile_info = item + break + + if not profile_info: + logger.warning(f"No profile information returned for Instagram account {handle}") + return {} + + # Transform and update account + account_data = self.transform_profile(profile_info) + + # Ensure we're not trying to update the account ID or political_entity_id + if "id" in account_data: + del account_data["id"] + + # Now update the account + await self.account_repository.update(account_id, account_data) + + logger.info(f"Updated profile information for Instagram account {handle}") + return account_data + + async def update_metrics( + self, + account_id: Union[UUID, str] + ) -> Dict[str, Any]: + """ + Update engagement metrics for an Instagram account. + + This performs the same function as collect_profile but is named separately + to match the interface requirements. + + Args: + account_id: UUID of the social media account to update metrics for + + Returns: + Updated account metrics + """ + # For Instagram, updating metrics is the same as collecting profile + return await self.collect_profile(account_id) + + def transform_post( + self, + raw_post: Dict[str, Any], + account_id: Union[UUID, str] + ) -> Dict[str, Any]: + """ + Transform a raw Instagram post from APIFY into the format expected by the repository. + + Args: + raw_post: Raw post data from APIFY + account_id: UUID of the social media account + + Returns: + Transformed post data + """ + # Extract basic information + post_id = raw_post.get("id", "") + caption = raw_post.get("caption", "") + shortcode = raw_post.get("shortCode", "") + + # Extract timestamps + created_at = datetime.utcnow() + if "timestamp" in raw_post: + try: + created_at = datetime.fromtimestamp(raw_post["timestamp"] / 1000) + except (ValueError, TypeError): + pass + elif "createdAt" in raw_post: + try: + created_at = datetime.strptime( + raw_post.get("createdAt", "").split("+")[0], + "%Y-%m-%dT%H:%M:%S" + ) + except (ValueError, TypeError): + pass + + # Extract media URLs + media_urls = [] + if "displayUrl" in raw_post: + media_urls.append(raw_post["displayUrl"]) + + if "videoUrl" in raw_post and raw_post["videoUrl"]: + media_urls.append(raw_post["videoUrl"]) + + if "images" in raw_post: + for image in raw_post.get("images", []): + if image and isinstance(image, str): + media_urls.append(image) + + # Extract post URL + post_url = f"https://www.instagram.com/p/{shortcode}/" if shortcode else None + links = [post_url] if post_url else [] + + # Add any links from the caption + links.extend(self.extract_links(caption)) + + # Determine content type + content_type = "post" + if raw_post.get("isVideo", False) or "videoUrl" in raw_post: + content_type = "video" + elif "images" in raw_post and len(raw_post["images"]) > 1: + content_type = "carousel" + elif raw_post.get("__typename") == "GraphStoryVideo": + content_type = "story" + + # Extract engagement metrics + engagement = { + "likes_count": raw_post.get("likesCount", 0), + "comments_count": raw_post.get("commentsCount", 0), + "shares_count": None, # Instagram doesn't provide share counts + "views_count": raw_post.get("videoViewCount", None) if content_type == "video" else None, + "engagement_rate": None, # Calculate if needed + "saves_count": raw_post.get("savesCount", None) # Add saves count if available + } + + # Handle dimensions + dimensions = None + if "dimensions" in raw_post: + dimensions = raw_post["dimensions"] + elif "imageWidth" in raw_post and "imageHeight" in raw_post: + dimensions = { + "width": raw_post["imageWidth"], + "height": raw_post["imageHeight"] + } + + # Handle location + location = None + if "location" in raw_post and raw_post["location"]: + location = { + "name": raw_post["location"].get("name"), + "id": raw_post["location"].get("id"), + "country": raw_post["location"].get("country"), + "state": raw_post["location"].get("state"), + "city": raw_post["location"].get("city") + } + + # Handle owner + owner = None + if "ownerUsername" in raw_post or "ownerId" in raw_post: + owner = { + "username": raw_post.get("ownerUsername", ""), + "id": raw_post.get("ownerId", ""), + "verified": raw_post.get("ownerVerified", False) + } + + # Handle tagged users + tagged_users = [] + if "taggedUsers" in raw_post and isinstance(raw_post["taggedUsers"], list): + for user in raw_post["taggedUsers"]: + if isinstance(user, dict): + tagged_users.append({ + "username": user.get("username", ""), + "id": user.get("id", ""), + "full_name": user.get("fullName"), + "is_verified": user.get("isVerified", False) + }) + + # Handle child posts for carousel/sidecar + child_posts = None + if content_type == "carousel" and "sidecarChildren" in raw_post: + child_posts = [] + for child in raw_post["sidecarChildren"]: + child_type = "Video" if child.get("isVideo", False) else "Image" + child_post = { + "id": child.get("id", ""), + "type": child_type, + "url": f"https://www.instagram.com/p/{child.get('shortCode', '')}/", + "display_url": child.get("displayUrl", "") + } + + # Add dimensions if available + if "dimensions" in child: + child_post["dimensions"] = child["dimensions"] + elif "imageWidth" in child and "imageHeight" in child: + child_post["dimensions"] = { + "width": child["imageWidth"], + "height": child["imageHeight"] + } + + # Add alt_text if available + if "accessibilityCaption" in child: + child_post["alt_text"] = child["accessibilityCaption"] + + child_posts.append(child_post) + + # Handle video data + video_data = None + if content_type == "video": + video_data = { + "duration": raw_post.get("videoDuration"), + "video_url": raw_post.get("videoUrl"), + "thumbnail_url": raw_post.get("displayUrl"), + "is_muted": raw_post.get("isMuted", False) + } + + # Transform to application post format + return { + "platform_id": post_id, + "platform": self.platform_name, + "account_id": str(account_id), + "content_type": content_type, + "short_code": shortcode, + "url": post_url, + "content": { + "text": caption, + "media": media_urls, + "links": links, + "hashtags": self.extract_hashtags(caption), + "mentions": self.extract_mentions(caption) + }, + "metadata": { + "created_at": created_at, + "language": "unknown", # Instagram doesn't provide language info + "location": location, + "client": "Instagram", + "is_repost": False, # Instagram doesn't have traditional reposts + "is_reply": False, + "dimensions": dimensions, + "alt_text": raw_post.get("accessibilityCaption"), + "product_type": raw_post.get("productType"), + "owner": owner, + "tagged_users": tagged_users + }, + "engagement": engagement, + "child_posts": child_posts, + "video_data": video_data, + "analysis": None # Will be populated by analysis pipelines + } + + def transform_comment( + self, + raw_comment: Dict[str, Any], + post_id: str + ) -> Dict[str, Any]: + """ + Transform a raw Instagram comment from APIFY into the format expected by the repository. + + Args: + raw_comment: Raw comment data from APIFY + post_id: MongoDB ID of the parent post + + Returns: + Transformed comment data + """ + # Fetch parent post to get post_url + post_url = None + try: + # Get post data synchronously + post = self.get_post_sync(post_id) + if post and "url" in post: + post_url = post["url"] + elif post and "short_code" in post: + post_url = f"https://www.instagram.com/p/{post['short_code']}/" + elif post and "metadata" in post and "shortcode" in post["metadata"]: + post_url = f"https://www.instagram.com/p/{post['metadata']['shortcode']}/" + except Exception as e: + logger.warning(f"Could not fetch parent post for url: {str(e)}") + + # Extract basic information + comment_id = raw_comment.get("id", "") + text = raw_comment.get("text", "") + + # Extract user info + user_name = raw_comment.get("ownerUsername", "") + user_id = raw_comment.get("ownerId", "") + user_full_name = raw_comment.get("ownerFullName", None) + user_profile_pic = raw_comment.get("ownerProfilePicUrl", None) + user_verified = raw_comment.get("ownerVerified", False) + user_private = raw_comment.get("ownerIsPrivate", False) + + # Handle created_at (Instagram format can vary) + created_at = datetime.utcnow() + if "timestamp" in raw_comment: + try: + created_at = datetime.fromtimestamp(raw_comment["timestamp"] / 1000) + except (ValueError, TypeError): + pass + + # Extract engagement metrics + engagement = { + "likes_count": raw_comment.get("likesCount", 0), + "replies_count": len(raw_comment.get("replies", [])) + } + + # Handle replies + replies = [] + if "replies" in raw_comment and isinstance(raw_comment["replies"], list): + for reply in raw_comment["replies"]: + if isinstance(reply, dict): + reply_created_at = datetime.utcnow() + if "timestamp" in reply: + try: + reply_created_at = datetime.fromtimestamp(reply["timestamp"] / 1000) + except (ValueError, TypeError): + pass + + replies.append({ + "platform_id": reply.get("id", ""), + "user_id": reply.get("ownerId", ""), + "user_name": reply.get("ownerUsername", ""), + "user_full_name": reply.get("ownerFullName"), + "user_profile_pic": reply.get("ownerProfilePicUrl"), + "user_verified": reply.get("ownerVerified", False), + "text": reply.get("text", ""), + "created_at": reply_created_at, + "likes_count": reply.get("likesCount", 0), + "replies_count": 0 # Instagram doesn't support nested replies + }) + + # Handle user details + user_details = None + if any(key in raw_comment for key in ["fbid", "is_mentionable", "latest_reel_media", "profile_pic_id"]): + user_details = { + "fbid_v2": raw_comment.get("fbid"), + "is_mentionable": raw_comment.get("is_mentionable"), + "latest_reel_media": raw_comment.get("latest_reel_media"), + "profile_pic_id": raw_comment.get("profile_pic_id") + } + + # Check for parent comment + is_reply = False + parent_comment_id = None + if "parentCommentId" in raw_comment: + is_reply = True + parent_comment_id = raw_comment["parentCommentId"] + + # Transform to application comment format + return { + "platform_id": comment_id, + "platform": self.platform_name, + "post_id": post_id, + "post_url": post_url, + "user_id": user_id, + "user_name": user_name, + "user_full_name": user_full_name, + "user_profile_pic": user_profile_pic, + "user_verified": user_verified, + "user_private": user_private, + "content": { + "text": text, + "media": [], # Instagram comments don't typically have media + "mentions": self.extract_mentions(text) + }, + "metadata": { + "created_at": created_at, + "language": "unknown", # Instagram doesn't provide language info + "is_reply": is_reply, + "parent_comment_id": parent_comment_id + }, + "engagement": engagement, + "replies": replies, + "user_details": user_details, + "analysis": None # Will be populated by analysis pipelines + } + + def transform_profile( + self, + raw_profile: Dict[str, Any] + ) -> Dict[str, Any]: + """ + Transform a raw Instagram profile from APIFY into the format expected by the repository. + + Args: + raw_profile: Raw profile data from APIFY + + Returns: + Transformed profile data + """ + # Extract handles and URLs + username = raw_profile.get("username", "") + profile_url = f"https://www.instagram.com/{username}/" if username else "" + + # Transform to application account format (for PostgreSQL update) + return { + "platform": Platform.INSTAGRAM, # Use enum value + "platform_id": raw_profile.get("id", ""), + "handle": username, + "name": raw_profile.get("fullName", ""), + "url": profile_url, + "verified": raw_profile.get("verified", False), + "follower_count": raw_profile.get("followersCount", 0), + "following_count": raw_profile.get("followsCount", 0) + # Note: political_entity_id is not included here as this is used for updates only + # The account should already exist with the political_entity_id set + } \ No newline at end of file diff --git a/backend/app/processing/collection/tiktok.py b/backend/app/processing/collection/tiktok.py new file mode 100644 index 0000000000..d79e5ddd5b --- /dev/null +++ b/backend/app/processing/collection/tiktok.py @@ -0,0 +1,541 @@ +""" +TikTok Data Collector + +This module provides a collector for TikTok data using APIFY's TikTok Scraper actor. +""" + +import re +import logging +from datetime import datetime +from typing import Any, Dict, List, Optional, Union +from uuid import UUID + +from app.core.config import settings +from app.processing.collection.base import BaseCollector +from app.services.repositories.social_media_account import SocialMediaAccountRepository +from app.processing.collection.apify_client import ApifyClient + +# Constants for APIFY actors +TIKTOK_SCRAPER_ACTOR_ID = "rH3CGsQVKPj35ePsK" # TikTok Scraper +TIKTOK_POST_SCRAPER_ACTOR_ID = "ZxHXJ2dyhbpx8TQyx" # TikTok Video Scraper +TIKTOK_COMMENT_SCRAPER_ACTOR_ID = "sJqYjqDcTKvF9TYrK" # TikTok Comment Scraper + +logger = logging.getLogger(__name__) + + +# Standalone transformation functions for testing purposes +def transform_profile(profile: Dict[str, Any]) -> Dict[str, Any]: + """ + Transform raw TikTok profile data into the format expected by the repository. + + Args: + profile: The raw TikTok profile data. + + Returns: + The transformed profile. + """ + # Extract basic information + profile_id = profile.get("id", "") + handle = profile.get("uniqueId", "") + name = profile.get("nickname", "") + bio = profile.get("signature", "") + verified = profile.get("verified", False) + private = profile.get("privateAccount", False) + + # Construct the profile URL + url = f"https://www.tiktok.com/@{handle}" + + # Extract follower and following counts + follower_count = profile.get("followerCount", 0) + following_count = profile.get("followingCount", 0) + + # Extract post count and total likes + post_count = profile.get("videoCount", 0) + total_likes = profile.get("heartCount", 0) + + # Extract profile picture URLs + profile_pic_url = profile.get("avatarMedium", "") + + # Construct the transformed profile + transformed_profile = { + "platform_id": profile_id, + "handle": handle, + "name": name, + "bio": bio, + "url": url, + "verified": verified, + "private": private, + "follower_count": follower_count, + "following_count": following_count, + "post_count": post_count, + "total_likes": total_likes, + "profile_pic_url": profile_pic_url + } + + return transformed_profile + +def transform_post(post: Dict[str, Any], account_id: str) -> Dict[str, Any]: + """ + Transform raw TikTok post data into the format expected by the repository. + + Args: + post: The raw TikTok post data. + account_id: The account ID this post belongs to. + + Returns: + The transformed post. + """ + # Extract basic information + post_id = post.get("id", "") + text = post.get("desc", "") + post_url = post.get("webVideoUrl", "") + + # Extract creation date and format it properly + created_at = None + if "createTime" in post: + try: + # TikTok provides timestamps in seconds since epoch + created_at = datetime.fromtimestamp(post.get("createTime", 0)) + except (ValueError, TypeError): + logger.warning(f"Invalid creation time for TikTok post {post_id}") + created_at = datetime.now() + + # Extract media URLs + media_urls = [] + if "videoUrl" in post and post["videoUrl"]: + media_urls.append(post["videoUrl"]) + + # Extract hashtags + hashtags = [] + if "hashtags" in post and isinstance(post["hashtags"], list): + hashtags = [tag["name"] for tag in post["hashtags"] if "name" in tag] + else: + # Try to extract hashtags from text + hashtag_pattern = r'#(\w+)' + hashtags = re.findall(hashtag_pattern, text) + + # Extract mentions + mentions = [] + mention_pattern = r'@(\w+)' + mentions = re.findall(mention_pattern, text) + + # Determine content type (TikTok posts are always videos) + content_type = "video" + + # Extract engagement metrics + likes_count = post.get("diggCount", 0) + comments_count = post.get("commentCount", 0) + shares_count = post.get("shareCount", 0) + views_count = post.get("playCount", 0) + saves_count = post.get("collectCount", 0) + + # Extract video metadata if available + dimensions = {"width": 0, "height": 0} + video_duration = 0 + + if "videoMeta" in post: + video_meta = post["videoMeta"] + dimensions["width"] = video_meta.get("width", 0) + dimensions["height"] = video_meta.get("height", 0) + video_duration = video_meta.get("duration", 0) + + # Extract author information + owner_info = {} + if "authorMeta" in post: + author = post["authorMeta"] + owner_info = { + "id": author.get("id", ""), + "username": author.get("name", ""), + "full_name": author.get("nickname", ""), + "verified": author.get("verified", False) + } + + # Extract thumbnail URL + thumbnail_url = "" + if "covers" in post and post["covers"] and isinstance(post["covers"], list): + thumbnail_url = post["covers"][0] + + # Construct the transformed post + transformed_post = { + "platform_id": post_id, + "platform": "tiktok", + "account_id": account_id, + "content_type": content_type, + "short_code": post_id, + "url": post_url, + "content": { + "text": text, + "media": media_urls, + "hashtags": hashtags, + "mentions": mentions + }, + "metadata": { + "created_at": created_at, + "dimensions": dimensions, + "alt_text": "", # TikTok does not provide alt text + "tagged_users": [], # TikTok does not provide tagged users in the API + "owner": owner_info + }, + "engagement": { + "likes_count": likes_count, + "comments_count": comments_count, + "shares_count": shares_count, + "views_count": views_count, + "saves_count": saves_count + }, + "video_data": { + "duration": video_duration, + "video_url": post.get("videoUrl", ""), + "thumbnail_url": thumbnail_url + } + } + + return transformed_post + +def transform_comment(comment: Dict[str, Any], post_id: str) -> Dict[str, Any]: + """ + Transform raw TikTok comment data into the format expected by the repository. + + Args: + comment: The raw TikTok comment data. + post_id: The post ID this comment belongs to. + + Returns: + The transformed comment. + """ + # Extract basic information + comment_id = comment.get("id", "") + text = comment.get("text", "") + + # Extract user information + user_info = comment.get("user", {}) + user_id = user_info.get("id", "") + user_name = user_info.get("uniqueId", "") + user_full_name = user_info.get("nickname", "") + user_profile_pic = user_info.get("avatarThumb", "") + user_verified = user_info.get("verified", False) + user_private = user_info.get("privateAccount", False) + + # Extract creation date and format it properly + created_at = None + if "createTime" in comment: + try: + # TikTok provides timestamps in seconds since epoch + created_at = datetime.fromtimestamp(comment.get("createTime", 0)) + except (ValueError, TypeError): + logger.warning(f"Invalid creation time for TikTok comment {comment_id}") + created_at = datetime.now() + + # Extract mentions + mentions = [] + mention_pattern = r'@(\w+)' + mentions = re.findall(mention_pattern, text) + + # Determine if this is a reply + is_reply = comment.get("isReply", False) + + # Extract engagement metrics + likes_count = comment.get("diggCount", 0) + replies_count = comment.get("replyCount", 0) + + # Process replies if available + replies = [] + if "replies" in comment and isinstance(comment["replies"], list): + for reply in comment["replies"]: + reply_created_at = None + try: + reply_created_at = datetime.fromtimestamp(reply.get("createTime", 0)) + except (ValueError, TypeError): + reply_created_at = datetime.now() + + replies.append({ + "platform_id": reply.get("id", ""), + "text": reply.get("text", ""), + "created_at": reply_created_at, + "user_id": reply.get("userId", ""), + "user_name": reply.get("uniqueId", ""), + "user_full_name": reply.get("nickname", ""), + "user_profile_pic": reply.get("avatarThumb", ""), + "user_verified": reply.get("verified", False), + "likes_count": reply.get("diggCount", 0) + }) + + # Construct the post URL from the post_id + post_url = f"https://www.tiktok.com/video/{post_id}" + + # Construct the transformed comment + transformed_comment = { + "platform_id": comment_id, + "platform": "tiktok", + "post_id": post_id, + "post_url": post_url, + "user_id": user_id, + "user_name": user_name, + "user_full_name": user_full_name, + "user_profile_pic": user_profile_pic, + "user_verified": user_verified, + "user_private": user_private, + "content": { + "text": text, + "mentions": mentions + }, + "metadata": { + "created_at": created_at, + "is_reply": is_reply + }, + "engagement": { + "likes_count": likes_count, + "replies_count": replies_count + }, + "replies": replies, + "user_details": { + "id": user_id, + "username": user_name, + "full_name": user_full_name, + "profile_pic_url": user_profile_pic, + "verified": user_verified, + "private": user_private + } + } + + return transformed_comment + + +class TikTokCollector(BaseCollector): + """ + TikTok data collector using APIFY's TikTok Scraper actor. + + This collector handles collecting posts, comments, and profile information + from TikTok accounts via APIFY, and transforms the data into the format + expected by the application's repositories. + """ + + def __init__(self, **kwargs): + """ + Initialize the TikTok collector. + + Args: + **kwargs: Additional arguments for the base collector. + """ + super().__init__(**kwargs) + self.platform_name = "tiktok" + self.actor_id = TIKTOK_SCRAPER_ACTOR_ID + self.post_actor_id = TIKTOK_POST_SCRAPER_ACTOR_ID + self.comment_actor_id = TIKTOK_COMMENT_SCRAPER_ACTOR_ID + self.api_key = settings.APIFY_API_KEY + self.account_repository = SocialMediaAccountRepository() + + # TikTok-specific default options + self.default_run_options = { + "maxPosts": settings.SCRAPING_MAX_POSTS, + "commentsPerPost": 0, # Don't collect comments during post collection + "shouldDownloadVideos": False, + "shouldDownloadCovers": False + } + + def _get_account_handle(self, account_id: Union[UUID, str]) -> str: + """ + Get the TikTok handle for a given account ID. + + Args: + account_id: UUID of the social media account + + Returns: + TikTok handle + + Raises: + ValueError: If the account is not found or has no handle + """ + account = self.account_repository.get(account_id) + + if not account: + raise ValueError(f"Social media account not found: {account_id}") + + if not account.handle: + raise ValueError(f"Account {account_id} has no TikTok handle") + + return account.handle + + def collect_posts( + self, + account_id: Union[UUID, str], + count: int = None, + since_date: datetime = None + ) -> List[Dict[str, Any]]: + """ + Collect videos from a TikTok account. + + Args: + account_id: UUID of the social media account to collect from + count: Maximum number of videos to collect (defaults to settings.SCRAPING_MAX_POSTS) + since_date: Only collect videos after this date (defaults to default date range) + + Returns: + List of MongoDB IDs for the collected posts + """ + handle = self._get_account_handle(account_id) + + max_count = count or self.max_items + start_date, _ = self.get_default_date_range() if not since_date else (since_date, datetime.utcnow()) + + logger.info(f"Collecting videos for TikTok account {handle} (max: {max_count}, since: {start_date})") + + # Create input for the APIFY task + input_data = { + "username": handle, + "maxPosts": max_count, + "downloadVideos": False, # We only need the video URLs, not the files + "proxyConfiguration": {"useApifyProxy": True}, + } + + # Add since date if provided + if since_date: + input_data["dateFrom"] = since_date.strftime("%Y-%m-%d") + + # Run the APIFY task + run_result = self._run_actor(self.post_actor_id, input_data) + + # Process the results + if not run_result or "items" not in run_result: + logger.warning(f"No posts found for TikTok account: {handle}") + return [] + + logger.info(f"Collected {len(run_result['items'])} videos for TikTok account {handle}") + + # Save posts to MongoDB + return self.save_posts(run_result['items'], account_id) + + def collect_comments( + self, + post_id: str, + count: int = None + ) -> List[Dict[str, Any]]: + """ + Collect comments for a TikTok post. + + Args: + post_id: MongoDB ID of the post to collect comments for + count: Maximum number of comments to collect (defaults to settings.SCRAPING_MAX_COMMENTS) + + Returns: + List of MongoDB IDs for the collected comments + """ + post = self.post_repository.get(post_id) + + if not post: + raise ValueError(f"Post not found: {post_id}") + + tiktok_post_id = post.get("platform_id") + if not tiktok_post_id: + raise ValueError(f"Invalid post platform ID for {post_id}") + + max_count = count or self.max_comments + + logger.info(f"Collecting comments for TikTok post {tiktok_post_id} (max: {max_count})") + + # Get the post URL + post_url = post.get("url") + if not post_url: + raise ValueError(f"No URL found for post {post_id}") + + # Create input for the APIFY task + input_data = { + "videoUrl": post_url, + "maxComments": max_count, + "maxReplies": 10, # Limit replies to avoid huge data volumes + "proxyConfiguration": {"useApifyProxy": True}, + } + + # Run the APIFY task + run_result = self._run_actor(self.comment_actor_id, input_data) + + # Process the results + if not run_result or "items" not in run_result: + logger.warning(f"No comments found for TikTok post: {post_url}") + return [] + + # Extract comments from results + comments = [] + for comment_data in run_result["items"]: + if "comments" in comment_data: + comments.extend(comment_data["comments"]) + + logger.info(f"Collected {len(comments)} comments for TikTok post {tiktok_post_id}") + + # Save comments to MongoDB + return self.save_comments(comments, post_id) + + def collect_profile( + self, + account_id: Union[UUID, str] + ) -> Dict[str, Any]: + """ + Collect profile information for a TikTok account. + + Args: + account_id: UUID of the social media account to collect profile for + + Returns: + Updated account information + """ + handle = self._get_account_handle(account_id) + + logger.info(f"Collecting profile information for TikTok account {handle}") + + # Create input for the APIFY task + input_data = { + "username": handle, + "proxyConfiguration": {"useApifyProxy": True}, + } + + # Run the APIFY task + run_result = self._run_actor(self.actor_id, input_data) + + # Process the results + if not run_result or "userInfo" not in run_result: + logger.warning(f"No profile found for TikTok account: {handle}") + return {} + + # TikTok profile info might be in the userInfo field + profile_info = run_result.get("userInfo", {}) + + if profile_info: + # Transform and update account + account_data = self.transform_profile(profile_info) + self.account_repository.update(account_id, account_data) + + logger.info(f"Updated profile information for TikTok account {handle}") + return account_data + + return {} + + def update_metrics( + self, + account_id: Union[UUID, str] + ) -> Dict[str, Any]: + """ + Update engagement metrics for a TikTok account. + + This performs the same function as collect_profile but is named separately + to match the interface requirements. + + Args: + account_id: UUID of the social media account to update metrics for + + Returns: + Updated account metrics + """ + # For TikTok, updating metrics is the same as collecting profile + return self.collect_profile(account_id) + + def transform_profile(self, profile: Dict[str, Any]) -> Dict[str, Any]: + """Use the standalone transform_profile function.""" + return transform_profile(profile) + + def transform_post(self, post: Dict[str, Any], account_id: str) -> Dict[str, Any]: + """Use the standalone transform_post function.""" + return transform_post(post, account_id) + + def transform_comment(self, comment: Dict[str, Any], post_id: str) -> Dict[str, Any]: + """Use the standalone transform_comment function.""" + return transform_comment(comment, post_id) \ No newline at end of file diff --git a/backend/app/processing/collection/twitter.py b/backend/app/processing/collection/twitter.py new file mode 100644 index 0000000000..fb326f51b1 --- /dev/null +++ b/backend/app/processing/collection/twitter.py @@ -0,0 +1,601 @@ +""" +Twitter Data Collector + +This module provides a collector for Twitter data using APIFY's Twitter Scraper actor. +""" + +import logging +from datetime import datetime +from typing import Any, Dict, List, Optional, Union +from uuid import UUID + +from app.core.config import settings +from app.processing.collection.base import BaseCollector +from app.services.repositories.social_media_account import SocialMediaAccountRepository + +logger = logging.getLogger(__name__) + + +class TwitterCollector(BaseCollector): + """ + Twitter data collector using APIFY's Twitter Scraper actor. + + This collector handles collecting posts, comments, and profile information + from Twitter/X accounts via APIFY, and transforms the data into the format + expected by the application's repositories. + """ + + def __init__(self, *args, **kwargs): + """Initialize the Twitter collector.""" + super().__init__(*args, **kwargs) + self.platform_name = "twitter" + self.actor_id = settings.APIFY_TWITTER_ACTOR_ID + self.account_repository = SocialMediaAccountRepository() + + # Twitter-specific default options + self.default_run_options = { + "maxItems": settings.SCRAPING_MAX_POSTS, + "includeReplies": False, + "includeRetweets": True, + "includeImages": True, + "includeVideos": True + } + + async def _get_account_handle(self, account_id: Union[UUID, str]) -> str: + """ + Get the Twitter handle for a given account ID. + + Args: + account_id: UUID of the social media account + + Returns: + Twitter handle + + Raises: + ValueError: If the account is not found or has no handle + """ + account = await self.account_repository.get(account_id) + + if not account: + raise ValueError(f"Social media account not found: {account_id}") + + if not account.handle: + raise ValueError(f"Account {account_id} has no Twitter handle") + + return account.handle + + async def collect_posts( + self, + account_id: Union[UUID, str], + count: int = None, + since_date: datetime = None + ) -> List[Dict[str, Any]]: + """ + Collect tweets from a Twitter account. + + Args: + account_id: UUID of the social media account to collect from + count: Maximum number of tweets to collect (defaults to settings.SCRAPING_MAX_POSTS) + since_date: Only collect tweets after this date (defaults to default date range) + + Returns: + List of MongoDB IDs for the collected tweets + """ + handle = await self._get_account_handle(account_id) + + max_count = count or self.max_items + start_date, _ = self.get_default_date_range() if not since_date else (since_date, datetime.utcnow()) + + logger.info(f"Collecting tweets for account {handle} (max: {max_count}, since: {start_date})") + + # Configure Twitter scraper input + run_input = self.prepare_run_input( + usernames=[handle], + maxItems=max_count, + dateFrom=start_date.strftime("%Y-%m-%d") + ) + + # Run actor and get results + results = await self.apify_client.start_and_wait_for_results( + actor_id=self.actor_id, + run_input=run_input, + limit=max_count + ) + + logger.info(f"Collected {len(results)} tweets for account {handle}") + + # Save posts to MongoDB + return await self.save_posts(results, account_id) + + async def collect_comments( + self, + post_id: str, + count: int = None + ) -> List[Dict[str, Any]]: + """ + Collect comments (replies) for a Twitter post. + + Args: + post_id: MongoDB ID of the post to collect comments for + count: Maximum number of comments to collect (defaults to settings.SCRAPING_MAX_COMMENTS) + + Returns: + List of MongoDB IDs for the collected comments + """ + post = await self.post_repository.get(post_id) + + if not post: + raise ValueError(f"Post not found: {post_id}") + + tweet_id = post.get("platform_id") + if not tweet_id: + raise ValueError(f"Invalid post platform ID for {post_id}") + + max_count = count or self.max_comments + + logger.info(f"Collecting comments for tweet {tweet_id} (max: {max_count})") + + # Configure Twitter scraper for comments + run_input = self.prepare_run_input( + tweetUrls=[f"https://twitter.com/i/status/{tweet_id}"], + maxReplies=max_count, + maxItems=1, # Just get the original tweet + includeReplies=True # Important - we want replies + ) + + # Run actor and get results + results = await self.apify_client.start_and_wait_for_results( + actor_id=self.actor_id, + run_input=run_input + ) + + # Extract replies from results + replies = [] + for tweet in results: + if "repliedTo" in tweet: + # Discard the main tweet and collect replies + replies.extend(tweet.get("replies", [])) + + logger.info(f"Collected {len(replies)} comments for tweet {tweet_id}") + + # Save comments to MongoDB + return await self.save_comments(replies, post_id) + + async def collect_profile( + self, + account_id: Union[UUID, str] + ) -> Dict[str, Any]: + """ + Collect profile information for a Twitter account. + + Args: + account_id: UUID of the social media account to collect profile for + + Returns: + Updated account information + """ + handle = await self._get_account_handle(account_id) + + logger.info(f"Collecting profile information for Twitter account {handle}") + + # Configure Twitter scraper for profile + run_input = self.prepare_run_input( + usernames=[handle], + maxItems=1, # Just need one post to get profile info + includeUserInfo=True + ) + + # Run actor and get results + results = await self.apify_client.start_and_wait_for_results( + actor_id=self.actor_id, + run_input=run_input, + limit=1 + ) + + if not results: + logger.warning(f"No profile information returned for Twitter account {handle}") + return {} + + # Twitter profile info is embedded in the tweet data + profile_info = results[0].get("user", {}) if results else {} + + if profile_info: + # Transform and update account + account_data = self.transform_profile(profile_info) + await self.account_repository.update(account_id, account_data) + + logger.info(f"Updated profile information for Twitter account {handle}") + return account_data + + return {} + + async def update_metrics( + self, + account_id: Union[UUID, str] + ) -> Dict[str, Any]: + """ + Update engagement metrics for a Twitter account. + + This performs the same function as collect_profile but is named separately + to match the interface requirements. + + Args: + account_id: UUID of the social media account to update metrics for + + Returns: + Updated account metrics + """ + # For Twitter, updating metrics is the same as collecting profile + return await self.collect_profile(account_id) + + def transform_post( + self, + raw_post: Dict[str, Any], + account_id: Union[UUID, str] + ) -> Dict[str, Any]: + """ + Transform a raw tweet from APIFY into the format expected by the repository. + + Args: + raw_post: Raw tweet data from APIFY + account_id: UUID of the social media account + + Returns: + Transformed post data + """ + # Extract basic information + post_id = raw_post.get("id", "") + text = raw_post.get("text", "") + post_url = raw_post.get("url", "") + + try: + created_at = datetime.strptime( + raw_post.get("createdAt", "").split(".")[0], + "%Y-%m-%dT%H:%M:%S" + ) if "createdAt" in raw_post else datetime.utcnow() + except (ValueError, TypeError): + # Handle alternative date formats + try: + created_at = datetime.strptime( + raw_post.get("createdAt", "").split("+")[0].strip(), + "%a %b %d %H:%M:%S %Y" + ) + except (ValueError, TypeError): + created_at = datetime.utcnow() + + # Extract media and links + media_urls = [] + dimensions = None + + # Extract media from extended entities if available + if "extendedEntities" in raw_post and "media" in raw_post["extendedEntities"]: + for media_item in raw_post["extendedEntities"]["media"]: + if "media_url_https" in media_item: + media_urls.append(media_item["media_url_https"]) + + # Extract dimensions from the first media item + if not dimensions and "sizes" in media_item and "large" in media_item["sizes"]: + dimensions = { + "width": media_item["sizes"]["large"].get("w", 0), + "height": media_item["sizes"]["large"].get("h", 0) + } + + # Extract regular media if extended entities not available + elif "media" in raw_post: + for media_item in raw_post.get("media", []): + if "url" in media_item: + media_urls.append(media_item["url"]) + + # Process hashtags and mentions from entities + hashtags = [] + mentions = [] + + if "entities" in raw_post: + entities = raw_post["entities"] + if "hashtags" in entities: + hashtags = [tag.get("text", "") for tag in entities.get("hashtags", []) if "text" in tag] + if "user_mentions" in entities: + mentions = [mention.get("screen_name", "") for mention in entities.get("user_mentions", []) if "screen_name" in mention] + else: + # Fallback to extraction from text + hashtags = self.extract_hashtags(text) + mentions = self.extract_mentions(text) + + # Extract links + links = [post_url] if post_url else [] + links.extend(self.extract_links(text)) + + # Determine content type + content_type = "post" + if raw_post.get("isRetweet", False): + content_type = "retweet" + elif raw_post.get("isQuote", False): + content_type = "quote" + elif raw_post.get("isReply", False): + content_type = "reply" + + # Check for media types + has_video = False + has_images = False + child_posts = None + video_data = None + + if "extendedEntities" in raw_post and "media" in raw_post["extendedEntities"]: + media_items = raw_post["extendedEntities"]["media"] + + # Check for videos + for item in media_items: + if item.get("type") == "video" or item.get("type") == "animated_gif": + has_video = True + break + elif item.get("type") == "photo": + has_images = True + + # For multiple images, create child posts + if len(media_items) > 1: + child_posts = [] + for i, item in enumerate(media_items): + child_post = { + "id": f"{post_id}_child_{i}", + "type": item.get("type", "Image"), + "url": post_url, + "display_url": item.get("media_url_https", "") + } + + # Add dimensions if available + if "sizes" in item and "large" in item["sizes"]: + child_post["dimensions"] = { + "width": item["sizes"]["large"].get("w", 0), + "height": item["sizes"]["large"].get("h", 0) + } + + child_posts.append(child_post) + + # For videos, extract video data + if has_video: + for item in media_items: + if item.get("type") == "video" or item.get("type") == "animated_gif": + video_url = None + thumbnail_url = item.get("media_url_https", "") + duration = None + + # Get video URL from variants + if "video_info" in item and "variants" in item["video_info"]: + variants = item["video_info"]["variants"] + best_bitrate = 0 + for variant in variants: + if "content_type" in variant and variant["content_type"].startswith("video/"): + if "bitrate" in variant and variant["bitrate"] > best_bitrate: + best_bitrate = variant["bitrate"] + video_url = variant.get("url", "") + + # Get duration + if "video_info" in item and "duration_millis" in item["video_info"]: + duration = item["video_info"]["duration_millis"] / 1000 # Convert to seconds + + video_data = { + "duration": duration, + "video_url": video_url, + "thumbnail_url": thumbnail_url, + "is_muted": item.get("type") == "animated_gif" # GIFs are typically muted + } + break + + # Update content type based on media + if content_type == "post": + if has_video: + content_type = "video" + elif len(media_urls) > 1: + content_type = "carousel" + elif has_images: + content_type = "image" + + # Extract engagement metrics + engagement = { + "likes_count": raw_post.get("likeCount", 0), + "shares_count": raw_post.get("retweetCount", 0), + "comments_count": raw_post.get("replyCount", 0), + "views_count": raw_post.get("viewCount", 0), + "engagement_rate": None, # Calculate if needed + "saves_count": raw_post.get("bookmarkCount", 0) # Twitter now tracks bookmarks + } + + # Extract alt text + alt_text = None + if "extendedEntities" in raw_post and "media" in raw_post["extendedEntities"]: + for item in raw_post["extendedEntities"]["media"]: + if "ext_alt_text" in item: + alt_text = item["ext_alt_text"] + break + + # Get tagged users if available + tagged_users = [] + if "entities" in raw_post and "user_mentions" in raw_post["entities"]: + for mention in raw_post["entities"]["user_mentions"]: + tagged_user = { + "username": mention.get("screen_name", ""), + "id": mention.get("id_str", ""), + "full_name": mention.get("name", ""), + "is_verified": False # Twitter API doesn't provide this in mentions + } + tagged_users.append(tagged_user) + + # Extract owner information + owner = None + if "author" in raw_post: + author = raw_post["author"] + owner = { + "username": author.get("userName", ""), + "id": author.get("id", ""), + "verified": author.get("isVerified", False) or author.get("isBlueVerified", False) + } + + # Transform to application post format + return { + "platform_id": post_id, + "platform": self.platform_name, + "account_id": str(account_id), + "content_type": content_type, + "short_code": post_id, # Twitter uses the ID as the short code + "url": post_url, + "content": { + "text": text, + "media": media_urls, + "links": links, + "hashtags": hashtags, + "mentions": mentions + }, + "metadata": { + "created_at": created_at, + "language": raw_post.get("lang", "unknown"), + "location": raw_post.get("place", None), + "client": raw_post.get("source", "Twitter"), + "is_repost": raw_post.get("isRetweet", False), + "is_reply": raw_post.get("isReply", False), + "dimensions": dimensions, + "alt_text": alt_text, + "tagged_users": tagged_users, + "owner": owner + }, + "engagement": engagement, + "child_posts": child_posts, + "video_data": video_data, + "analysis": None # Will be populated by analysis pipelines + } + + def transform_comment( + self, + raw_comment: Dict[str, Any], + post_id: str + ) -> Dict[str, Any]: + """ + Transform a raw Twitter reply from APIFY into the format expected by the repository. + + Args: + raw_comment: Raw comment data from APIFY + post_id: MongoDB ID of the parent post + + Returns: + Transformed comment data + """ + # Extract basic information + comment_id = raw_comment.get("id", "") + text = raw_comment.get("text", "") + + # Process author information + author = raw_comment.get("author", {}) + user_id = author.get("id", "") + user_name = author.get("userName", "") + user_full_name = author.get("name", "") + user_profile_pic = author.get("profilePicture", "") + user_verified = author.get("isVerified", False) or author.get("isBlueVerified", False) + user_private = False # Twitter API doesn't consistently provide this + + # Parse created_at date + try: + created_at = datetime.strptime( + raw_comment.get("createdAt", "").split(".")[0], + "%Y-%m-%dT%H:%M:%S" + ) if "createdAt" in raw_comment else datetime.utcnow() + except (ValueError, TypeError): + # Handle alternative date formats + try: + created_at = datetime.strptime( + raw_comment.get("createdAt", "").split("+")[0].strip(), + "%a %b %d %H:%M:%S %Y" + ) + except (ValueError, TypeError): + created_at = datetime.utcnow() + + # Extract media + media_urls = [] + if "extendedEntities" in raw_comment and "media" in raw_comment["extendedEntities"]: + for media_item in raw_comment["extendedEntities"]["media"]: + if "media_url_https" in media_item: + media_urls.append(media_item["media_url_https"]) + elif "media" in raw_comment: + for media_item in raw_comment.get("media", []): + if "url" in media_item: + media_urls.append(media_item["url"]) + + # Process mentions + mentions = [] + if "entities" in raw_comment and "user_mentions" in raw_comment["entities"]: + mentions = [mention.get("screen_name", "") for mention in raw_comment["entities"]["user_mentions"] if "screen_name" in mention] + else: + mentions = self.extract_mentions(text) + + # Extract engagement metrics + engagement = { + "likes_count": raw_comment.get("likeCount", 0), + "replies_count": raw_comment.get("replyCount", 0) + } + + # Get post URL + post_url = raw_comment.get("url", "").split("?")[0] if raw_comment.get("url") else None + + # Determine if this is a reply to another comment + is_reply = raw_comment.get("isReply", False) + parent_comment_id = raw_comment.get("inReplyToId", None) + + # Extract replies if available + replies = [] + + # Build user_details + user_details = { + "is_mentionable": True, + "profile_pic_id": None + } + + # Transform to application comment format + return { + "platform_id": comment_id, + "platform": self.platform_name, + "post_id": post_id, + "post_url": post_url, + "user_id": user_id, + "user_name": user_name, + "user_full_name": user_full_name, + "user_profile_pic": user_profile_pic, + "user_verified": user_verified, + "user_private": user_private, + "content": { + "text": text, + "media": media_urls, + "mentions": mentions + }, + "metadata": { + "created_at": created_at, + "language": raw_comment.get("lang", "unknown"), + "is_reply": is_reply, + "parent_comment_id": parent_comment_id + }, + "engagement": engagement, + "replies": replies, + "user_details": user_details, + "analysis": None # Will be populated by analysis pipelines + } + + def transform_profile( + self, + raw_profile: Dict[str, Any] + ) -> Dict[str, Any]: + """ + Transform a raw Twitter profile from APIFY into the format expected by the repository. + + Args: + raw_profile: Raw profile data from APIFY + + Returns: + Transformed profile data + """ + # Transform to application account format (for PostgreSQL update) + return { + "platform_id": raw_profile.get("id", ""), + "handle": raw_profile.get("username", ""), + "name": raw_profile.get("displayName", ""), + "url": f"https://twitter.com/{raw_profile.get('username', '')}", + "verified": raw_profile.get("verified", False), + "follower_count": raw_profile.get("followersCount", 0), + "following_count": raw_profile.get("followingCount", 0) + } \ No newline at end of file diff --git a/backend/app/services/__init__.py b/backend/app/services/__init__.py index 30c215bd0b..4e4179e785 100644 --- a/backend/app/services/__init__.py +++ b/backend/app/services/__init__.py @@ -1,4 +1,10 @@ from app.services import item, user, political_entity, social_media_account, entity_relationship from app.services.repositories import ItemRepository, UserRepository +from app.services.vector_embedding import VectorEmbeddingService +from app.services.similarity_search import SimilaritySearchService -__all__ = ["item", "user", "political_entity", "social_media_account", "entity_relationship", "ItemRepository", "UserRepository"] \ No newline at end of file +__all__ = [ + "item", "user", "political_entity", "social_media_account", "entity_relationship", + "ItemRepository", "UserRepository", + "VectorEmbeddingService", "SimilaritySearchService" +] \ No newline at end of file diff --git a/backend/app/services/redis_service.py b/backend/app/services/redis_service.py index 9c65f7334c..9db573fec3 100644 --- a/backend/app/services/redis_service.py +++ b/backend/app/services/redis_service.py @@ -4,6 +4,11 @@ This module provides service classes for interacting with Redis, implementing key generation, common operations, and caching strategies for the Political Social Media Analysis Platform. + +NOTE: NOT USED IN MVP - This implementation is reserved for future releases. +Redis functionality is disabled in the MVP to simplify initial deployment. +When activating Redis, ensure the appropriate dependencies are installed +and the feature flags are enabled in the application configuration. """ import json diff --git a/backend/app/services/repositories/__init__.py b/backend/app/services/repositories/__init__.py index c4b1e289ff..effd42d8eb 100644 --- a/backend/app/services/repositories/__init__.py +++ b/backend/app/services/repositories/__init__.py @@ -6,6 +6,7 @@ from app.services.repositories.post_repository import PostRepository from app.services.repositories.comment_repository import CommentRepository from app.services.repositories.metrics_repository import MetricsRepository +from app.services.repositories.topic_repository import TopicRepository __all__ = [ "UserRepository", @@ -16,4 +17,5 @@ "PostRepository", "CommentRepository", "MetricsRepository", + "TopicRepository", ] \ No newline at end of file diff --git a/backend/app/services/repositories/topic_repository.py b/backend/app/services/repositories/topic_repository.py new file mode 100644 index 0000000000..846fc9b686 --- /dev/null +++ b/backend/app/services/repositories/topic_repository.py @@ -0,0 +1,921 @@ +""" +Repository for topic analysis stored in MongoDB. + +This module provides a repository for CRUD operations and queries on topic analysis data +stored in MongoDB as part of the Political Social Media Analysis Platform. +""" + +from datetime import datetime, timedelta +from typing import Dict, List, Optional, Any, Union, Literal +from uuid import UUID + +import motor.motor_asyncio +from bson import ObjectId +from fastapi import Depends +from pymongo import ReturnDocument + +from app.db.connections import get_mongodb +from app.db.schemas.mongodb import TopicAnalysis, TopicOccurrence, TopicTrend + + +class TopicRepository: + """ + Repository for topic analysis data stored in MongoDB. + + This repository provides methods for CRUD operations and specialized queries + on topic analysis data stored in the MongoDB database, including topics, + topic occurrences, and topic trends. + """ + + def __init__(self, db: motor.motor_asyncio.AsyncIOMotorDatabase = None): + """ + Initialize the repository with a MongoDB database connection. + + Args: + db: MongoDB database connection. If None, a connection will be + established when methods are called. + """ + self._db = db + self._topics_collection = "topics" + self._occurrences_collection = "topic_occurrences" + self._trends_collection = "topic_trends" + + @property + async def topics_collection(self) -> motor.motor_asyncio.AsyncIOMotorCollection: + """Get the topics collection, ensuring a database connection exists.""" + db = self._db + if db is None: + async with get_mongodb() as db: + return db[self._topics_collection] + return db[self._topics_collection] + + @property + async def occurrences_collection(self) -> motor.motor_asyncio.AsyncIOMotorCollection: + """Get the topic occurrences collection, ensuring a database connection exists.""" + db = self._db + if db is None: + async with get_mongodb() as db: + return db[self._occurrences_collection] + return db[self._occurrences_collection] + + @property + async def trends_collection(self) -> motor.motor_asyncio.AsyncIOMotorCollection: + """Get the topic trends collection, ensuring a database connection exists.""" + db = self._db + if db is None: + async with get_mongodb() as db: + return db[self._trends_collection] + return db[self._trends_collection] + + # --- Topic CRUD Operations --- + + async def create_topic(self, topic_data: Dict[str, Any]) -> str: + """ + Create a new topic for analysis. + + Args: + topic_data: Dictionary with topic data following the TopicAnalysis schema + + Returns: + The ID of the created topic + """ + collection = await self.topics_collection + + # Set timestamps if not provided + if "created_at" not in topic_data: + topic_data["created_at"] = datetime.utcnow() + if "updated_at" not in topic_data: + topic_data["updated_at"] = datetime.utcnow() + + # Convert timestamps from string format if necessary + if isinstance(topic_data.get("created_at"), str): + topic_data["created_at"] = datetime.fromisoformat( + topic_data["created_at"].replace("Z", "+00:00") + ) + if isinstance(topic_data.get("updated_at"), str): + topic_data["updated_at"] = datetime.fromisoformat( + topic_data["updated_at"].replace("Z", "+00:00") + ) + + result = await collection.insert_one(topic_data) + return str(result.inserted_id) + + async def get_topic(self, topic_id: str) -> Optional[Dict[str, Any]]: + """ + Get a topic by ID. + + Args: + topic_id: The ID of the topic to retrieve + + Returns: + The topic data if found, None otherwise + """ + collection = await self.topics_collection + try: + # Try to find by MongoDB ObjectId + topic = await collection.find_one({"_id": ObjectId(topic_id)}) + if topic: + return topic + except: + pass + + # Try to find by custom topic_id field + topic = await collection.find_one({"topic_id": topic_id}) + return topic + + async def get_topic_by_name(self, name: str) -> Optional[Dict[str, Any]]: + """ + Get a topic by name (exact match). + + Args: + name: The name of the topic to retrieve + + Returns: + The topic data if found, None otherwise + """ + collection = await self.topics_collection + topic = await collection.find_one({"name": name}) + return topic + + async def list_topics( + self, + skip: int = 0, + limit: int = 100, + sort_by: str = "name", + sort_direction: int = 1 + ) -> List[Dict[str, Any]]: + """ + Get a list of topics with pagination and sorting options. + + Args: + skip: Number of topics to skip + limit: Maximum number of topics to return + sort_by: Field to sort by + sort_direction: Sort direction (1 for ascending, -1 for descending) + + Returns: + List of topics + """ + collection = await self.topics_collection + cursor = collection.find().skip(skip).limit(limit).sort(sort_by, sort_direction) + return await cursor.to_list(length=limit) + + async def update_topic( + self, + topic_id: str, + update_data: Dict[str, Any] + ) -> Optional[Dict[str, Any]]: + """ + Update a topic. + + Args: + topic_id: The ID of the topic to update + update_data: Dictionary with updated topic data + + Returns: + The updated topic data if found and updated, None otherwise + """ + collection = await self.topics_collection + + # Always update the updated_at timestamp + update_data["updated_at"] = datetime.utcnow() + + try: + # Try to update by MongoDB ObjectId + updated_topic = await collection.find_one_and_update( + {"_id": ObjectId(topic_id)}, + {"$set": update_data}, + return_document=ReturnDocument.AFTER + ) + if updated_topic: + return updated_topic + except: + pass + + # Try to update by custom topic_id field + updated_topic = await collection.find_one_and_update( + {"topic_id": topic_id}, + {"$set": update_data}, + return_document=ReturnDocument.AFTER + ) + return updated_topic + + async def delete_topic(self, topic_id: str) -> bool: + """ + Delete a topic. + + Args: + topic_id: The ID of the topic to delete + + Returns: + True if the topic was found and deleted, False otherwise + """ + collection = await self.topics_collection + + try: + # Try to delete by MongoDB ObjectId + result = await collection.delete_one({"_id": ObjectId(topic_id)}) + if result.deleted_count > 0: + return True + except: + pass + + # Try to delete by custom topic_id field + result = await collection.delete_one({"topic_id": topic_id}) + return result.deleted_count > 0 + + # --- Topic Search and Filtering --- + + async def find_topics_by_keywords( + self, + keywords: List[str], + match_all: bool = False, + skip: int = 0, + limit: int = 100 + ) -> List[Dict[str, Any]]: + """ + Find topics by matching keywords. + + Args: + keywords: List of keywords to match + match_all: If True, all keywords must match; if False, any keyword can match + skip: Number of topics to skip + limit: Maximum number of topics to return + + Returns: + List of matching topics + """ + collection = await self.topics_collection + + if match_all: + # All keywords must be in the keywords array + query = {"keywords": {"$all": keywords}} + else: + # Any of the keywords can be in the keywords array + query = {"keywords": {"$in": keywords}} + + cursor = collection.find(query).skip(skip).limit(limit) + return await cursor.to_list(length=limit) + + async def find_topics_by_text_search( + self, + search_text: str, + skip: int = 0, + limit: int = 100 + ) -> List[Dict[str, Any]]: + """ + Find topics using full-text search across names, keywords, and descriptions. + + Args: + search_text: Text to search for + skip: Number of topics to skip + limit: Maximum number of topics to return + + Returns: + List of matching topics with search score + """ + collection = await self.topics_collection + + # Ensure text index exists + await collection.create_index([ + ("name", "text"), + ("keywords", "text"), + ("description", "text") + ]) + + cursor = collection.find( + {"$text": {"$search": search_text}}, + {"score": {"$meta": "textScore"}} + ).sort([("score", {"$meta": "textScore"})]).skip(skip).limit(limit) + + return await cursor.to_list(length=limit) + + async def get_topics_by_category( + self, + category: str, + skip: int = 0, + limit: int = 100 + ) -> List[Dict[str, Any]]: + """ + Get topics by category. + + Args: + category: Category to filter by + skip: Number of topics to skip + limit: Maximum number of topics to return + + Returns: + List of topics in the specified category + """ + collection = await self.topics_collection + cursor = collection.find({"category": category}).skip(skip).limit(limit) + return await cursor.to_list(length=limit) + + # --- Topic Occurrence Operations --- + + async def record_topic_occurrence( + self, + occurrence_data: Dict[str, Any] + ) -> str: + """ + Record a topic occurrence in content. + + Args: + occurrence_data: Dictionary with occurrence data following the TopicOccurrence schema + + Returns: + The ID of the created occurrence record + """ + collection = await self.occurrences_collection + + # Set detected_at if not provided + if "detected_at" not in occurrence_data: + occurrence_data["detected_at"] = datetime.utcnow() + + # Convert timestamp from string format if necessary + if isinstance(occurrence_data.get("detected_at"), str): + occurrence_data["detected_at"] = datetime.fromisoformat( + occurrence_data["detected_at"].replace("Z", "+00:00") + ) + + result = await collection.insert_one(occurrence_data) + return str(result.inserted_id) + + async def get_topic_occurrences( + self, + topic_id: str, + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None, + content_type: Optional[str] = None, + skip: int = 0, + limit: int = 100 + ) -> List[Dict[str, Any]]: + """ + Get occurrences of a topic with optional filtering. + + Args: + topic_id: ID of the topic + start_date: Optional start date for filtering + end_date: Optional end date for filtering + content_type: Optional content type filter (post or comment) + skip: Number of occurrences to skip + limit: Maximum number of occurrences to return + + Returns: + List of topic occurrences + """ + collection = await self.occurrences_collection + + # Build query with required filters + query = {"topic_id": topic_id} + + # Add optional date range filter + if start_date or end_date: + date_filter = {} + if start_date: + date_filter["$gte"] = start_date + if end_date: + date_filter["$lte"] = end_date + if date_filter: + query["detected_at"] = date_filter + + # Add optional content type filter + if content_type: + query["content_type"] = content_type + + cursor = collection.find(query).skip(skip).limit(limit).sort("detected_at", -1) + return await cursor.to_list(length=limit) + + async def delete_topic_occurrences(self, topic_id: str) -> int: + """ + Delete all occurrences of a topic. + + Args: + topic_id: ID of the topic + + Returns: + Number of deleted occurrence records + """ + collection = await self.occurrences_collection + result = await collection.delete_many({"topic_id": topic_id}) + return result.deleted_count + + # --- Topic Trend Operations --- + + async def create_or_update_topic_trend( + self, + trend_data: Dict[str, Any] + ) -> str: + """ + Create or update a topic trend record. + + Args: + trend_data: Dictionary with trend data following the TopicTrend schema + + Returns: + The ID of the created or updated trend record + """ + collection = await self.trends_collection + + # Convert dates from string format if necessary + if isinstance(trend_data.get("start_date"), str): + trend_data["start_date"] = datetime.fromisoformat( + trend_data["start_date"].replace("Z", "+00:00") + ) + if isinstance(trend_data.get("end_date"), str): + trend_data["end_date"] = datetime.fromisoformat( + trend_data["end_date"].replace("Z", "+00:00") + ) + + # Check if a trend record already exists for this topic and time period + existing_trend = await collection.find_one({ + "topic_id": trend_data["topic_id"], + "time_period": trend_data["time_period"], + "start_date": trend_data["start_date"], + "end_date": trend_data["end_date"] + }) + + if existing_trend: + # Update existing trend + result = await collection.update_one( + {"_id": existing_trend["_id"]}, + {"$set": trend_data} + ) + return str(existing_trend["_id"]) + else: + # Create new trend + result = await collection.insert_one(trend_data) + return str(result.inserted_id) + + async def get_topic_trend( + self, + topic_id: str, + time_period: str, + start_date: datetime + ) -> Optional[Dict[str, Any]]: + """ + Get a specific topic trend. + + Args: + topic_id: ID of the topic + time_period: Time period (day, week, month) + start_date: Start date of the time period + + Returns: + The topic trend data if found, None otherwise + """ + collection = await self.trends_collection + trend = await collection.find_one({ + "topic_id": topic_id, + "time_period": time_period, + "start_date": start_date + }) + return trend + + async def get_topic_trends( + self, + topic_id: str, + time_period: str, + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None, + limit: int = 10 + ) -> List[Dict[str, Any]]: + """ + Get trends for a topic over time. + + Args: + topic_id: ID of the topic + time_period: Time period (day, week, month) + start_date: Optional start date for filtering + end_date: Optional end date for filtering + limit: Maximum number of trend records to return + + Returns: + List of topic trend records + """ + collection = await self.trends_collection + + # Build query with required filters + query = { + "topic_id": topic_id, + "time_period": time_period + } + + # Add optional date range filter + if start_date or end_date: + if start_date: + query["start_date"] = {"$gte": start_date} + if end_date: + query["end_date"] = {"$lte": end_date} + + cursor = collection.find(query).limit(limit).sort("start_date", -1) + return await cursor.to_list(length=limit) + + # --- Aggregation and Analysis Methods --- + + async def aggregate_topic_occurrences_by_time( + self, + topic_id: str, + time_period: Literal["day", "week", "month"], + start_date: datetime, + end_date: datetime + ) -> Dict[str, Any]: + """ + Aggregate topic occurrences by time period. + + Args: + topic_id: ID of the topic + time_period: Time period for aggregation (day, week, month) + start_date: Start date for the aggregation period + end_date: End date for the aggregation period + + Returns: + Aggregated data for the specified time period + """ + collection = await self.occurrences_collection + + # Determine the date grouping format based on time period + date_format = None + if time_period == "day": + date_format = "%Y-%m-%d" + elif time_period == "week": + date_format = "%Y-%U" # Year and week number + elif time_period == "month": + date_format = "%Y-%m" + + # Create aggregation pipeline + pipeline = [ + { + "$match": { + "topic_id": topic_id, + "detected_at": { + "$gte": start_date, + "$lte": end_date + } + } + }, + { + "$group": { + "_id": { + "date": {"$dateToString": {"format": date_format, "date": "$detected_at"}} + }, + "count": {"$sum": 1}, + "avg_sentiment": {"$avg": "$sentiment_context"}, + "content_ids": {"$push": "$content_id"} + } + }, + { + "$sort": { + "_id.date": 1 + } + } + ] + + results = await collection.aggregate(pipeline).to_list(None) + + # Format the results + formatted_results = { + "topic_id": topic_id, + "time_period": time_period, + "start_date": start_date, + "end_date": end_date, + "data_points": [] + } + + for result in results: + formatted_results["data_points"].append({ + "date": result["_id"]["date"], + "count": result["count"], + "avg_sentiment": result.get("avg_sentiment"), + "content_count": len(result.get("content_ids", [])) + }) + + return formatted_results + + async def find_trending_topics( + self, + start_date: datetime, + end_date: datetime, + limit: int = 10, + min_occurrences: int = 5 + ) -> List[Dict[str, Any]]: + """ + Find trending topics within a given timeframe. + + Args: + start_date: Start date for the trend period + end_date: End date for the trend period + limit: Maximum number of trending topics to return + min_occurrences: Minimum number of occurrences to be considered trending + + Returns: + List of trending topics with occurrence counts and growth rate + """ + collection = await self.occurrences_collection + topics_collection = await self.topics_collection + + # Define the comparison periods + current_period = { + "start": start_date, + "end": end_date + } + + period_duration = end_date - start_date + previous_period = { + "start": start_date - period_duration, + "end": start_date + } + + # Aggregate current period + current_pipeline = [ + { + "$match": { + "detected_at": { + "$gte": current_period["start"], + "$lte": current_period["end"] + } + } + }, + { + "$group": { + "_id": "$topic_id", + "current_count": {"$sum": 1}, + "avg_sentiment": {"$avg": "$sentiment_context"}, + "content_ids": {"$addToSet": "$content_id"} + } + }, + { + "$match": { + "current_count": {"$gte": min_occurrences} + } + } + ] + + current_results = await collection.aggregate(current_pipeline).to_list(None) + + # Create a mapping of topic_id to current data + topic_data = {result["_id"]: result for result in current_results} + + # Aggregate previous period for the same topics + if topic_data: + topic_ids = list(topic_data.keys()) + + previous_pipeline = [ + { + "$match": { + "topic_id": {"$in": topic_ids}, + "detected_at": { + "$gte": previous_period["start"], + "$lte": previous_period["end"] + } + } + }, + { + "$group": { + "_id": "$topic_id", + "previous_count": {"$sum": 1} + } + } + ] + + previous_results = await collection.aggregate(previous_pipeline).to_list(None) + + # Add previous data to the mapping + for result in previous_results: + if result["_id"] in topic_data: + topic_data[result["_id"]]["previous_count"] = result["previous_count"] + + # Calculate growth rates and add default previous_count if missing + for topic_id, data in topic_data.items(): + if "previous_count" not in data: + data["previous_count"] = 0 + + previous = data["previous_count"] or 1 # Avoid division by zero + data["growth_rate"] = (data["current_count"] - previous) / previous + data["engagement"] = len(data.get("content_ids", [])) + + # Get topic details + topic = await topics_collection.find_one({"topic_id": topic_id}) + if not topic: + topic = await topics_collection.find_one({"_id": ObjectId(topic_id)}) + + if topic: + data["name"] = topic.get("name", "Unknown Topic") + data["category"] = topic.get("category", "Uncategorized") + + # Clean up content_ids to reduce response size + if "content_ids" in data: + del data["content_ids"] + + # Sort by growth rate and then by current count + sorted_data = sorted( + topic_data.values(), + key=lambda x: (x["growth_rate"], x["current_count"]), + reverse=True + ) + + return sorted_data[:limit] + + return [] + + async def get_topic_sentiment_analysis( + self, + topic_id: str, + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None + ) -> Dict[str, Any]: + """ + Get sentiment analysis for a topic. + + Args: + topic_id: ID of the topic + start_date: Optional start date for filtering + end_date: Optional end date for filtering + + Returns: + Sentiment analysis data for the topic + """ + collection = await self.occurrences_collection + + # Build query with required filters + query = {"topic_id": topic_id} + + # Add optional date range filter + if start_date or end_date: + date_filter = {} + if start_date: + date_filter["$gte"] = start_date + if end_date: + date_filter["$lte"] = end_date + if date_filter: + query["detected_at"] = date_filter + + # Create aggregation pipeline for sentiment analysis + pipeline = [ + {"$match": query}, + { + "$group": { + "_id": None, + "avg_sentiment": {"$avg": "$sentiment_context"}, + "count": {"$sum": 1}, + "sentiments": {"$push": "$sentiment_context"} + } + } + ] + + results = await collection.aggregate(pipeline).to_list(None) + + if not results: + return { + "topic_id": topic_id, + "count": 0, + "avg_sentiment": None, + "sentiment_distribution": {} + } + + result = results[0] + + # Calculate sentiment distribution + sentiments = [s for s in result.get("sentiments", []) if s is not None] + distribution = { + "positive": 0, + "neutral": 0, + "negative": 0 + } + + for sentiment in sentiments: + if sentiment > 0.3: + distribution["positive"] += 1 + elif sentiment < -0.3: + distribution["negative"] += 1 + else: + distribution["neutral"] += 1 + + # Convert to percentages + total = len(sentiments) or 1 # Avoid division by zero + for key in distribution: + distribution[key] = round((distribution[key] / total) * 100, 2) + + return { + "topic_id": topic_id, + "count": result["count"], + "avg_sentiment": result.get("avg_sentiment"), + "sentiment_distribution": distribution + } + + async def find_related_topics( + self, + topic_id: str, + min_co_occurrences: int = 3, + limit: int = 10 + ) -> List[Dict[str, Any]]: + """ + Find related topics based on content co-occurrence. + + Args: + topic_id: ID of the topic + min_co_occurrences: Minimum number of co-occurrences to be considered related + limit: Maximum number of related topics to return + + Returns: + List of related topics with co-occurrence counts + """ + occurrences_collection = await self.occurrences_collection + topics_collection = await self.topics_collection + + # Get content IDs where the topic occurs + query = {"topic_id": topic_id} + cursor = occurrences_collection.find(query, {"content_id": 1}) + topic_content_ids = [doc["content_id"] for doc in await cursor.to_list(None)] + + if not topic_content_ids: + return [] + + # Find other topics that occur in the same content + pipeline = [ + { + "$match": { + "content_id": {"$in": topic_content_ids}, + "topic_id": {"$ne": topic_id} + } + }, + { + "$group": { + "_id": "$topic_id", + "co_occurrence_count": {"$sum": 1}, + "content_ids": {"$addToSet": "$content_id"} + } + }, + { + "$match": { + "co_occurrence_count": {"$gte": min_co_occurrences} + } + }, + { + "$sort": { + "co_occurrence_count": -1 + } + }, + { + "$limit": limit + } + ] + + related_topics = await occurrences_collection.aggregate(pipeline).to_list(None) + + # Get topic details for the related topics + for related_topic in related_topics: + topic = await topics_collection.find_one({"topic_id": related_topic["_id"]}) + if not topic: + topic = await topics_collection.find_one({"_id": ObjectId(related_topic["_id"])}) + + if topic: + related_topic["name"] = topic.get("name", "Unknown Topic") + related_topic["category"] = topic.get("category", "Uncategorized") + related_topic["keywords"] = topic.get("keywords", []) + + # Calculate correlation strength (percentage of content overlap) + related_topic["correlation_strength"] = round( + len(related_topic.get("content_ids", [])) / len(topic_content_ids) * 100, 2 + ) + + # Clean up content_ids to reduce response size + if "content_ids" in related_topic: + del related_topic["content_ids"] + + return related_topics + + async def aggregate_topics_by_entity( + self, + entity_id: Union[UUID, str], + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None, + limit: int = 10 + ) -> List[Dict[str, Any]]: + """ + Aggregate topics by political entity. + + Args: + entity_id: UUID of the political entity + start_date: Optional start date for filtering + end_date: Optional end date for filtering + limit: Maximum number of topics to return + + Returns: + List of topics with occurrence and sentiment data for the entity + """ + # Implementation would require joining data from PostgreSQL entities + # with MongoDB topic occurrences, which is beyond the scope of this implementation + # This would involve first getting posts from the entity, then analyzing topics + # For now, we'll return a placeholder result + + return [ + { + "topic_id": "sample_topic_1", + "topic_name": "Sample Topic 1", + "category": "Economy", + "occurrences": 25, + "avg_sentiment": 0.45, + "most_recent_occurrence": datetime.utcnow() - timedelta(days=1) + } + ] \ No newline at end of file diff --git a/backend/app/services/similarity_search.py b/backend/app/services/similarity_search.py new file mode 100644 index 0000000000..5bd223cc3f --- /dev/null +++ b/backend/app/services/similarity_search.py @@ -0,0 +1,570 @@ +""" +Similarity search service for finding similar content using vector embeddings. + +This module provides a service for performing similarity searches against +Pinecone vector database in the Political Social Media Analysis Platform. +It works with embeddings generated by OpenAI's embedding models, which +provide excellent performance for multilingual content including Spanish. +""" + +import asyncio +import logging +from datetime import datetime +from functools import partial +from typing import Any, Dict, List, Optional, Tuple, Union, cast +from uuid import UUID +from concurrent.futures import ThreadPoolExecutor + +import backoff +import numpy as np +# Import pinecone dynamically when needed +# from pinecone.exceptions import PineconeException + +from app.core.config import settings +from app.db.connections import get_pinecone +from app.services.vector_embedding import VectorEmbeddingService + + +logger = logging.getLogger(__name__) + + +class SimilaritySearchService: + """ + Service for performing similarity searches using vector embeddings. + + This service provides methods for finding similar content in the Pinecone vector + database, with options for filtering by metadata and configuring search parameters. + """ + + def __init__( + self, + pinecone_index=None, + embedding_service: Optional[VectorEmbeddingService] = None, + default_top_k: int = 10, + default_threshold: float = 0.7, + max_workers: int = 4 + ): + """ + Initialize the similarity search service. + + Args: + pinecone_index: Pinecone index instance. If None, gets the default index. + embedding_service: VectorEmbeddingService instance for generating embeddings. + If None, creates a new instance. + default_top_k: Default number of results to return. + default_threshold: Default similarity threshold (0.0 to 1.0). + max_workers: Maximum number of worker threads for parallel processing. + """ + self._pinecone_index = pinecone_index or get_pinecone() + self._pinecone_available = self._pinecone_index is not None + self._embedding_service = embedding_service or VectorEmbeddingService(pinecone_index=self._pinecone_index) + self._default_top_k = default_top_k + self._default_threshold = default_threshold + self._executor = ThreadPoolExecutor(max_workers=max_workers) + + if not self._pinecone_available: + logger.warning("Pinecone is not available - similarity search operations will be disabled") + + @staticmethod + def _build_filter_dict( + content_type: Optional[str] = None, + platform: Optional[str] = None, + account_id: Optional[Union[UUID, str]] = None, + entity_id: Optional[Union[UUID, str]] = None, + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None, + additional_filters: Optional[Dict[str, Any]] = None + ) -> Dict[str, Any]: + """ + Build a filter dictionary for Pinecone queries. + + Args: + content_type: Optional filter by content type ('post' or 'comment'). + platform: Optional filter by platform. + account_id: Optional filter by account ID. + entity_id: Optional filter by entity ID. + start_date: Optional filter for content created after this date. + end_date: Optional filter for content created before this date. + additional_filters: Optional additional filter conditions. + + Returns: + A dictionary with filter conditions for Pinecone queries. + """ + filter_dict: Dict[str, Any] = {} + + if content_type: + filter_dict["content_type"] = content_type + + if platform: + filter_dict["platform"] = platform + + if account_id: + filter_dict["account_id"] = str(account_id) + + if entity_id: + filter_dict["entity_id"] = str(entity_id) + + # Date range filter + if start_date or end_date: + date_filter = {} + + if start_date: + date_filter["$gte"] = start_date.isoformat() + + if end_date: + date_filter["$lte"] = end_date.isoformat() + + if date_filter: + filter_dict["created_at"] = date_filter + + # Add additional filters + if additional_filters: + filter_dict.update(additional_filters) + + return filter_dict + + @backoff.on_exception( + backoff.expo, + Exception, + max_tries=5, + jitter=backoff.full_jitter + ) + async def search_by_text( + self, + query_text: str, + top_k: Optional[int] = None, + threshold: Optional[float] = None, + namespace: str = "", + content_type: Optional[str] = None, + platform: Optional[str] = None, + account_id: Optional[Union[UUID, str]] = None, + entity_id: Optional[Union[UUID, str]] = None, + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None, + additional_filters: Optional[Dict[str, Any]] = None, + include_metadata: bool = True, + include_values: bool = False + ) -> List[Dict[str, Any]]: + """ + Search for similar content using a text query. + + Args: + query_text: The text to search for similar content. + top_k: Maximum number of results to return. + threshold: Minimum similarity score (0.0 to 1.0) for results. + namespace: Optional namespace to search in. + content_type: Optional filter by content type ('post' or 'comment'). + platform: Optional filter by platform. + account_id: Optional filter by account ID. + entity_id: Optional filter by entity ID. + start_date: Optional filter for content created after this date. + end_date: Optional filter for content created before this date. + additional_filters: Optional additional filter conditions. + include_metadata: Whether to include metadata in results. + include_values: Whether to include vector values in results. + + Returns: + List of dictionaries containing match results. + + Raises: + Exception: If there's an error from the Pinecone API. + ConnectionError: If there's a connection error. + ValueError: If Pinecone is not available. + """ + if not self._pinecone_available or not self._pinecone_index: + logger.error("Pinecone not available - cannot perform similarity search") + return [] + + if not query_text.strip(): + logger.warning("Empty query text provided for similarity search") + return [] + + try: + # Generate embedding for the query text + query_embedding = await self._embedding_service.generate_embedding(query_text) + except Exception as e: + logger.error(f"Error generating embedding for query text: {str(e)}") + raise + + # Call search by vector with the generated embedding + return await self.search_by_vector( + query_vector=query_embedding, + top_k=top_k, + threshold=threshold, + namespace=namespace, + content_type=content_type, + platform=platform, + account_id=account_id, + entity_id=entity_id, + start_date=start_date, + end_date=end_date, + additional_filters=additional_filters, + include_metadata=include_metadata, + include_values=include_values + ) + + @backoff.on_exception( + backoff.expo, + (Exception, ConnectionError), + max_tries=5, + jitter=backoff.full_jitter + ) + async def search_by_vector( + self, + query_vector: np.ndarray, + top_k: Optional[int] = None, + threshold: Optional[float] = None, + namespace: str = "", + content_type: Optional[str] = None, + platform: Optional[str] = None, + account_id: Optional[Union[UUID, str]] = None, + entity_id: Optional[Union[UUID, str]] = None, + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None, + additional_filters: Optional[Dict[str, Any]] = None, + include_metadata: bool = True, + include_values: bool = False + ) -> List[Dict[str, Any]]: + """ + Search for similar content using a vector. + + Args: + query_vector: The vector to search for similar content. + top_k: Maximum number of results to return. + threshold: Minimum similarity score (0.0 to 1.0) for results. + namespace: Optional namespace to search in. + content_type: Optional filter by content type ('post' or 'comment'). + platform: Optional filter by platform. + account_id: Optional filter by account ID. + entity_id: Optional filter by entity ID. + start_date: Optional filter for content created after this date. + end_date: Optional filter for content created before this date. + additional_filters: Optional additional filter conditions. + include_metadata: Whether to include metadata in results. + include_values: Whether to include vector values in results. + + Returns: + List of dictionaries containing match results. + + Raises: + Exception: If there's an error from the Pinecone API. + ConnectionError: If there's a connection error. + """ + # Apply defaults + top_k = top_k if top_k is not None else self._default_top_k + threshold = threshold if threshold is not None else self._default_threshold + + # Build filter dictionary + filter_dict = self._build_filter_dict( + content_type=content_type, + platform=platform, + account_id=account_id, + entity_id=entity_id, + start_date=start_date, + end_date=end_date, + additional_filters=additional_filters + ) + + try: + # Pinecone query is synchronous, so run in thread pool + loop = asyncio.get_event_loop() + + # Convert numpy array to list for Pinecone + vector_values = query_vector.tolist() + + response = await loop.run_in_executor( + self._executor, + partial( + self._pinecone_index.query, + vector=vector_values, + top_k=top_k, + namespace=namespace, + filter=filter_dict if filter_dict else None, + include_metadata=include_metadata, + include_values=include_values + ) + ) + + # Process and filter results by threshold + matches = [] + + for match in response.matches: + if match.score >= threshold: + match_dict = { + "id": match.id, + "score": match.score + } + + if include_metadata and hasattr(match, "metadata") and match.metadata: + match_dict["metadata"] = match.metadata + + if include_values and hasattr(match, "values") and match.values: + match_dict["values"] = match.values + + matches.append(match_dict) + + return matches + except Exception as e: + logger.error(f"Error in vector search: {str(e)}") + raise + + @backoff.on_exception( + backoff.expo, + (Exception, ConnectionError), + max_tries=5, + jitter=backoff.full_jitter + ) + async def search_by_vector_id( + self, + vector_id: str, + top_k: Optional[int] = None, + threshold: Optional[float] = None, + namespace: str = "", + content_type: Optional[str] = None, + platform: Optional[str] = None, + account_id: Optional[Union[UUID, str]] = None, + entity_id: Optional[Union[UUID, str]] = None, + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None, + additional_filters: Optional[Dict[str, Any]] = None, + include_metadata: bool = True, + include_values: bool = False + ) -> List[Dict[str, Any]]: + """ + Search for similar content using an existing vector ID. + + Args: + vector_id: ID of an existing vector to use for the search. + top_k: Maximum number of results to return. + threshold: Minimum similarity score (0.0 to 1.0) for results. + namespace: Optional namespace to search in. + content_type: Optional filter by content type ('post' or 'comment'). + platform: Optional filter by platform. + account_id: Optional filter by account ID. + entity_id: Optional filter by entity ID. + start_date: Optional filter for content created after this date. + end_date: Optional filter for content created before this date. + additional_filters: Optional additional filter conditions. + include_metadata: Whether to include metadata in results. + include_values: Whether to include vector values in results. + + Returns: + List of dictionaries containing match results. + + Raises: + Exception: If there's an error from the Pinecone API. + ConnectionError: If there's a connection error. + ValueError: If the specified vector ID doesn't exist. + """ + try: + # First, fetch the vector from Pinecone + loop = asyncio.get_event_loop() + + fetch_response = await loop.run_in_executor( + self._executor, + partial( + self._pinecone_index.fetch, + ids=[vector_id], + namespace=namespace + ) + ) + + if vector_id not in fetch_response.vectors: + raise ValueError(f"Vector with ID {vector_id} not found") + + # Use the fetched vector for the search + vector_values = fetch_response.vectors[vector_id].values + + # Call search by vector with the fetched vector + return await self.search_by_vector( + query_vector=np.array(vector_values), + top_k=top_k, + threshold=threshold, + namespace=namespace, + content_type=content_type, + platform=platform, + account_id=account_id, + entity_id=entity_id, + start_date=start_date, + end_date=end_date, + additional_filters=additional_filters, + include_metadata=include_metadata, + include_values=include_values + ) + except ValueError: + # Re-raise ValueError for client handling + raise + except Exception as e: + logger.error(f"Error in vector ID search: {str(e)}") + raise + + async def search_and_get_ids_only( + self, + query_text: str, + **kwargs + ) -> List[str]: + """ + Search for similar content and return only the vector IDs. + + Args: + query_text: The text to search for similar content. + **kwargs: Additional arguments for search_by_text. + + Returns: + List of vector IDs of matching content. + """ + results = await self.search_by_text( + query_text=query_text, + include_metadata=False, + include_values=False, + **kwargs + ) + + return [result["id"] for result in results] + + async def filter_by_metadata( + self, + content_type: Optional[str] = None, + platform: Optional[str] = None, + account_id: Optional[Union[UUID, str]] = None, + entity_id: Optional[Union[UUID, str]] = None, + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None, + additional_filters: Optional[Dict[str, Any]] = None, + namespace: str = "", + limit: int = 100, + include_metadata: bool = True + ) -> List[Dict[str, Any]]: + """ + Filter vectors by metadata without performing a similarity search. + + This is useful for finding content based on metadata criteria alone. + + Args: + content_type: Optional filter by content type ('post' or 'comment'). + platform: Optional filter by platform. + account_id: Optional filter by account ID. + entity_id: Optional filter by entity ID. + start_date: Optional filter for content created after this date. + end_date: Optional filter for content created before this date. + additional_filters: Optional additional filter conditions. + namespace: Optional namespace to search in. + limit: Maximum number of results to return. + include_metadata: Whether to include metadata in results. + + Returns: + List of dictionaries containing match results. + + Raises: + Exception: If there's an error from the Pinecone API. + ConnectionError: If there's a connection error. + """ + # Build filter dictionary + filter_dict = self._build_filter_dict( + content_type=content_type, + platform=platform, + account_id=account_id, + entity_id=entity_id, + start_date=start_date, + end_date=end_date, + additional_filters=additional_filters + ) + + if not filter_dict: + logger.warning("No filter criteria provided for metadata search") + return [] + + try: + # Create a dummy vector for the query + # This is needed because Pinecone doesn't have a pure metadata query API + dummy_vector = [0.0] * settings.OPENAI_EMBEDDING_DIMENSION # Use dimension from settings + + # Pinecone query is synchronous, so run in thread pool + loop = asyncio.get_event_loop() + + response = await loop.run_in_executor( + self._executor, + partial( + self._pinecone_index.query, + vector=dummy_vector, + top_k=limit, + namespace=namespace, + filter=filter_dict, + include_metadata=include_metadata, + include_values=False + ) + ) + + # Process results + matches = [] + + for match in response.matches: + match_dict = { + "id": match.id, + } + + if include_metadata and hasattr(match, "metadata") and match.metadata: + match_dict["metadata"] = match.metadata + + matches.append(match_dict) + + return matches + except Exception as e: + logger.error(f"Error in metadata filter search: {str(e)}") + raise + + @backoff.on_exception( + backoff.expo, + (Exception, ConnectionError), + max_tries=5, + jitter=backoff.full_jitter + ) + async def get_stats( + self, + namespace: str = "" + ) -> Dict[str, Any]: + """ + Get statistics about the index. + + Args: + namespace: Optional namespace to get statistics for. + + Returns: + Dictionary containing index statistics. + + Raises: + Exception: If there's an error from the Pinecone API. + ConnectionError: If there's a connection error. + """ + try: + # Pinecone describe_index_stats is synchronous, so run in thread pool + loop = asyncio.get_event_loop() + + response = await loop.run_in_executor( + self._executor, + partial( + self._pinecone_index.describe_index_stats, + filter={} if not namespace else {"namespace": namespace} + ) + ) + + # Convert to dictionary + result = { + "dimension": response.dimension, + "index_fullness": response.index_fullness, + "namespaces": response.namespaces + } + + return result + except Exception as e: + logger.error(f"Error getting index stats: {str(e)}") + raise + + async def close(self): + """ + Clean up resources used by the service. + + This should be called when the service is no longer needed to ensure + proper cleanup of resources. + """ + await self._embedding_service.close() \ No newline at end of file diff --git a/backend/app/services/vector_embedding.py b/backend/app/services/vector_embedding.py new file mode 100644 index 0000000000..abc9196ae8 --- /dev/null +++ b/backend/app/services/vector_embedding.py @@ -0,0 +1,715 @@ +""" +Vector embedding service for generating and managing embeddings in Pinecone. + +This module provides a service for generating text embeddings using OpenAI's +embedding models and managing them in the Pinecone vector database for the +Political Social Media Analysis Platform. +""" + +import asyncio +import logging +from concurrent.futures import ThreadPoolExecutor +from datetime import datetime +from functools import partial +from typing import Any, Dict, List, Optional, Tuple, Union +from uuid import UUID + +import backoff +import numpy as np +from openai import AsyncOpenAI, OpenAIError +# Import exceptions without importing the Pinecone package +# Exceptions will be handled generically to avoid import issues + +from app.core.config import settings +from app.db.connections import get_pinecone + + +logger = logging.getLogger(__name__) + + +class VectorEmbeddingService: + """ + Service for generating and managing text embeddings in Pinecone. + + This service provides methods for generating embeddings from text content using + OpenAI's API, storing them in Pinecone, and managing their lifecycle, including + updates and deletions. + """ + + def __init__( + self, + pinecone_index=None, + model_name: Optional[str] = None, + batch_size: int = 32, + max_workers: int = 4 + ): + """ + Initialize the vector embedding service. + + Args: + pinecone_index: Pinecone index instance. If None, gets the default index. + model_name: Name of the OpenAI embedding model to use. If None, + uses the default from settings. + batch_size: Maximum number of embeddings to process in a batch. + max_workers: Maximum number of worker threads for parallel processing. + """ + self._pinecone_index = pinecone_index or get_pinecone() + self._pinecone_available = self._pinecone_index is not None + self._model_name = model_name or settings.OPENAI_MODEL + self._batch_size = batch_size + self._executor = ThreadPoolExecutor(max_workers=max_workers) + + # Vector dimension from settings + self._vector_dimension = settings.OPENAI_EMBEDDING_DIMENSION + + # Initialize OpenAI client if API key is available + self._openai_client = None + if settings.OPENAI_API_KEY: + self._openai_client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) + + if not self._pinecone_available: + logger.warning("Pinecone is not available - vector storage operations will be disabled") + + if not self._openai_client: + logger.warning("OpenAI API key not provided - embedding generation will be disabled") + + @backoff.on_exception( + backoff.expo, + OpenAIError, + max_tries=5, + jitter=backoff.full_jitter + ) + async def generate_embedding(self, text: str) -> np.ndarray: + """ + Generate embedding for a single text input using OpenAI. + + Args: + text: The text to generate an embedding for. + + Returns: + The generated embedding vector as a numpy array. + + Raises: + OpenAIError: If there's an error from the OpenAI API. + ValueError: If OpenAI client is not initialized. + """ + if not text.strip(): + logger.warning("Attempt to generate embedding for empty text") + # Return zero vector with correct dimension + return np.zeros(self._vector_dimension) + + if not self._openai_client: + logger.error("OpenAI client not initialized - cannot generate embeddings") + raise ValueError("OpenAI client not initialized. Please provide an API key.") + + try: + # Call OpenAI API to get embedding + response = await self._openai_client.embeddings.create( + model=self._model_name, + input=text, + encoding_format="float" + ) + + # Extract the embedding values from the response + embedding = np.array(response.data[0].embedding) + return embedding + except OpenAIError as e: + logger.error(f"Error generating embedding with OpenAI: {str(e)}") + raise + + @backoff.on_exception( + backoff.expo, + OpenAIError, + max_tries=5, + jitter=backoff.full_jitter + ) + async def generate_embeddings_batch( + self, + texts: List[str] + ) -> List[np.ndarray]: + """ + Generate embeddings for a batch of text inputs using OpenAI. + + Args: + texts: List of texts to generate embeddings for. + + Returns: + List of generated embedding vectors as numpy arrays. + + Raises: + OpenAIError: If there's an error from the OpenAI API. + """ + if not texts: + return [] + + # Process in batches to respect OpenAI API limits + all_embeddings = [] + + for i in range(0, len(texts), self._batch_size): + batch_texts = texts[i:i + self._batch_size] + + # Filter out empty texts and create a mapping to track their positions + non_empty_texts = [] + empty_indices = [] + + for j, text in enumerate(batch_texts): + if text.strip(): + non_empty_texts.append(text) + else: + empty_indices.append(j) + + # If all texts in the batch are empty, add zero vectors and continue + if not non_empty_texts: + all_embeddings.extend([np.zeros(self._vector_dimension) for _ in batch_texts]) + continue + + try: + # Call OpenAI API to get embeddings for non-empty texts + response = await self._openai_client.embeddings.create( + model=self._model_name, + input=non_empty_texts, + encoding_format="float" + ) + + # Extract embeddings from response + batch_embeddings = [np.array(data.embedding) for data in response.data] + + # Reinsert zero vectors for empty texts + if empty_indices: + full_batch_embeddings = [] + non_empty_idx = 0 + + for j in range(len(batch_texts)): + if j in empty_indices: + full_batch_embeddings.append(np.zeros(self._vector_dimension)) + else: + full_batch_embeddings.append(batch_embeddings[non_empty_idx]) + non_empty_idx += 1 + + all_embeddings.extend(full_batch_embeddings) + else: + all_embeddings.extend(batch_embeddings) + + except OpenAIError as e: + logger.error(f"Error generating batch embeddings with OpenAI: {str(e)}") + raise + + return all_embeddings + + @staticmethod + def _prepare_metadata( + content_type: str, + content_id: str, + account_id: Optional[Union[UUID, str]] = None, + platform: Optional[str] = None, + created_at: Optional[datetime] = None, + additional_metadata: Optional[Dict[str, Any]] = None + ) -> Dict[str, Any]: + """ + Prepare metadata for storing with the vector embedding. + + Args: + content_type: Type of content ('post' or 'comment'). + content_id: ID of the content in MongoDB. + account_id: Optional ID of the social media account. + platform: Optional social media platform name. + created_at: Optional timestamp of when the content was created. + additional_metadata: Optional additional metadata to include. + + Returns: + Dictionary of metadata suitable for storage in Pinecone. + """ + metadata = { + "content_type": content_type, + "content_id": str(content_id), + "indexed_at": datetime.utcnow().isoformat() + } + + if account_id: + metadata["account_id"] = str(account_id) + + if platform: + metadata["platform"] = platform + + if created_at: + metadata["created_at"] = created_at.isoformat() + + # Add additional metadata + if additional_metadata: + # Filter out None values and convert all values to strings + # since Pinecone metadata only supports string values + for key, value in additional_metadata.items(): + if value is not None: + if isinstance(value, (dict, list)): + # Convert complex objects to strings + metadata[key] = str(value) + else: + metadata[key] = str(value) + + return metadata + + @backoff.on_exception( + backoff.expo, + Exception, # Use generic exception instead of PineconeException + max_tries=5, + jitter=backoff.full_jitter + ) + async def store_embedding( + self, + vector_id: str, + embedding: np.ndarray, + content_type: str, + content_id: str, + account_id: Optional[Union[UUID, str]] = None, + platform: Optional[str] = None, + created_at: Optional[datetime] = None, + additional_metadata: Optional[Dict[str, Any]] = None, + namespace: str = "" + ) -> bool: + """ + Store a single embedding in Pinecone. + + Args: + vector_id: ID to assign to the vector in Pinecone. + embedding: The embedding vector to store. + content_type: Type of content ('post' or 'comment'). + content_id: ID of the content in MongoDB. + account_id: Optional ID of the social media account. + platform: Optional social media platform name. + created_at: Optional timestamp of when the content was created. + additional_metadata: Optional additional metadata to include. + namespace: Optional namespace to store the vector in. + + Returns: + True if successful, False otherwise. + + Raises: + Exception: If there's an error from the Pinecone API. + ConnectionError: If there's a connection error. + ValueError: If Pinecone is not available. + """ + if not self._pinecone_available or not self._pinecone_index: + logger.error("Pinecone not available - cannot store embedding") + return False + + try: + metadata = self._prepare_metadata( + content_type=content_type, + content_id=content_id, + account_id=account_id, + platform=platform, + created_at=created_at, + additional_metadata=additional_metadata + ) + + # Pinecone upsert is synchronous, so run in thread pool + loop = asyncio.get_event_loop() + + # Convert numpy array to list for Pinecone + vector_values = embedding.tolist() + + await loop.run_in_executor( + self._executor, + partial( + self._pinecone_index.upsert, + vectors=[(vector_id, vector_values, metadata)], + namespace=namespace + ) + ) + + logger.debug(f"Stored embedding for {content_type} {content_id} with vector_id {vector_id}") + return True + except Exception as e: + logger.error(f"Error storing embedding: {str(e)}") + raise + + @backoff.on_exception( + backoff.expo, + (Exception, ConnectionError), + max_tries=5, + jitter=backoff.full_jitter + ) + async def store_embeddings_batch( + self, + embedding_data: List[Tuple[ + str, # vector_id + np.ndarray, # embedding + str, # content_type + str, # content_id + Optional[Union[UUID, str]], # account_id + Optional[str], # platform + Optional[datetime], # created_at + Optional[Dict[str, Any]] # additional_metadata + ]], + namespace: str = "" + ) -> int: + """ + Store multiple embeddings in Pinecone in batches. + + Args: + embedding_data: List of tuples containing data for each embedding. + namespace: Optional namespace to store the vectors in. + + Returns: + Number of successfully stored embeddings. + + Raises: + Exception: If there's an error from the Pinecone API. + ConnectionError: If there's a connection error. + """ + if not embedding_data: + return 0 + + vectors = [] + + # Prepare vectors and metadata + for ( + vector_id, + embedding, + content_type, + content_id, + account_id, + platform, + created_at, + additional_metadata + ) in embedding_data: + metadata = self._prepare_metadata( + content_type=content_type, + content_id=content_id, + account_id=account_id, + platform=platform, + created_at=created_at, + additional_metadata=additional_metadata + ) + + # Convert numpy array to list for Pinecone + vector_values = embedding.tolist() + + vectors.append((vector_id, vector_values, metadata)) + + # Process in batches + total_stored = 0 + loop = asyncio.get_event_loop() + + for i in range(0, len(vectors), self._batch_size): + batch = vectors[i:i + self._batch_size] + + try: + # Pinecone upsert is synchronous, so run in thread pool + await loop.run_in_executor( + self._executor, + partial( + self._pinecone_index.upsert, + vectors=batch, + namespace=namespace + ) + ) + + total_stored += len(batch) + logger.debug(f"Stored batch of {len(batch)} embeddings") + except Exception as e: + logger.error(f"Error storing batch of embeddings: {str(e)}") + raise + + return total_stored + + @backoff.on_exception( + backoff.expo, + Exception, + max_tries=5, + jitter=backoff.full_jitter + ) + async def update_embedding( + self, + vector_id: str, + embedding: Optional[np.ndarray] = None, + updated_metadata: Optional[Dict[str, Any]] = None, + namespace: str = "" + ) -> bool: + """ + Update an existing embedding in Pinecone. + + Args: + vector_id: ID of the vector to update. + embedding: Optional new embedding vector. If None, only updates metadata. + updated_metadata: Optional metadata to update. + namespace: Optional namespace to update the vector in. + + Returns: + True if successful, False otherwise. + + Raises: + Exception: If there's an error from the Pinecone API. + ConnectionError: If there's a connection error. + """ + try: + loop = asyncio.get_event_loop() + + # If only updating metadata + if embedding is None and updated_metadata: + # First, retrieve the existing vector to get its current values + fetch_response = await loop.run_in_executor( + self._executor, + partial( + self._pinecone_index.fetch, + ids=[vector_id], + namespace=namespace + ) + ) + + if vector_id not in fetch_response.vectors: + logger.warning(f"Vector {vector_id} not found for metadata update") + return False + + # Update only the metadata, keeping the existing vector values + vector_values = fetch_response.vectors[vector_id].values + current_metadata = fetch_response.vectors[vector_id].metadata or {} + + # Merge existing metadata with updates + updated_metadata = {**current_metadata, **updated_metadata} + + # Upsert with updated metadata + await loop.run_in_executor( + self._executor, + partial( + self._pinecone_index.upsert, + vectors=[(vector_id, vector_values, updated_metadata)], + namespace=namespace + ) + ) + # If updating vector values + elif embedding is not None: + vector_values = embedding.tolist() + + if updated_metadata: + # Get current metadata if needed + fetch_response = await loop.run_in_executor( + self._executor, + partial( + self._pinecone_index.fetch, + ids=[vector_id], + namespace=namespace + ) + ) + + if vector_id in fetch_response.vectors: + current_metadata = fetch_response.vectors[vector_id].metadata or {} + # Merge existing metadata with updates + updated_metadata = {**current_metadata, **updated_metadata} + + # Upsert with new vector values and updated metadata + await loop.run_in_executor( + self._executor, + partial( + self._pinecone_index.upsert, + vectors=[(vector_id, vector_values, updated_metadata or {})], + namespace=namespace + ) + ) + else: + # Both embedding and updated_metadata are None + logger.warning("No updates provided for vector") + return False + + logger.debug(f"Updated embedding for vector_id {vector_id}") + return True + except Exception as e: + logger.error(f"Error updating embedding: {str(e)}") + raise + + @backoff.on_exception( + backoff.expo, + Exception, + max_tries=5, + jitter=backoff.full_jitter + ) + async def delete_embedding( + self, + vector_id: str, + namespace: str = "" + ) -> bool: + """ + Delete an embedding from Pinecone. + + Args: + vector_id: ID of the vector to delete. + namespace: Optional namespace to delete the vector from. + + Returns: + True if successful, False otherwise. + + Raises: + Exception: If there's an error from the Pinecone API. + ConnectionError: If there's a connection error. + """ + try: + # Pinecone delete is synchronous, so run in thread pool + loop = asyncio.get_event_loop() + + await loop.run_in_executor( + self._executor, + partial( + self._pinecone_index.delete, + ids=[vector_id], + namespace=namespace + ) + ) + + logger.debug(f"Deleted embedding with vector_id {vector_id}") + return True + except Exception as e: + logger.error(f"Error deleting embedding: {str(e)}") + raise + + @backoff.on_exception( + backoff.expo, + Exception, + max_tries=5, + jitter=backoff.full_jitter + ) + async def delete_embeddings_batch( + self, + vector_ids: List[str], + namespace: str = "" + ) -> int: + """ + Delete multiple embeddings from Pinecone in batches. + + Args: + vector_ids: List of vector IDs to delete. + namespace: Optional namespace to delete the vectors from. + + Returns: + Number of successfully deleted embeddings. + + Raises: + Exception: If there's an error from the Pinecone API. + ConnectionError: If there's a connection error. + """ + if not vector_ids: + return 0 + + total_deleted = 0 + loop = asyncio.get_event_loop() + + # Process in batches + for i in range(0, len(vector_ids), self._batch_size): + batch_ids = vector_ids[i:i + self._batch_size] + + try: + # Pinecone delete is synchronous, so run in thread pool + await loop.run_in_executor( + self._executor, + partial( + self._pinecone_index.delete, + ids=batch_ids, + namespace=namespace + ) + ) + + total_deleted += len(batch_ids) + logger.debug(f"Deleted batch of {len(batch_ids)} embeddings") + except Exception as e: + logger.error(f"Error deleting batch of embeddings: {str(e)}") + raise + + return total_deleted + + @backoff.on_exception( + backoff.expo, + Exception, + max_tries=5, + jitter=backoff.full_jitter + ) + async def delete_embeddings_by_filter( + self, + filter_condition: Dict[str, Any], + namespace: str = "" + ) -> bool: + """ + Delete embeddings from Pinecone based on metadata filter. + + Args: + filter_condition: Metadata filter condition to match vectors for deletion. + namespace: Optional namespace to delete the vectors from. + + Returns: + True if successful, False otherwise. + + Raises: + Exception: If there's an error from the Pinecone API. + ConnectionError: If there's a connection error. + """ + try: + # Pinecone delete is synchronous, so run in thread pool + loop = asyncio.get_event_loop() + + await loop.run_in_executor( + self._executor, + partial( + self._pinecone_index.delete, + filter=filter_condition, + namespace=namespace + ) + ) + + logger.debug(f"Deleted embeddings using filter: {filter_condition}") + return True + except Exception as e: + logger.error(f"Error deleting embeddings by filter: {str(e)}") + raise + + @backoff.on_exception( + backoff.expo, + Exception, + max_tries=5, + jitter=backoff.full_jitter + ) + async def check_vector_exists( + self, + vector_id: str, + namespace: str = "" + ) -> bool: + """ + Check if a vector with the given ID exists in Pinecone. + + Args: + vector_id: ID of the vector to check. + namespace: Optional namespace to check in. + + Returns: + True if the vector exists, False otherwise. + + Raises: + Exception: If there's an error from the Pinecone API. + ConnectionError: If there's a connection error. + """ + try: + # Pinecone fetch is synchronous, so run in thread pool + loop = asyncio.get_event_loop() + + response = await loop.run_in_executor( + self._executor, + partial( + self._pinecone_index.fetch, + ids=[vector_id], + namespace=namespace + ) + ) + + return vector_id in response.vectors + except Exception as e: + logger.error(f"Error checking if vector exists: {str(e)}") + raise + + async def close(self): + """ + Clean up resources used by the service. + + This should be called when the service is no longer needed to ensure + proper cleanup of thread pool and other resources. + """ + self._executor.shutdown() + # Close the OpenAI client + if self._openai_client: + await self._openai_client.close() \ No newline at end of file diff --git a/backend/app/tasks/README.md b/backend/app/tasks/README.md new file mode 100644 index 0000000000..7aae7ee4d1 --- /dev/null +++ b/backend/app/tasks/README.md @@ -0,0 +1,112 @@ +# Task Processing System (MVP Version) + +This module provides a simplified task processing system for the Political Social Media Analysis Platform. **Important: For the MVP version, Celery, Redis, and related message broker infrastructure will NOT be implemented.** Instead, we use a lightweight approach that leverages FastAPI's built-in features. + +## Key Components + +### 1. Task Manager + +The `TaskManager` class (`task_manager.py`) is the central component of the system, responsible for: + +- Creating and tracking tasks +- Executing tasks synchronously or asynchronously using FastAPI's `BackgroundTasks` +- Maintaining task status (pending, running, completed, failed) +- Providing error handling and logging +- Offering convenience methods for common task types + +### 2. Task Types + +The `task_types.py` module defines: + +- Common enums like `TaskPriority` and `TaskStatus` +- Type definitions for task results +- Base function signatures for different types of operations: + - `collect_platform_data()`: For platform-specific data collection + - `analyze_content()`: For content analysis (sentiment, topics, etc.) + - `analyze_relationships()`: For entity relationship analysis + - `generate_report()`: For report generation + +### 3. API Integration + +The task processing system integrates with FastAPI through: + +- Dependency injection (`TaskManagerDep` in `app/api/deps.py`) +- API endpoints in `app/api/api_v1/endpoints/tasks.py` + +## Usage + +### Creating and Running Tasks + +```python +from fastapi import Depends, BackgroundTasks +from app.api.deps import TaskManagerDep + +@app.post("/example") +async def example_endpoint( + background_tasks: BackgroundTasks, + task_manager: TaskManagerDep, +): + # Create and schedule a task + task_id = task_manager.create_task( + func=some_async_function, + args=[arg1, arg2], + kwargs={"key": "value"} + ) + + # Execute asynchronously + task_manager.execute_task_async(task_id, background_tasks) + + # Or execute synchronously + result = await task_manager.execute_task_sync(task_id) + + return {"task_id": task_id} +``` + +### Using Convenience Methods + +```python +# Data collection +task_id = task_manager.schedule_data_collection( + platform="twitter", + entity_ids=["id1", "id2"], + background_tasks=background_tasks +) + +# Content analysis +task_id = task_manager.schedule_content_analysis( + content_ids=["content1", "content2"], + analysis_types=["sentiment", "topics"], + background_tasks=background_tasks +) +``` + +### Checking Task Status + +```python +# Get status of a specific task +status = task_manager.get_task_status(task_id) + +# Get all tasks +tasks = task_manager.get_all_tasks() +``` + +## Known Limitations for MVP + +- **No Persistent Storage**: Tasks are stored in memory and will be lost if the server restarts +- **No Distributed Processing**: All tasks run on the same server instance +- **No Scheduled Tasks**: No mechanism for recurring or scheduled tasks +- **No Task Prioritization Queue**: Tasks execute in the order they're received +- **Limited Scalability**: The system is not designed to handle a high volume of concurrent tasks + +## Future Migration to Celery + +In future versions beyond the MVP, this implementation can be replaced with a full Celery/Redis infrastructure: + +1. The task function signatures are compatible with Celery task definitions +2. The status tracking aligns with Celery's task states +3. The interface is abstracted to make replacement straightforward + +When migrating to Celery: +- Update the implementation of `TaskManager` to use Celery under the hood +- Maintain the same interface for backward compatibility +- Add additional Celery-specific features as needed \ No newline at end of file diff --git a/backend/app/tasks/__init__.py b/backend/app/tasks/__init__.py index 9ee24630cf..ee3d024c81 100644 --- a/backend/app/tasks/__init__.py +++ b/backend/app/tasks/__init__.py @@ -5,11 +5,33 @@ process_data_pipeline, scrape_social_media, ) +from app.tasks.task_manager import TaskManager, get_task_manager +from app.tasks.task_types import ( + TaskPriority, + TaskStatus, + TaskResult, + collect_platform_data, + analyze_content, + analyze_relationships, + generate_report, +) __all__ = [ + # Celery components (to be replaced in future) "celery_app", "scrape_social_media", "analyze_social_data", "generate_reports", "process_data_pipeline", + + # MVP Task Manager components + "TaskManager", + "get_task_manager", + "TaskPriority", + "TaskStatus", + "TaskResult", + "collect_platform_data", + "analyze_content", + "analyze_relationships", + "generate_report", ] \ No newline at end of file diff --git a/backend/app/tasks/collection_tasks.py b/backend/app/tasks/collection_tasks.py new file mode 100644 index 0000000000..feea04dbf0 --- /dev/null +++ b/backend/app/tasks/collection_tasks.py @@ -0,0 +1,363 @@ +""" +Collection Tasks + +This module provides task functions for social media data collection +using the platform-specific collectors from the APIFY API. +""" + +import logging +from datetime import datetime +from typing import Any, Dict, List, Optional, Union +from uuid import UUID + +from app.processing.collection.factory import CollectorFactory +from app.tasks.task_types import TaskResult + +logger = logging.getLogger(__name__) + + +async def scrape_account_posts( + account_id: Union[UUID, str], + platform: str, + count: Optional[int] = None, + since_date: Optional[datetime] = None, + **kwargs +) -> TaskResult: + """ + Task to scrape posts from a social media account. + + Args: + account_id: UUID of the social media account to collect from + platform: Social media platform (twitter, facebook, instagram) + count: Maximum number of posts to collect + since_date: Only collect posts after this date + **kwargs: Additional platform-specific parameters + + Returns: + TaskResult containing collected post IDs or error information + """ + start_time = datetime.utcnow() + logger.info(f"Starting post collection task for {platform} account {account_id}") + + try: + # Get the appropriate collector for the platform + collector = CollectorFactory.get_collector(platform) + + # Collect posts + post_ids = await collector.collect_posts( + account_id=account_id, + count=count, + since_date=since_date + ) + + end_time = datetime.utcnow() + duration = (end_time - start_time).total_seconds() + + logger.info(f"Completed post collection task for {platform} account {account_id}, collected {len(post_ids)} posts in {duration:.2f} seconds") + + return { + "success": True, + "data": { + "platform": platform, + "account_id": str(account_id), + "post_count": len(post_ids), + "post_ids": post_ids + }, + "started_at": start_time, + "completed_at": end_time, + "duration_seconds": duration + } + + except Exception as e: + end_time = datetime.utcnow() + duration = (end_time - start_time).total_seconds() + error_message = f"Error collecting posts for {platform} account {account_id}: {str(e)}" + + logger.error(error_message, exc_info=True) + + return { + "success": False, + "error": error_message, + "started_at": start_time, + "completed_at": end_time, + "duration_seconds": duration + } + + +async def scrape_post_comments( + post_id: str, + platform: str, + count: Optional[int] = None, + **kwargs +) -> TaskResult: + """ + Task to scrape comments for a social media post. + + Args: + post_id: MongoDB ID of the post to collect comments for + platform: Social media platform (twitter, facebook, instagram) + count: Maximum number of comments to collect + **kwargs: Additional platform-specific parameters + + Returns: + TaskResult containing collected comment IDs or error information + """ + start_time = datetime.utcnow() + logger.info(f"Starting comment collection task for {platform} post {post_id}") + + try: + # Get the appropriate collector for the platform + collector = CollectorFactory.get_collector(platform) + + # Collect comments + comment_ids = await collector.collect_comments( + post_id=post_id, + count=count + ) + + end_time = datetime.utcnow() + duration = (end_time - start_time).total_seconds() + + logger.info(f"Completed comment collection task for {platform} post {post_id}, collected {len(comment_ids)} comments in {duration:.2f} seconds") + + return { + "success": True, + "data": { + "platform": platform, + "post_id": post_id, + "comment_count": len(comment_ids), + "comment_ids": comment_ids + }, + "started_at": start_time, + "completed_at": end_time, + "duration_seconds": duration + } + + except Exception as e: + end_time = datetime.utcnow() + duration = (end_time - start_time).total_seconds() + error_message = f"Error collecting comments for {platform} post {post_id}: {str(e)}" + + logger.error(error_message, exc_info=True) + + return { + "success": False, + "error": error_message, + "started_at": start_time, + "completed_at": end_time, + "duration_seconds": duration + } + + +async def update_account_profile( + account_id: Union[UUID, str], + platform: str, + **kwargs +) -> TaskResult: + """ + Task to update profile information for a social media account. + + Args: + account_id: UUID of the social media account to update + platform: Social media platform (twitter, facebook, instagram) + **kwargs: Additional platform-specific parameters + + Returns: + TaskResult containing updated profile data or error information + """ + start_time = datetime.utcnow() + logger.info(f"Starting profile update task for {platform} account {account_id}") + + try: + # Get the appropriate collector for the platform + collector = CollectorFactory.get_collector(platform) + + # Update profile + profile_data = await collector.collect_profile(account_id=account_id) + + end_time = datetime.utcnow() + duration = (end_time - start_time).total_seconds() + + logger.info(f"Completed profile update task for {platform} account {account_id} in {duration:.2f} seconds") + + return { + "success": True, + "data": { + "platform": platform, + "account_id": str(account_id), + "profile_data": profile_data + }, + "started_at": start_time, + "completed_at": end_time, + "duration_seconds": duration + } + + except Exception as e: + end_time = datetime.utcnow() + duration = (end_time - start_time).total_seconds() + error_message = f"Error updating profile for {platform} account {account_id}: {str(e)}" + + logger.error(error_message, exc_info=True) + + return { + "success": False, + "error": error_message, + "started_at": start_time, + "completed_at": end_time, + "duration_seconds": duration + } + + +async def update_account_metrics( + account_id: Union[UUID, str], + platform: str, + **kwargs +) -> TaskResult: + """ + Task to update engagement metrics for a social media account. + + Args: + account_id: UUID of the social media account to update + platform: Social media platform (twitter, facebook, instagram) + **kwargs: Additional platform-specific parameters + + Returns: + TaskResult containing updated metrics data or error information + """ + start_time = datetime.utcnow() + logger.info(f"Starting metrics update task for {platform} account {account_id}") + + try: + # Get the appropriate collector for the platform + collector = CollectorFactory.get_collector(platform) + + # Update metrics + metrics_data = await collector.update_metrics(account_id=account_id) + + end_time = datetime.utcnow() + duration = (end_time - start_time).total_seconds() + + logger.info(f"Completed metrics update task for {platform} account {account_id} in {duration:.2f} seconds") + + return { + "success": True, + "data": { + "platform": platform, + "account_id": str(account_id), + "metrics_data": metrics_data + }, + "started_at": start_time, + "completed_at": end_time, + "duration_seconds": duration + } + + except Exception as e: + end_time = datetime.utcnow() + duration = (end_time - start_time).total_seconds() + error_message = f"Error updating metrics for {platform} account {account_id}: {str(e)}" + + logger.error(error_message, exc_info=True) + + return { + "success": False, + "error": error_message, + "started_at": start_time, + "completed_at": end_time, + "duration_seconds": duration + } + + +async def batch_scrape_accounts( + account_ids: List[Union[UUID, str]], + platform: str, + count_per_account: Optional[int] = None, + since_date: Optional[datetime] = None, + **kwargs +) -> TaskResult: + """ + Task to batch scrape posts from multiple social media accounts. + + Args: + account_ids: List of UUIDs of social media accounts to collect from + platform: Social media platform (twitter, facebook, instagram) + count_per_account: Maximum number of posts to collect per account + since_date: Only collect posts after this date + **kwargs: Additional platform-specific parameters + + Returns: + TaskResult containing collected post IDs by account or error information + """ + start_time = datetime.utcnow() + logger.info(f"Starting batch post collection task for {len(account_ids)} {platform} accounts") + + try: + # Get the appropriate collector for the platform + collector = CollectorFactory.get_collector(platform) + + results = {} + for account_id in account_ids: + try: + # Collect posts for this account + post_ids = await collector.collect_posts( + account_id=account_id, + count=count_per_account, + since_date=since_date + ) + + # Store results + results[str(account_id)] = { + "success": True, + "post_count": len(post_ids), + "post_ids": post_ids + } + + except Exception as e: + # Record failure for this account but continue with others + error_message = f"Error collecting posts for {platform} account {account_id}: {str(e)}" + logger.error(error_message) + + results[str(account_id)] = { + "success": False, + "error": error_message + } + + end_time = datetime.utcnow() + duration = (end_time - start_time).total_seconds() + + # Count successes and failures + success_count = sum(1 for r in results.values() if r.get("success", False)) + post_count = sum(r.get("post_count", 0) for r in results.values() if r.get("success", False)) + + logger.info( + f"Completed batch post collection task: {success_count}/{len(account_ids)} accounts succeeded, " + f"collected {post_count} posts in {duration:.2f} seconds" + ) + + return { + "success": True, + "data": { + "platform": platform, + "account_count": len(account_ids), + "success_count": success_count, + "post_count": post_count, + "results_by_account": results + }, + "started_at": start_time, + "completed_at": end_time, + "duration_seconds": duration + } + + except Exception as e: + end_time = datetime.utcnow() + duration = (end_time - start_time).total_seconds() + error_message = f"Error in batch collection task: {str(e)}" + + logger.error(error_message, exc_info=True) + + return { + "success": False, + "error": error_message, + "started_at": start_time, + "completed_at": end_time, + "duration_seconds": duration + } \ No newline at end of file diff --git a/backend/app/tasks/task_manager.py b/backend/app/tasks/task_manager.py new file mode 100644 index 0000000000..f2f86498b1 --- /dev/null +++ b/backend/app/tasks/task_manager.py @@ -0,0 +1,424 @@ +""" +MVP Task Management System + +This module provides a simple task management system for the MVP version of the application. +IMPORTANT: This implementation intentionally does NOT use Celery, Redis, RabbitMQ or any +related message broker infrastructure for the MVP. + +Instead, it uses FastAPI's built-in background tasks feature for basic asynchronous processing. +Tasks are stored in memory and will be lost if the server restarts. + +The design allows for easy migration to Celery in future versions. +""" + +import logging +import uuid +from typing import Any, Callable, Dict, List, Optional, Union, cast +from datetime import datetime +import asyncio +import traceback +from uuid import UUID + +from fastapi import BackgroundTasks + +from app.tasks.task_types import TaskPriority, TaskResult, TaskStatus + +logger = logging.getLogger(__name__) + +class Task: + """Represents a single task in the task processing system.""" + + def __init__( + self, + task_id: str, + func: Callable, + args: List[Any], + kwargs: Dict[str, Any], + priority: TaskPriority = TaskPriority.MEDIUM, + ): + self.task_id = task_id + self.func = func + self.args = args + self.kwargs = kwargs + self.priority = priority + self.status = TaskStatus.PENDING + self.result: Optional[TaskResult] = None + self.created_at = datetime.now() + self.started_at: Optional[datetime] = None + self.completed_at: Optional[datetime] = None + self.error: Optional[str] = None + + async def execute(self) -> TaskResult: + """Execute the task and return the result.""" + self.status = TaskStatus.RUNNING + self.started_at = datetime.now() + + try: + # Execute the task function + result = await self.func(*self.args, **self.kwargs) + self.status = TaskStatus.COMPLETED + self.result = result + + except Exception as e: + self.status = TaskStatus.FAILED + error_message = f"Task {self.task_id} failed: {str(e)}" + tb = traceback.format_exc() + logger.error(f"{error_message}\n{tb}") + + self.error = error_message + self.result = { + "success": False, + "error": error_message, + "started_at": self.started_at, + "completed_at": datetime.now(), + "duration_seconds": (datetime.now() - self.started_at).total_seconds() if self.started_at else 0 + } + + self.completed_at = datetime.now() + return cast(TaskResult, self.result) + + +class TaskManager: + """ + Task manager for the MVP version of the application. + + Handles both synchronous and background task execution, + with basic task status tracking and error handling. + + Note: This is an in-memory implementation. Tasks will be lost if the server restarts. + No persistent storage, distributed processing, or scheduled tasks are supported. + """ + + def __init__(self): + self.tasks: Dict[str, Task] = {} + self.logger = logger + + def create_task( + self, + func: Callable, + args: List[Any] = None, + kwargs: Dict[str, Any] = None, + priority: TaskPriority = TaskPriority.MEDIUM, + task_id: Optional[str] = None, + ) -> str: + """ + Create a new task to be executed. + + Args: + func: Async function to be executed + args: Positional arguments for the function + kwargs: Keyword arguments for the function + priority: Task priority level + task_id: Optional custom task ID (UUID will be generated if not provided) + + Returns: + task_id: Unique identifier for the created task + """ + args = args or [] + kwargs = kwargs or {} + task_id = task_id or str(uuid.uuid4()) + + task = Task( + task_id=task_id, + func=func, + args=args, + kwargs=kwargs, + priority=priority, + ) + + self.tasks[task_id] = task + self.logger.info(f"Created task {task_id} with priority {priority.name}") + + return task_id + + async def execute_task_sync(self, task_id: str) -> TaskResult: + """ + Execute a task synchronously (blocking). + + Args: + task_id: ID of the task to execute + + Returns: + TaskResult: Result of the task execution + + Raises: + ValueError: If the task ID is not found + """ + task = self.tasks.get(task_id) + if not task: + raise ValueError(f"Task {task_id} not found") + + self.logger.info(f"Executing task {task_id} synchronously") + return await task.execute() + + def execute_task_async(self, task_id: str, background_tasks: BackgroundTasks) -> str: + """ + Add a task to FastAPI's BackgroundTasks for asynchronous execution. + + Args: + task_id: ID of the task to execute + background_tasks: FastAPI BackgroundTasks instance + + Returns: + task_id: The ID of the scheduled task + + Raises: + ValueError: If the task ID is not found + """ + task = self.tasks.get(task_id) + if not task: + raise ValueError(f"Task {task_id} not found") + + # Add the task execution to FastAPI's background tasks + background_tasks.add_task(self._execute_background_task, task_id) + + self.logger.info(f"Scheduled task {task_id} for background execution") + return task_id + + async def _execute_background_task(self, task_id: str) -> None: + """ + Internal method to execute a task in the background. + + Args: + task_id: ID of the task to execute + """ + task = self.tasks.get(task_id) + if not task: + self.logger.error(f"Background task {task_id} not found") + return + + try: + await task.execute() + except Exception as e: + self.logger.error(f"Unhandled error in background task {task_id}: {str(e)}") + + def get_task_status(self, task_id: str) -> Dict[str, Any]: + """ + Get the current status of a task. + + Args: + task_id: ID of the task to check + + Returns: + Dict with task status information + + Raises: + ValueError: If the task ID is not found + """ + task = self.tasks.get(task_id) + if not task: + raise ValueError(f"Task {task_id} not found") + + return { + "task_id": task.task_id, + "status": task.status.value, + "created_at": task.created_at, + "started_at": task.started_at, + "completed_at": task.completed_at, + "priority": task.priority.name, + "error": task.error, + "result": task.result["data"] if task.result and "data" in task.result else None + } + + def get_all_tasks( + self, + status: Optional[TaskStatus] = None, + limit: int = 100, + offset: int = 0 + ) -> List[Dict[str, Any]]: + """ + Get a list of all tasks, optionally filtered by status. + + Args: + status: Optional filter by task status + limit: Maximum number of tasks to return + offset: Offset for pagination + + Returns: + List of task status dictionaries + """ + tasks = list(self.tasks.values()) + + # Filter by status if provided + if status: + tasks = [task for task in tasks if task.status == status] + + # Sort by created_at (newest first) + tasks = sorted(tasks, key=lambda t: t.created_at, reverse=True) + + # Apply pagination + tasks = tasks[offset:offset+limit] + + return [ + { + "task_id": task.task_id, + "status": task.status.value, + "created_at": task.created_at, + "started_at": task.started_at, + "completed_at": task.completed_at, + "priority": task.priority.name, + "error": task.error, + } + for task in tasks + ] + + def clear_completed_tasks(self, max_age_hours: int = 24) -> int: + """ + Clear completed or failed tasks older than the specified age. + + Args: + max_age_hours: Maximum age in hours for tasks to be retained + + Returns: + Number of tasks removed + """ + current_time = datetime.now() + tasks_to_remove = [] + + for task_id, task in self.tasks.items(): + if task.status in [TaskStatus.COMPLETED, TaskStatus.FAILED]: + completed_at = task.completed_at or task.created_at + age = (current_time - completed_at).total_seconds() / 3600 + + if age > max_age_hours: + tasks_to_remove.append(task_id) + + # Remove the tasks + for task_id in tasks_to_remove: + del self.tasks[task_id] + + return len(tasks_to_remove) + + # Convenience methods for common task types + + def schedule_data_collection( + self, + platform: str, + entity_ids: List[str], + background_tasks: BackgroundTasks, + **kwargs + ) -> str: + """ + Schedule a data collection task. + + Args: + platform: Name of the social media platform + entity_ids: List of entity IDs to collect data for + background_tasks: FastAPI BackgroundTasks instance + **kwargs: Additional parameters for collect_platform_data + + Returns: + task_id: ID of the scheduled task + """ + from app.tasks.task_types import collect_platform_data + + task_id = self.create_task( + func=collect_platform_data, + args=[platform, entity_ids], + kwargs=kwargs, + priority=TaskPriority.HIGH + ) + + return self.execute_task_async(task_id, background_tasks) + + def schedule_content_analysis( + self, + content_ids: List[str], + analysis_types: List[str], + background_tasks: BackgroundTasks, + **kwargs + ) -> str: + """ + Schedule a content analysis task. + + Args: + content_ids: List of content IDs to analyze + analysis_types: Types of analysis to perform + background_tasks: FastAPI BackgroundTasks instance + **kwargs: Additional parameters for analyze_content + + Returns: + task_id: ID of the scheduled task + """ + from app.tasks.task_types import analyze_content + + task_id = self.create_task( + func=analyze_content, + args=[content_ids], + kwargs={"analysis_types": analysis_types, **kwargs}, + priority=TaskPriority.MEDIUM + ) + + return self.execute_task_async(task_id, background_tasks) + + def schedule_relationship_analysis( + self, + entity_ids: List[str], + background_tasks: BackgroundTasks, + **kwargs + ) -> str: + """ + Schedule a relationship analysis task. + + Args: + entity_ids: List of entity IDs to analyze relationships for + background_tasks: FastAPI BackgroundTasks instance + **kwargs: Additional parameters for analyze_relationships + + Returns: + task_id: ID of the scheduled task + """ + from app.tasks.task_types import analyze_relationships + + task_id = self.create_task( + func=analyze_relationships, + args=[entity_ids], + kwargs=kwargs, + priority=TaskPriority.LOW + ) + + return self.execute_task_async(task_id, background_tasks) + + def schedule_report_generation( + self, + report_type: str, + background_tasks: BackgroundTasks, + **kwargs + ) -> str: + """ + Schedule a report generation task. + + Args: + report_type: Type of report to generate + background_tasks: FastAPI BackgroundTasks instance + **kwargs: Additional parameters for generate_report + + Returns: + task_id: ID of the scheduled task + """ + from app.tasks.task_types import generate_report + + task_id = self.create_task( + func=generate_report, + args=[report_type], + kwargs=kwargs, + priority=TaskPriority.LOW + ) + + return self.execute_task_async(task_id, background_tasks) + +# Global TaskManager instance +_task_manager: Optional[TaskManager] = None + +def get_task_manager() -> TaskManager: + """ + Get the global TaskManager instance. + Creates a new instance if one doesn't exist yet. + + Returns: + TaskManager: Global task manager instance + """ + global _task_manager + if _task_manager is None: + _task_manager = TaskManager() + return _task_manager \ No newline at end of file diff --git a/backend/app/tasks/task_types.py b/backend/app/tasks/task_types.py new file mode 100644 index 0000000000..64082705f9 --- /dev/null +++ b/backend/app/tasks/task_types.py @@ -0,0 +1,213 @@ +""" +MVP Task Type Definitions and Dummy Implementations + +This module defines task types and provides dummy implementations for the MVP version. +IMPORTANT: These are not actual Celery tasks. For the MVP, we are NOT using Celery, Redis, +RabbitMQ or any related message broker infrastructure. + +These are simple async functions that will be executed directly by the TaskManager +using FastAPI's background tasks feature. +""" + +from enum import Enum +from typing import Any, Dict, List, Optional, TypedDict, Union +import logging +from datetime import datetime + +logger = logging.getLogger(__name__) + +class TaskPriority(Enum): + """Priority levels for tasks.""" + LOW = 0 + MEDIUM = 1 + HIGH = 2 + CRITICAL = 3 + +class TaskStatus(Enum): + """Status states for tasks.""" + PENDING = "pending" + RUNNING = "running" + COMPLETED = "completed" + FAILED = "failed" + +class TaskResult(TypedDict, total=False): + """Type definition for task results.""" + success: bool + data: Any + error: str + started_at: datetime + completed_at: datetime + duration_seconds: float + +# Platform data collection tasks +async def collect_platform_data( + platform: str, + entity_ids: List[str], + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None, + limit: int = 100, + **kwargs +) -> TaskResult: + """ + Collect social media data from a specific platform for given entities. + + MVP NOTE: This is a dummy implementation. In the real implementation, + this would be a Celery task calling platform-specific API clients. + + Args: + platform: Name of the social media platform (twitter, facebook, etc.) + entity_ids: List of political entity IDs to collect data for + start_date: Optional start date for data collection + end_date: Optional end date for data collection + limit: Maximum number of items to collect per entity + **kwargs: Additional platform-specific parameters + + Returns: + TaskResult containing collected data or error information + """ + logger.info( + f"[DUMMY IMPLEMENTATION] Collecting data from {platform} for {len(entity_ids)} entities" + f" (from {start_date} to {end_date})" + ) + + # In the real implementation, this would call platform-specific API clients + return { + "success": True, + "data": { + "platform": platform, + "entities_processed": len(entity_ids), + "items_collected": 0, + "time_range": f"{start_date} to {end_date}" + }, + "started_at": datetime.now(), + "completed_at": datetime.now(), + "duration_seconds": 0.1 + } + +# Content analysis tasks +async def analyze_content( + content_ids: List[str], + analysis_types: List[str] = ["sentiment", "topics", "entities"], + **kwargs +) -> TaskResult: + """ + Analyze social media content for sentiment, topics, entities, etc. + + MVP NOTE: This is a dummy implementation. In the real implementation, + this would be a Celery task using NLP pipelines. + + Args: + content_ids: List of content IDs to analyze + analysis_types: Types of analysis to perform + **kwargs: Additional analysis parameters + + Returns: + TaskResult containing analysis results or error information + """ + logger.info( + f"[DUMMY IMPLEMENTATION] Analyzing {len(content_ids)} content items" + f" for {', '.join(analysis_types)}" + ) + + # In the real implementation, this would use NLP pipelines + return { + "success": True, + "data": { + "items_analyzed": len(content_ids), + "analysis_types": analysis_types, + "results_summary": { + "sentiment": {"positive": 0, "negative": 0, "neutral": 0}, + "topics": ["politics", "economy", "healthcare"], + "entities": ["person", "organization", "location"] + } + }, + "started_at": datetime.now(), + "completed_at": datetime.now(), + "duration_seconds": 0.2 + } + +# Relationship analysis tasks +async def analyze_relationships( + entity_ids: List[str], + time_period: str = "last_30_days", + relationship_types: List[str] = ["mentions", "sentiment", "engagement"], + **kwargs +) -> TaskResult: + """ + Analyze relationships between political entities based on social media interactions. + + MVP NOTE: This is a dummy implementation. In the real implementation, + this would be a Celery task analyzing entity interactions. + + Args: + entity_ids: List of political entity IDs to analyze relationships for + time_period: Time period for analysis (e.g., "last_30_days", "last_week") + relationship_types: Types of relationships to analyze + **kwargs: Additional analysis parameters + + Returns: + TaskResult containing relationship analysis or error information + """ + logger.info( + f"[DUMMY IMPLEMENTATION] Analyzing relationships for {len(entity_ids)} entities" + f" over {time_period} looking at {', '.join(relationship_types)}" + ) + + # In the real implementation, this would analyze entity interactions + return { + "success": True, + "data": { + "entities_analyzed": len(entity_ids), + "time_period": time_period, + "relationship_types": relationship_types, + "connections_found": 0 + }, + "started_at": datetime.now(), + "completed_at": datetime.now(), + "duration_seconds": 0.3 + } + +# Report generation tasks +async def generate_report( + report_type: str, + entity_ids: Optional[List[str]] = None, + time_period: str = "last_30_days", + format: str = "json", + **kwargs +) -> TaskResult: + """ + Generate reports based on analyzed social media data. + + MVP NOTE: This is a dummy implementation. In the real implementation, + this would be a Celery task aggregating data and formatting reports. + + Args: + report_type: Type of report to generate (e.g., "activity", "influence", "sentiment") + entity_ids: Optional list of political entity IDs to include in report + time_period: Time period for the report + format: Output format (json, csv, pdf) + **kwargs: Additional report parameters + + Returns: + TaskResult containing generated report or error information + """ + logger.info( + f"[DUMMY IMPLEMENTATION] Generating {report_type} report for" + f" {len(entity_ids) if entity_ids else 'all'} entities" + f" over {time_period} in {format} format" + ) + + # In the real implementation, this would aggregate data and format a report + return { + "success": True, + "data": { + "report_type": report_type, + "entity_count": len(entity_ids) if entity_ids else 0, + "time_period": time_period, + "format": format, + "report_url": "example.com/reports/dummy-report" + }, + "started_at": datetime.now(), + "completed_at": datetime.now(), + "duration_seconds": 0.5 + } \ No newline at end of file diff --git a/backend/app/testing/__init__.py b/backend/app/testing/__init__.py new file mode 100644 index 0000000000..246bf87bbf --- /dev/null +++ b/backend/app/testing/__init__.py @@ -0,0 +1,5 @@ +""" +Testing Package + +This package provides utilities and tools for testing the application. +""" \ No newline at end of file diff --git a/backend/app/testing/data/facebook/actors.md b/backend/app/testing/data/facebook/actors.md new file mode 100644 index 0000000000..522667e193 --- /dev/null +++ b/backend/app/testing/data/facebook/actors.md @@ -0,0 +1,8 @@ +# facebook profile +apify/facebook-pages-scraper + +# facebook posts +apify/facebook-posts-scraper + +# facebook comments +apify/facebook-comments-scraper \ No newline at end of file diff --git a/backend/app/testing/data/facebook/comment_samples.json b/backend/app/testing/data/facebook/comment_samples.json new file mode 100644 index 0000000000..937858b51b --- /dev/null +++ b/backend/app/testing/data/facebook/comment_samples.json @@ -0,0 +1,870 @@ +[ + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=2355693494796891", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzIzNTU2OTM0OTQ3OTY4OTE=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8yMzU1NjkzNDk0Nzk2ODkx", + "date": "2025-03-26T18:40:01.000Z", + "text": "Muchas gracias por informarnos gober estamos atentos de sus redes y las de protección civil de nl", + "profileUrl": "https://www.facebook.com/people/Rogelio-Cosme/pfbid0Lv71KXNCgDmD7rw21TPHYLguEaxLWLz6HUbevXT41QrmvQURyv2ZWrvXa6k8AwdWl/", + "profilePicture": "https://scontent.fnap7-1.fna.fbcdn.net/v/t39.30808-1/486681172_122226415772227891_5121059508313638440_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=110&ccb=1-7&_nc_sid=e99d92&_nc_ohc=4BzxX7UxPBwQ7kNvgGaNINu&_nc_oc=AdmuA9w0d50hKTrRDdjmY35tQlCMvGFAdeTj_h3_4JE2nO9Lt4mJb1TIpIuEbg9QSzk&_nc_zt=24&_nc_ht=scontent.fnap7-1.fna&_nc_gid=rUYBOTb4OA_-K_PBuURyag&oh=00_AYG565Mb9jxcdd-vUdmfimiVHMdhlSRevG09mmoLCPthtg&oe=67EA6ED3", + "profileId": "pfbid0Lv71KXNCgDmD7rw21TPHYLguEaxLWLz6HUbevXT41QrmvQURyv2ZWrvXa6k8AwdWl", + "profileName": "Rogelio Cosme", + "likesCount": "3", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1384165469273309", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzEzODQxNjU0NjkyNzMzMDk=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xMzg0MTY1NDY5MjczMzA5", + "date": "2025-03-26T18:41:36.000Z", + "text": "Saludos señor Gobernador Samuel Garcia exelente tarde Bendiciones", + "profileUrl": "https://www.facebook.com/people/Jorge-Paez/pfbid0255KQt2F12jGyZV1uRPzVHxJEBKbkkHVCTRGCmwY3bbhQrMNdfZwkcbrEmXnvB534l/", + "profilePicture": "https://scontent.fnap7-1.fna.fbcdn.net/v/t39.30808-1/393641616_122106373466080321_425663065104396343_n.jpg?stp=cp0_dst-jpg_p32x32_tt6&_nc_cat=100&ccb=1-7&_nc_sid=e99d92&_nc_ohc=8on6FfEr44QQ7kNvgEC13ba&_nc_oc=AdkXGxlcTL_jKTIwn7OxDG4AdJJVZKvWBa8Nvt5qK7Y8fRdjPRUa4sYpAcWT0ok6PcY&_nc_zt=24&_nc_ht=scontent.fnap7-1.fna&_nc_gid=rUYBOTb4OA_-K_PBuURyag&oh=00_AYHEFKaCbnZtsZjuAKOgJF7MshvYL1KywhCQ2_1kyBXfvQ&oe=67EA953C", + "profileId": "pfbid0255KQt2F12jGyZV1uRPzVHxJEBKbkkHVCTRGCmwY3bbhQrMNdfZwkcbrEmXnvB534l", + "profileName": "Jorge Paez", + "likesCount": "1", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1737765413484754", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzE3Mzc3NjU0MTM0ODQ3NTQ=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xNzM3NzY1NDEzNDg0NzU0", + "date": "2025-03-26T18:23:19.000Z", + "text": "Gracias por bombardear las nubes 🌧️ Sr gobernador ya que hacía falta la lluvia 🌧️ 🍊🍊", + "profileUrl": "https://www.facebook.com/lupe.gancho", + "profilePicture": "https://scontent.fnap7-2.fna.fbcdn.net/v/t39.30808-1/391645615_6642993509150543_6210686765595448643_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=103&ccb=1-7&_nc_sid=1d2534&_nc_ohc=Nl44Q5F1xK0Q7kNvgFVEiso&_nc_oc=Adn14Lc-KquBMMXO5wlUZSw7s8-GK0nYeSw7xUoELbVHj7fJ5tn9hzcnH0cCy3cRSyo&_nc_zt=24&_nc_ht=scontent.fnap7-2.fna&_nc_gid=rUYBOTb4OA_-K_PBuURyag&oh=00_AYGRP6_eXzny-Wq5qiQGV4HTmabu8gKGEkKdPBdWE2l-zA&oe=67EA928A", + "profileId": "100003198781208", + "profileName": "Guadalupe Díaz", + "likesCount": "21", + "commentsCount": 3, + "comments": [], + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1577990146252686", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzE1Nzc5OTAxNDYyNTI2ODY=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xNTc3OTkwMTQ2MjUyNjg2", + "date": "2025-03-26T18:13:44.000Z", + "text": "Te amo 💖", + "profileUrl": "https://www.facebook.com/people/Karly-Ya%C3%B1ez/pfbid0XBdJKSJ9EY6e28VEdynu5rpvSjDLPri51XV14TgenZHpGT8LXDxeiWReqUp2oimbl/", + "profilePicture": "https://scontent.fnap7-1.fna.fbcdn.net/v/t39.30808-1/481356004_122191192310122066_5711024420294776331_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=102&ccb=1-7&_nc_sid=e99d92&_nc_ohc=50l2TxpJx8sQ7kNvgE5lSc6&_nc_oc=AdlKLjmGiPrf099aS7wHVI1-k7y2vpbGQ0ggAyYbGKZIcCzwYO4Y7zF38O3XPj1pflA&_nc_zt=24&_nc_ht=scontent.fnap7-1.fna&_nc_gid=rUYBOTb4OA_-K_PBuURyag&oh=00_AYECdG4Q3-nhV7hJ7-7xWfMvBYwDqkJbtlaEsmUCXHHJIg&oe=67EA92BB", + "profileId": "pfbid0XBdJKSJ9EY6e28VEdynu5rpvSjDLPri51XV14TgenZHpGT8LXDxeiWReqUp2oimbl", + "profileName": "Karly Yañez", + "likesCount": "14", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=2067301967111892", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzIwNjczMDE5NjcxMTE4OTI=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8yMDY3MzAxOTY3MTExODky", + "date": "2025-03-26T18:25:24.000Z", + "text": "Gracias por traer la lluvia a nuevo leon", + "profilePicture": "https://scontent.fnap7-1.fna.fbcdn.net/v/t39.30808-1/416971203_2821619391321210_3423948252944591235_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=110&ccb=1-7&_nc_sid=e99d92&_nc_ohc=6R_CMn9j4IAQ7kNvgF8nqPH&_nc_oc=AdkY-r4XM-ccS6L6r4mlZZ6NvtHSjNT9JiUPMk9ELSX6K2pj61-L5znc7vNQX4mUxzs&_nc_zt=24&_nc_ht=scontent.fnap7-1.fna&_nc_gid=rUYBOTb4OA_-K_PBuURyag&oh=00_AYFuhCkASn19dffoJDRWLRypeMKZEqGdqlqfRachA5lIoA&oe=67EA9F59", + "profileId": "pfbid02Dh4g2fYo6H8jM9Epc5qitEYLoDGdamLwpw7VQmMdQWvipVDgEDU6p8H87A6r7JRgl", + "profileName": "Jesus Ruiz Briones", + "likesCount": "12", + "commentsCount": 2, + "comments": [], + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1337765004106759", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzEzMzc3NjUwMDQxMDY3NTk=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xMzM3NzY1MDA0MTA2NzU5", + "date": "2025-03-26T18:29:10.000Z", + "text": "Exelente mi gobernador, siempre en alerta, pero como siempre hay gente muy ignorante para hablar, a pero les pasa algo y de volada le echan la culpa al gobierno, k no hace nada, saludos Señor Gobernador.", + "profileUrl": "https://www.facebook.com/people/Irma-Rios/pfbid02K3mQg28WToUxCrn1Taczjhb2RvwNd6PnYD1kyfRXoJZRNmvdezUtbqc6ARkYYuosl/", + "profilePicture": "https://scontent.fnap7-1.fna.fbcdn.net/v/t39.30808-1/473422564_1116507683271750_8267497663665787154_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=101&ccb=1-7&_nc_sid=e99d92&_nc_ohc=95EQ9RhnIsQQ7kNvgFl3HUr&_nc_oc=AdlYsYsJQedIOXf4TcvF0bVbFnGCoxGWDRSVrAICWtXNFZKx-VuJvYYFxAn5CqO0GIA&_nc_zt=24&_nc_ht=scontent.fnap7-1.fna&_nc_gid=rUYBOTb4OA_-K_PBuURyag&oh=00_AYEX8FuQkup7-bPVsx_ybTNHTxeaw7GpctQnGOn9uVvY7Q&oe=67EA7844", + "profileId": "pfbid02K3mQg28WToUxCrn1Taczjhb2RvwNd6PnYD1kyfRXoJZRNmvdezUtbqc6ARkYYuosl", + "profileName": "Irma Rios", + "likesCount": "7", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=2160790277683706", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzIxNjA3OTAyNzc2ODM3MDY=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8yMTYwNzkwMjc3NjgzNzA2", + "date": "2025-03-26T18:16:43.000Z", + "text": "Muy bien mi Gober#1 siempre nos mantienen informados al estado de Nuevo leo abrazo 🫂 lo veo muy cansado", + "profileUrl": "https://www.facebook.com/chavitaa.skaa", + "profilePicture": "https://scontent.fnap7-2.fna.fbcdn.net/v/t39.30808-1/484638349_9320400904716202_2229288724079951138_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=109&ccb=1-7&_nc_sid=e99d92&_nc_ohc=lFHvRIHsFm0Q7kNvgGT3JI4&_nc_oc=AdmESO3U6-metUQ9xND0VYSRq9ZRGIugJ8pv9xWm7fgAptyk9h5LPQtI9Bw0-NW6mOE&_nc_zt=24&_nc_ht=scontent.fnap7-2.fna&_nc_gid=rUYBOTb4OA_-K_PBuURyag&oh=00_AYHZc5gBqHFe9iMeAdK-04Va8Xk9i8KgrCY9qrUtvdQ8Ng&oe=67EA7B4C", + "profileId": "pfbid0CQupaGyzABwqktHVftW5u5m42FuwzUst5mTNyQvLxYGsJNaFvQ7JaSyDEYBiCDCzl", + "profileName": "Ska Chavita", + "likesCount": "12", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1018188713568153", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzEwMTgxODg3MTM1NjgxNTM=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xMDE4MTg4NzEzNTY4MTUz", + "date": "2025-03-26T18:45:29.000Z", + "text": "Le agradecemos su aviso Sr Gobernador!! Nosotros también estamos contentos por la lluvia , Gracias a Dios !!", + "profileUrl": "https://www.facebook.com/selenia.garcia.71", + "profilePicture": "https://scontent.fnap7-2.fna.fbcdn.net/v/t39.30808-1/464472252_2988498307972756_849796262391810646_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=105&ccb=1-7&_nc_sid=e99d92&_nc_ohc=70lz3-SKP5QQ7kNvgE5T6it&_nc_oc=AdnEzu72coKiXReNnOLaxeFG4GRKToUvRbNJSGmTLsNqCzpf9V8o_OrV2F0yS1HWy2w&_nc_zt=24&_nc_ht=scontent.fnap7-2.fna&_nc_gid=rUYBOTb4OA_-K_PBuURyag&oh=00_AYFJjwLWoE8lAa4_4nWGKEOvMJkUZJji78OAL1XOR5nqZg&oe=67EA7566", + "profileId": "pfbid02nhBxSpngdEWNPzSgLq1CzaM2hQpky2GjB8MYgTGRGn6J9jBctZbbekyvdPg1h88el", + "profileName": "Selenia García", + "likesCount": "3", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=8689183631184416", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1Xzg2ODkxODM2MzExODQ0MTY=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV84Njg5MTgzNjMxMTg0NDE2", + "date": "2025-03-26T18:14:46.000Z", + "text": "Bendiciones amigo gobernador Samuel García y la gente trabajadora de Nuevo León,de todos modos le pido a Dios que llueva es mejor tener agua, que vivir en la escasez,el agua es vida,ni el oro del mundo jamás lo va a comprar,de todos modos, hay que tener conciencia para cuidarla.", + "profileUrl": "https://www.facebook.com/luis.victorino.1293", + "profilePicture": "https://scontent.fnap7-1.fna.fbcdn.net/v/t1.30497-1/453178253_471506465671661_2781666950760530985_n.png?stp=cp0_dst-png_s32x32&_nc_cat=110&ccb=1-7&_nc_sid=136b72&_nc_ohc=N52xZ5eAv9QQ7kNvgG7qEXp&_nc_oc=AdlhHUdZ2vZy4qm8rOlBjVw2uMBVNRSLigBA9D3Cxfft4T2Jzp5qe0knnU9d9EPAmOk&_nc_zt=24&_nc_ht=scontent.fnap7-1.fna&oh=00_AYEXESZkBoLrm78slpW8ZaOtbt9amH7l4OXgPx3xRayfiA&oe=680C2EBA", + "profileId": "pfbid0ovWEtPXgH9L4CkBrZZTiSt1pJumvV2GT7ikcCibNs6x2VGxeGGmvoxrJhZJtVBPRl", + "profileName": "Luis Victorino", + "likesCount": "4", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=669669982235391", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzY2OTY2OTk4MjIzNTM5MQ==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV82Njk2Njk5ODIyMzUzOTE=", + "date": "2025-03-26T20:15:45.000Z", + "text": "Grasias Señor Gobernador 🧡🧡🧡🧡🧡🧡🧡", + "profileUrl": "https://www.facebook.com/susana.caballero.752861", + "profilePicture": "https://scontent.fnap7-1.fna.fbcdn.net/v/t39.30808-1/480877331_1188570796121052_5960228146940117136_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=110&ccb=1-7&_nc_sid=e99d92&_nc_ohc=w6NzFEEtrMsQ7kNvgGzK3y2&_nc_oc=AdkmC2HSMG95mqvLIOD0Q5VAwl0yJRjbx-j4jPXHw-xNMtBAqyyb5_I9ACWju0UXMMI&_nc_zt=24&_nc_ht=scontent.fnap7-1.fna&_nc_gid=rUYBOTb4OA_-K_PBuURyag&oh=00_AYEm2obvLopDDMeK8iyWIWi8H1KZbu_p_jMMOEcQeOgK2g&oe=67EA93DB", + "profileId": "pfbid02QihygTPgi7YMX93TwY8cNaPBDnKztucknFNGQgEHRkQkQiQgTjpwdNm8wQuKasuTl", + "profileName": "Susana Caballero", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=2152814741828598", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzIxNTI4MTQ3NDE4Mjg1OTg=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8yMTUyODE0NzQxODI4NTk4", + "date": "2025-03-26T20:12:48.000Z", + "text": "Bendiciones para todos ustedes gobernador bendiciones 🙏", + "profileUrl": "https://www.facebook.com/angel.evangelista.849968", + "profilePicture": "https://scontent.feau1-1.fna.fbcdn.net/v/t39.30808-1/484965904_122128458290619028_13066884975160629_n.jpg?stp=cp0_dst-jpg_p32x32_tt6&_nc_cat=104&ccb=1-7&_nc_sid=e99d92&_nc_ohc=FpN5yOllnrwQ7kNvgG_xm0K&_nc_oc=AdlazkFqFAY5cts_-YKiRyKlb5c1DeJqSSrkO6z_7r1gUHML9oiNh-j6aqXBSKguPBI&_nc_zt=24&_nc_ht=scontent.feau1-1.fna&_nc_gid=ipHgkovbfAZm6fsLBbJn3Q&oh=00_AYF3Q6aRcjDUx7eAmi9dQB9JMG8xsw1gowuLB5eGYSnC7g&oe=67EA941C", + "profileId": "pfbid02pWgkoNDsb8bWcw9buuAJE8PDaocy8q3AEwKvw2i9VMPVcXhWtv928jca1SQz4Frfl", + "profileName": "Angel Evangelista", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=569761075419903", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzU2OTc2MTA3NTQxOTkwMw==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV81Njk3NjEwNzU0MTk5MDM=", + "date": "2025-03-26T18:28:46.000Z", + "text": "Ya hacía falta la lluvia 💦☂️🌧️☔", + "profileUrl": "https://www.facebook.com/claudiav.wongreyes", + "profilePicture": "https://scontent.feau1-1.fna.fbcdn.net/v/t1.6435-1/151266671_1114797758987383_644118824846581654_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=106&ccb=1-7&_nc_sid=e99d92&_nc_ohc=lGGhA845qAYQ7kNvgFzytGr&_nc_oc=AdnmVrNrIz1-tjb3K9IPRLKqFDrWb3Q6qbVtMHnwib_XMqAAla-HpJP5WPxwW57002E&_nc_zt=24&_nc_ht=scontent.feau1-1.fna&_nc_gid=ipHgkovbfAZm6fsLBbJn3Q&oh=00_AYFbei_nbvVxIvOsItvYrkRqE-9bN8o0vBqatf45cLAYuQ&oe=680C3CE2", + "profileId": "pfbid0dLdn9CAVv8bJPsrgEi9sJQbKdK42qW7CfNni14zA8VfYPEay4Byp1Ad92hPgL548l", + "profileName": "Claudia Verónica Wong", + "likesCount": "2", + "commentsCount": 2, + "comments": [], + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=674609188263289", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzY3NDYwOTE4ODI2MzI4OQ==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV82NzQ2MDkxODgyNjMyODk=", + "date": "2025-03-26T18:51:06.000Z", + "text": "Gracias Sr Gobernador Samuel García por decirnos que va a llover fui por la leche bien temprano y la verdura y cuando salí de la tienda remojada que me di con la lluvia 🌧", + "profileUrl": "https://www.facebook.com/mariapatricia.riveracostilla", + "profilePicture": "https://scontent.feau1-1.fna.fbcdn.net/v/t39.30808-1/475797273_9176798342439895_1271245844636424668_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=104&ccb=1-7&_nc_sid=e99d92&_nc_ohc=rWiBAVIZriYQ7kNvgHXNewG&_nc_oc=AdlSlejpY16NMQXQDMOP5tpNUzw2iXGsyFWEeO5ObzBv_coIHWfEj3wEgnLBpWdXHNg&_nc_zt=24&_nc_ht=scontent.feau1-1.fna&_nc_gid=ipHgkovbfAZm6fsLBbJn3Q&oh=00_AYFRfy2eLMGYIYxus-Bb0M1nibz_kjOGu7RhzpUw3v2oiw&oe=67EA8874", + "profileId": "pfbid0buk8bNKZHS6xjDxNrzBjCo2iyBxEZpyvzJwDGJF1TTVoaP8NZZcAFgshEcHjhPcNl", + "profileName": "Maria Patricia Rivera Costilla", + "likesCount": "3", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=3015078611989047", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzMwMTUwNzg2MTE5ODkwNDc=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8zMDE1MDc4NjExOTg5MDQ3", + "date": "2025-03-26T18:19:02.000Z", + "text": "Espero y si Samuel \nBendiciones", + "profileUrl": "https://www.facebook.com/roswrosalba.gonzalez", + "profilePicture": "https://scontent.feau1-1.fna.fbcdn.net/v/t39.30808-1/485659899_1697187957867643_8683645362198128852_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=109&ccb=1-7&_nc_sid=e99d92&_nc_ohc=OBUGPeU4ulQQ7kNvgGCI4Ar&_nc_oc=AdnnU5y-KngBNYE4BTHujPF2jEdfM_P6n_QClJ25q2jUKkahOAQrUlisCff0wqEVpSs&_nc_zt=24&_nc_ht=scontent.feau1-1.fna&_nc_gid=ipHgkovbfAZm6fsLBbJn3Q&oh=00_AYFch1xXP46BKD5PQ8LYANvEcpJAFIL_kUvD9mIk4zFMKA&oe=67EA83B7", + "profileId": "pfbid02aAPwt52YKhrQ2AgZwrgSVbG8KarXUtccfq4HqQ68ZSv5CVoPia24Mn1AAA2Pi3oWl", + "profileName": "Rosw Rosalba González", + "likesCount": "3", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=2392293254440904", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzIzOTIyOTMyNTQ0NDA5MDQ=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8yMzkyMjkzMjU0NDQwOTA0", + "date": "2025-03-26T21:17:38.000Z", + "text": "Gracias a Dios lluvias, gracias por avisar, saludos Sr Gobernador 🙏", + "profilePicture": "https://scontent.feau1-1.fna.fbcdn.net/v/t39.30808-1/475149992_10236758254760914_6055935821245727620_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=108&ccb=1-7&_nc_sid=e99d92&_nc_ohc=Ajo9kM7sDGMQ7kNvgHPlzuS&_nc_oc=Adkpzbs1KHbghqV6J_0_kgch_zHJhJiE8KOdeffFUBHaf5m-bLaH2Wq3mK7jFmyTpT0&_nc_zt=24&_nc_ht=scontent.feau1-1.fna&_nc_gid=ipHgkovbfAZm6fsLBbJn3Q&oh=00_AYEn203JWfwz6WiXDfqqs8g938gkUU9U2o6kslvuZ7_WVQ&oe=67EA6C92", + "profileId": "pfbid02kh6h69s9vpRafjVtKgDddipTpxmC2XxPiDWiYsjKyX3unhM9oBUwnMUBr5ftog1hl", + "profileName": "Dora Elia Gutierrez", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=3628616460765525", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzM2Mjg2MTY0NjA3NjU1MjU=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8zNjI4NjE2NDYwNzY1NTI1", + "date": "2025-03-26T20:01:41.000Z", + "text": "Toda la cargada de Allende del a rollo mireles ba rumbo a la presa el cuchillo gracias", + "profileUrl": "https://www.facebook.com/people/Manuel-Salazar/pfbid02sjDa4kx35jjkKygSuoXejVESLR69LW6fcgH11xcFXPkSpkD4DNGiNM2D1ZkwtvHhl/", + "profilePicture": "https://scontent.feau1-1.fna.fbcdn.net/v/t39.30808-1/328758577_743028213877449_1444085031460241402_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=101&ccb=1-7&_nc_sid=e99d92&_nc_ohc=A2LldsrpticQ7kNvgF1IHst&_nc_oc=Adm7Uo7fhq5n37oNAEH7e-i88Sg3IBQ6hAvFj_vEXt9cv-AGWlf3jyD_mCE8Oy8uffg&_nc_zt=24&_nc_ht=scontent.feau1-1.fna&_nc_gid=ipHgkovbfAZm6fsLBbJn3Q&oh=00_AYFp83Rhwlx5b2qvwp8531lWFkyhojliDXtiM8KkfNoylA&oe=67EA8493", + "profileId": "pfbid02sjDa4kx35jjkKygSuoXejVESLR69LW6fcgH11xcFXPkSpkD4DNGiNM2D1ZkwtvHhl", + "profileName": "Manuel Salazar", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=822618116749470", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzgyMjYxODExNjc0OTQ3MA==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV84MjI2MTgxMTY3NDk0NzA=", + "date": "2025-03-26T20:16:51.000Z", + "text": "Gracias a Dios,ya llovió 😀", + "profileUrl": "https://www.facebook.com/leonardo.medranozendejas.3", + "profilePicture": "https://scontent.feau1-1.fna.fbcdn.net/v/t39.30808-1/429949957_7054872764638615_3669323189301074642_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=106&ccb=1-7&_nc_sid=e99d92&_nc_ohc=OH1J1_bqi6MQ7kNvgFgTb6o&_nc_oc=Adkgb69SxKfG9Eyv9imSC5AgSWBOZcrb4oS0FBigyMkSFCOz1rYUMK3Nh0bdxu__95E&_nc_zt=24&_nc_ht=scontent.feau1-1.fna&_nc_gid=ipHgkovbfAZm6fsLBbJn3Q&oh=00_AYEjXhPoOP1HrUIPLxv5vHli3NwbOLgZ7o6SWBEOCvUZ9A&oe=67EA887D", + "profileId": "pfbid0GBzB3bLndR82xVqnsTfix2LvP3y4STeuEgWvnkGzHA25C94MWUa192NMe1vHPwiUl", + "profileName": "John Med Zendejas", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=951160697180035", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1Xzk1MTE2MDY5NzE4MDAzNQ==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV85NTExNjA2OTcxODAwMzU=", + "date": "2025-03-26T19:29:25.000Z", + "text": "Excelente mi gober di oro", + "profileUrl": "https://www.facebook.com/alvaro.brionespalacios", + "profilePicture": "https://scontent.feau1-1.fna.fbcdn.net/v/t39.30808-1/315214889_8517904344901147_5802729119473470932_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=103&ccb=1-7&_nc_sid=e99d92&_nc_ohc=qrzL02XW6ccQ7kNvgEIXAP8&_nc_oc=Adn_AOjS80SzxxlvPcaffC5U9wLFP9T8qVHertEAPsSJ8p_skPods6B3vH32Lc1xw4s&_nc_zt=24&_nc_ht=scontent.feau1-1.fna&_nc_gid=ipHgkovbfAZm6fsLBbJn3Q&oh=00_AYHH3tvdFNTeFx2fu7rveo4B_WwYSeLjFLE2PwdgHBbMbA&oe=67EA974C", + "profileId": "pfbid0PnsJrzr4J5JYJthvUbjXVrvqPvpu7Bj7MvRyacfEXZBLVgimUzJVsUdizf9Uv5vul", + "profileName": "Alvaro Briones Palacios", + "likesCount": "1", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=684065517390802", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzY4NDA2NTUxNzM5MDgwMg==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV82ODQwNjU1MTczOTA4MDI=", + "date": "2025-03-26T18:27:53.000Z", + "text": "Saludos desde Las Rosas, Chiapas!!", + "profileUrl": "https://www.facebook.com/rafaelrigoberto.padillavillagomez", + "profilePicture": "https://scontent.feau1-1.fna.fbcdn.net/v/t39.30808-1/477439776_9918857354810155_2578524807784266156_n.jpg?stp=c120.0.720.720a_cp0_dst-jpg_s32x32_tt6&_nc_cat=103&ccb=1-7&_nc_sid=e99d92&_nc_ohc=GPp6m0kE3iYQ7kNvgF-nAvU&_nc_oc=AdkMWYfvRkaSnfAGO8zOhTCAXi_Ol0zg_otKBkVtwVdIIbYkzQ3M4v3Dxxj_lLGnT3c&_nc_zt=24&_nc_ht=scontent.feau1-1.fna&_nc_gid=ipHgkovbfAZm6fsLBbJn3Q&oh=00_AYH29xQvk39CjRHQuMdzLpgGV5xGaAX0kpnL9adxKN1_VA&oe=67EA7B1A", + "profileId": "pfbid02VpSykWrUaGoRNZUbsVAVpQndMoyvz7xGuzJM8AtZLKPXqsc1wmEnirySyxoMSTepl", + "profileName": "Rafael Rigoberto Padilla Villagomez", + "likesCount": "1", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1845333902907657", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzE4NDUzMzM5MDI5MDc2NTc=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xODQ1MzMzOTAyOTA3NjU3", + "date": "2025-03-26T23:46:01.000Z", + "text": "Samuel Tlaloc gracias", + "profileUrl": "https://www.facebook.com/nelly.hernandez.79230", + "profilePicture": "https://scontent.feau1-1.fna.fbcdn.net/v/t39.30808-1/483923769_9879210902089089_5478927751365070958_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=102&ccb=1-7&_nc_sid=e99d92&_nc_ohc=S3YH9Mkc__wQ7kNvgEYfgUs&_nc_oc=AdmGtjBROzQ8h1aR4UTGbtAWkvJWf_ZLZi-fBeLrunnBn6pnhvPkmNXfGTEx6YfMPjs&_nc_zt=24&_nc_ht=scontent.feau1-1.fna&_nc_gid=ipHgkovbfAZm6fsLBbJn3Q&oh=00_AYHSPtZlxpdt9-9UGLHlR6b6gFCWlPGbijJwznZg2fQtPg&oe=67EA9E48", + "profileId": "pfbid02EVNNLbZEijCTEZMr5wr59mN5pEavd6fYdo9TPwkxBMxs2GLhKRLgo4ScksGP2RDwl", + "profileName": "Dos Besos Bye", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1234930504715264", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzEyMzQ5MzA1MDQ3MTUyNjQ=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xMjM0OTMwNTA0NzE1MjY0", + "date": "2025-03-27T01:37:44.000Z", + "text": "Saludos y muchas bendiciones", + "profileUrl": "https://www.facebook.com/people/Rolando-Vazquez/pfbid0xrbbUcMQdGhDWNFBycjqq4VZqMScnynQkmYJFGkqK9WyWLPzp51533hyCgCUDNSal/", + "profilePicture": "https://scontent.fgig14-2.fna.fbcdn.net/v/t39.30808-1/269739024_104639478755672_5969436950007915523_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=106&ccb=1-7&_nc_sid=e99d92&_nc_ohc=33HdKUpHfvQQ7kNvgG5fOJd&_nc_oc=AdnuUKZMLUEpPnCQFsb7wsbraou-_FX4BKTZZi_7Cr4EzZ9uX_lYlDBryg2cl-CbUQs&_nc_zt=24&_nc_ht=scontent.fgig14-2.fna&_nc_gid=vSK-CDRfaHylkQF_kzt0ZQ&oh=00_AYGouM6DwbsF3hFq9Ho_kakLzV61RpcBME5eOOcZxHEK2g&oe=67EA7A25", + "profileId": "pfbid0xrbbUcMQdGhDWNFBycjqq4VZqMScnynQkmYJFGkqK9WyWLPzp51533hyCgCUDNSal", + "profileName": "Rolando Vazquez", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1325251858590381", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzEzMjUyNTE4NTg1OTAzODE=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xMzI1MjUxODU4NTkwMzgx", + "date": "2025-03-26T20:39:34.000Z", + "text": "Solucionando 🫡", + "profileUrl": "https://www.facebook.com/oliver.vega.35", + "profilePicture": "https://scontent.fgig14-2.fna.fbcdn.net/v/t39.30808-1/471437134_10160667254166441_1736799591523988583_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=103&ccb=1-7&_nc_sid=e99d92&_nc_ohc=SIDzy9EbCeYQ7kNvgFWX5ik&_nc_oc=AdlRA0Gqfl-2phMG70YhiOr1NT9KQbaHxj0fqAkT3wtACObmc9Dwk3EY-m22LFdq1BU&_nc_zt=24&_nc_ht=scontent.fgig14-2.fna&_nc_gid=vSK-CDRfaHylkQF_kzt0ZQ&oh=00_AYEL1OBWpq9LHreBnd2rQJWMFy0xWA8gue1pgObx1_QcHA&oe=67EA97F2", + "profileId": "pfbid02pcNA997Fhptj97HiFhsBLiPwtRpiAHEQ9EoMicEZXBj9UmMWLPoqRpRXqvtYZtowl", + "profileName": "Oliver Vega", + "likesCount": "2", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=639068995610673", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzYzOTA2ODk5NTYxMDY3Mw==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV82MzkwNjg5OTU2MTA2NzM=", + "date": "2025-03-26T18:35:05.000Z", + "text": "Saludos mi gober", + "profileUrl": "https://www.facebook.com/people/Aldo-Hurtado-Dorado/pfbid0J98R4ntQFtPVWkVgzB25gRvC3rgCHeHc4NNf3m4MoXz7SLM1Twk3iYsqBARfoaBQl/", + "profilePicture": "https://scontent.fgig14-2.fna.fbcdn.net/v/t39.30808-1/466623790_539751118949412_3279193135908149901_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=106&ccb=1-7&_nc_sid=e99d92&_nc_ohc=4Eor3dc736MQ7kNvgGmC1ct&_nc_oc=AdlcGH4yVePaBh4VWR2kCnt8TnPeL0ScPKghOqwBBwwqAAfGqILZeRPGYgU7b_iCCzc&_nc_zt=24&_nc_ht=scontent.fgig14-2.fna&_nc_gid=vSK-CDRfaHylkQF_kzt0ZQ&oh=00_AYHgudJqnH0QbMSsmgh2LzE8pbvQ6TeD_YEuyXTiZxwaXw&oe=67EA9E69", + "profileId": "pfbid0J98R4ntQFtPVWkVgzB25gRvC3rgCHeHc4NNf3m4MoXz7SLM1Twk3iYsqBARfoaBQl", + "profileName": "Aldo Hurtado Dorado", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=551964994040105", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzU1MTk2NDk5NDA0MDEwNQ==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV81NTE5NjQ5OTQwNDAxMDU=", + "date": "2025-03-26T21:43:55.000Z", + "text": "Se.Gobernador\nGracias por estas esperadas lluvias.... bendiciones", + "profilePicture": "https://scontent.fgig14-2.fna.fbcdn.net/v/t39.30808-1/184373320_1703433399843482_314953367595750202_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=106&ccb=1-7&_nc_sid=e99d92&_nc_ohc=Po5HdODH48MQ7kNvgGUirAS&_nc_oc=AdmuCuF0xjl9BhiBNjY34yagyACMIxcDOBSkwcf5Uj9ljdc_LiRJxcr4cPFSLvuicws&_nc_zt=24&_nc_ht=scontent.fgig14-2.fna&_nc_gid=vSK-CDRfaHylkQF_kzt0ZQ&oh=00_AYEiH6g6jwLsrQisjg0xfCXd1ds7tG7dKTGSI57dfGxfTQ&oe=67EA7602", + "profileId": "pfbid02PnKZ3XXvP5JdA3p4E14LTEpUidUMuiNRaAjJrXRtCymdpa19HQbwGSeosBdfWtZ5l", + "profileName": "Cesar Alonso", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1790699621787324", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzE3OTA2OTk2MjE3ODczMjQ=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xNzkwNjk5NjIxNzg3MzI0", + "date": "2025-03-26T18:15:51.000Z", + "text": "buenas noticias para todos saludos gobernador", + "profileUrl": "https://www.facebook.com/pedro.hinojosa.85", + "profilePicture": "https://scontent.fgig14-2.fna.fbcdn.net/v/t39.30808-1/461855011_3954069151583727_189815657627518639_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=111&ccb=1-7&_nc_sid=e99d92&_nc_ohc=_ZDBBetNLkkQ7kNvgEL9rAK&_nc_oc=AdkKtP5QS0ZBZfNlAHN4t8VIYVY1WuWxGpU-A06EErrB1LqmBHxX3HgtoSv39uqUMl4&_nc_zt=24&_nc_ht=scontent.fgig14-2.fna&_nc_gid=vSK-CDRfaHylkQF_kzt0ZQ&oh=00_AYFWImERY1Us4Tqvnsx5qkQZ4RuQe9GF_-VkYr7Q7rFzwQ&oe=67EA793E", + "profileId": "pfbid02r9orf8Eg3A9oqEQ4c1M4emPxk1Fj3Ct1FqqcJwLT1fcxhRFLB3dJvrXDZNa8KT48l", + "profileName": "Pedro Hinojosa", + "likesCount": "2", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1723902835223432", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzE3MjM5MDI4MzUyMjM0MzI=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xNzIzOTAyODM1MjIzNDMy", + "date": "2025-03-26T18:33:42.000Z", + "text": "Buenos días señor gobernador \nSaludos desde Cadereyta ❤️🍊", + "profileUrl": "https://www.facebook.com/ventas.anell.2024", + "profilePicture": "https://scontent.fgig14-2.fna.fbcdn.net/v/t39.30808-1/482029108_122158883462353718_515424450464780523_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=105&ccb=1-7&_nc_sid=111fe6&_nc_ohc=vdeS_Db6GmcQ7kNvgG2iIy1&_nc_oc=AdnHfgx46zycMnD7kvc-TrLbbrtL30KNfJb9cVZDcVbOnzS_Ubpp4WR531Hh8t2YHKA&_nc_zt=24&_nc_ht=scontent.fgig14-2.fna&_nc_gid=vSK-CDRfaHylkQF_kzt0ZQ&oh=00_AYHb-8NzSGT5MRGnxz6LVtMhsO5n1kVmIX1715tuCzUEBA&oe=67EA8ACF", + "profileId": "61560611545182", + "profileName": "Ventas Anell", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=644177691696800", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzY0NDE3NzY5MTY5NjgwMA==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV82NDQxNzc2OTE2OTY4MDA=", + "date": "2025-03-26T21:07:21.000Z", + "text": "Feliz día Samuel", + "profileUrl": "https://www.facebook.com/people/Yare-Sweet/pfbid02N8sVudXzq2ADSULdQ3GZ5mfpZvyaxBmNToqqtZvTqHsYwbbYB5jnr8zjaLkDH8Q7l/", + "profilePicture": "https://scontent.fgig14-2.fna.fbcdn.net/v/t39.30808-1/475209690_122101231478749521_6502395951873928368_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=109&ccb=1-7&_nc_sid=e99d92&_nc_ohc=r4J6jTwakOsQ7kNvgGgUz0M&_nc_oc=AdnAlVSbAfAQMa6YIoxxUeWP8S37uAQML2PXUHOqQN6Fb4AhN72YAnezyb9W-9P63es&_nc_zt=24&_nc_ht=scontent.fgig14-2.fna&_nc_gid=vSK-CDRfaHylkQF_kzt0ZQ&oh=00_AYG5Pir0O86xPERFGLCiKtPTykN8NTmlgpfIlxvc77rE6A&oe=67EAA055", + "profileId": "pfbid02N8sVudXzq2ADSULdQ3GZ5mfpZvyaxBmNToqqtZvTqHsYwbbYB5jnr8zjaLkDH8Q7l", + "profileName": "Yare Sweet", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1714077709147663", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzE3MTQwNzc3MDkxNDc2NjM=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xNzE0MDc3NzA5MTQ3NjYz", + "date": "2025-03-26T18:15:07.000Z", + "text": "Excelente noticia Gobernador", + "profilePicture": "https://scontent.fgig14-2.fna.fbcdn.net/v/t39.30808-1/470204935_9037465266273736_8017677067262202716_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=101&ccb=1-7&_nc_sid=1d2534&_nc_ohc=p7y01OJ7gYgQ7kNvgEj91b0&_nc_oc=AdnBLew6N9qWG5AlrgpLmvyXyMuq_8SXkUCQwjpSs9IdpqYL6E_dQRoAm1eG_FGuePY&_nc_zt=24&_nc_ht=scontent.fgig14-2.fna&_nc_gid=vSK-CDRfaHylkQF_kzt0ZQ&oh=00_AYHerAxMT6kS9Sz6Kh0LCxmQJrZYxCdLUPUwNKMSz528NQ&oe=67EA9FE7", + "profileId": "100000310575883", + "profileName": "Myriam González Hernández", + "likesCount": "2", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1005731614322209", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzEwMDU3MzE2MTQzMjIyMDk=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xMDA1NzMxNjE0MzIyMjA5", + "date": "2025-03-27T01:38:01.000Z", + "text": "Gracias por tenernos informados Sr Gobernador.", + "profileUrl": "https://www.facebook.com/alicia.guevara.777", + "profilePicture": "https://scontent.fgig14-2.fna.fbcdn.net/v/t39.30808-1/448261298_3646429945570760_1144335219206694073_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=100&ccb=1-7&_nc_sid=e99d92&_nc_ohc=5vxAtLGjRRwQ7kNvgHY0FU5&_nc_oc=Adm-Lv8uVOtJp74ug0bZ1LU-IvY8JFIuJAYrUn9zX_ztyQwqmyMiq8nRxStr3tx7r1A&_nc_zt=24&_nc_ht=scontent.fgig14-2.fna&_nc_gid=vSK-CDRfaHylkQF_kzt0ZQ&oh=00_AYEqEA8htGTHD9ad9I8_teRWySxVTVFzS8c2278iQiO0og&oe=67EA711D", + "profileId": "pfbid02tUgaRRLLaLXwLWf4m5xdCiCbbfDEu4o7Q8UsUUa6W7rBNN6nVFsGGHJhDu7tn1ETl", + "profileName": "Alicia Guevara", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1560556817942128", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzE1NjA1NTY4MTc5NDIxMjg=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xNTYwNTU2ODE3OTQyMTI4", + "date": "2025-03-26T18:27:09.000Z", + "text": "Bendiciones samuel adios gracias por la lluvia ke nos hase tanta falta", + "profileUrl": "https://www.facebook.com/people/Blanca-Serna/pfbid068Ue9yS4ByVMxWQGJkHfVrPRuRSYECpqi8sxFA4d8BCgZ5qXPH82ZbscSKQVVPrUl/", + "profilePicture": "https://scontent.fgig14-2.fna.fbcdn.net/v/t39.30808-1/486023765_122201689196156771_9040234222693078986_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=103&ccb=1-7&_nc_sid=e99d92&_nc_ohc=vJQHxI4DrrYQ7kNvgF6Wspi&_nc_oc=Adn9XDtX13AzyIxgUzkFufmyBiFLiBN8qIvg8Sn5ItKHIphxl4Cwtrc13ZGtgZzuWo4&_nc_zt=24&_nc_ht=scontent.fgig14-2.fna&_nc_gid=vSK-CDRfaHylkQF_kzt0ZQ&oh=00_AYE9Wins_7nLanRxaZaGwfAURo3vxVh2beN-LnVfAlBvLg&oe=67EA7EE0", + "profileId": "pfbid068Ue9yS4ByVMxWQGJkHfVrPRuRSYECpqi8sxFA4d8BCgZ5qXPH82ZbscSKQVVPrUl", + "profileName": "Blanca Serna", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=555630750890343", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzU1NTYzMDc1MDg5MDM0Mw==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV81NTU2MzA3NTA4OTAzNDM=", + "date": "2025-03-26T18:13:58.000Z", + "text": "Excelente gobernador", + "profileUrl": "https://www.facebook.com/people/Marco-Guerrero/pfbid0zC8sGWDPmUnsKugKKkpEMWnaHMR1iKrjGUmWzARQuuxbfGgHihrsFp5F9kviC5Vjl/", + "profilePicture": "https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-1/331476541_670206378217043_5494745430061645608_n.jpg?stp=c0.0.720.720a_cp0_dst-jpg_s32x32_tt6&_nc_cat=106&ccb=1-7&_nc_sid=e99d92&_nc_ohc=J8duTyYhpvMQ7kNvgHm7G8L&_nc_oc=AdmUaEBPBqgSwH0-N2n4AHcUDqTbOKHfGiasC5EpS7figTr_qcnYX8WlX-dB3cfXdVQ&_nc_zt=24&_nc_ht=scontent-lax3-2.xx&_nc_gid=krzFu2-tQZnbEk_R-dGxCg&oh=00_AYGtPOYu8hDbyTKpon-EMKOKju6B_hPrQlaS50S-F01NMg&oe=67EA88EE", + "profileId": "pfbid0zC8sGWDPmUnsKugKKkpEMWnaHMR1iKrjGUmWzARQuuxbfGgHihrsFp5F9kviC5Vjl", + "profileName": "Marco Guerrero", + "likesCount": "2", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=452134797923284", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzQ1MjEzNDc5NzkyMzI4NA==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV80NTIxMzQ3OTc5MjMyODQ=", + "date": "2025-03-26T18:44:38.000Z", + "text": "Excelente gober 👍", + "profileUrl": "https://www.facebook.com/people/Jose-%C3%91a%C3%B1ez/pfbid05sWriD2w17r1uDtTbt2FoaRo78PMvngmffG989ornop2J19j2HbZTNdvpNXkGWhCl/", + "profilePicture": "https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-1/474227397_122106655646728373_5094287754242483501_n.jpg?stp=c120.0.768.768a_cp0_dst-jpg_s32x32_tt6&_nc_cat=106&ccb=1-7&_nc_sid=e99d92&_nc_ohc=f9DFKy4nqLUQ7kNvgH0uEfL&_nc_oc=AdlsYCfIen0JIY5V70BuVub-oBrGTABFbAwQM3TQm65xyZXDF4P6-L41tf329YY9wGw&_nc_zt=24&_nc_ht=scontent-lax3-2.xx&_nc_gid=krzFu2-tQZnbEk_R-dGxCg&oh=00_AYF9ZmcjGz9UHYXEaZw8IQpeVf5u7jFixUGkoirhmAiegg&oe=67EA999B", + "profileId": "pfbid05sWriD2w17r1uDtTbt2FoaRo78PMvngmffG989ornop2J19j2HbZTNdvpNXkGWhCl", + "profileName": "Jose Ñañez", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1256781239204140", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzEyNTY3ODEyMzkyMDQxNDA=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xMjU2NzgxMjM5MjA0MTQw", + "date": "2025-03-26T18:36:37.000Z", + "text": "Que. Dios. Lo. Bendiga gob son. Bendiciones de. Dios", + "profileUrl": "https://www.facebook.com/people/Eulalio-Ayala-Cortina/pfbid02Wo8RRhZWQ9VcUcCXde8CrrjNEzCyxBUP6UvRXr2YvgZrG17meB1k7A4ULXnxFroUl/", + "profilePicture": "https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-1/436498400_2178789499133230_2177407480251592153_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=105&ccb=1-7&_nc_sid=e99d92&_nc_ohc=tXxkMLp_71QQ7kNvgE7jnxs&_nc_oc=AdlsQW61C0poPtMnkfudlntuDcqtOi4D3wenMiRgs8HhwhO6Yf4P3gGz0b_Ke7D4T9M&_nc_zt=24&_nc_ht=scontent-lax3-1.xx&_nc_gid=krzFu2-tQZnbEk_R-dGxCg&oh=00_AYEaaQ5szxPnLGh1vNWLaHj7EaymGKmkOwGNnU9tFdCwig&oe=67EA7B70", + "profileId": "pfbid02Wo8RRhZWQ9VcUcCXde8CrrjNEzCyxBUP6UvRXr2YvgZrG17meB1k7A4ULXnxFroUl", + "profileName": "Eulalio Ayala Cortina", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1547424949284417", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzE1NDc0MjQ5NDkyODQ0MTc=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xNTQ3NDI0OTQ5Mjg0NDE3", + "date": "2025-03-27T00:42:27.000Z", + "text": "Que se hagan efectivas las leyes contra el maltrato esto es de todos los días \nhttps://www.facebook.com/share/p/1Htxsg1YmZ/?mibextid=wwXIfr", + "profileUrl": "https://www.facebook.com/michelle.delcastillo.7", + "profilePicture": "https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-1/466972135_9029970483721194_1970947355033532756_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=105&ccb=1-7&_nc_sid=e99d92&_nc_ohc=2_foN4inxwQQ7kNvgELXrpG&_nc_oc=AdmMsJ0ZCqSc5VAk9--m1cGqy5eO3eJkC79EjtbNeviTqCTm-GoUAD1Tai7QaFVAf-Y&_nc_zt=24&_nc_ht=scontent-lax3-1.xx&_nc_gid=krzFu2-tQZnbEk_R-dGxCg&oh=00_AYHlP3Q_OvO_LfG2ZpA7vDlOE8XPSJaki0O6AqOQA4Lmiw&oe=67EA9E01", + "profileId": "pfbid02wPju19fTgwRNMEF2T7dbTK2WJychJt48dUmgovy49P1guChhsrZc3bWuaHrZ7UXcl", + "profileName": "Michelle del Castillo", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=691303943254194", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzY5MTMwMzk0MzI1NDE5NA==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV82OTEzMDM5NDMyNTQxOTQ=", + "date": "2025-03-26T21:00:13.000Z", + "text": "Una tarjeta sam", + "profileUrl": "https://www.facebook.com/mercedes.samaniego.399", + "profilePicture": "https://scontent-lax3-1.xx.fbcdn.net/v/t1.30497-1/453178253_471506465671661_2781666950760530985_n.png?stp=cp0_dst-png_s32x32&_nc_cat=110&ccb=1-7&_nc_sid=136b72&_nc_ohc=N52xZ5eAv9QQ7kNvgHkFPZz&_nc_oc=AdmypVf0UWoPUSqrECsHMDq5uHgXIL76MXum2UrKZtCOUjmVmF5WEwDUqciQJjm0j1I&_nc_zt=24&_nc_ht=scontent-lax3-1.xx&oh=00_AYG1LTMoDvwoAd314wckswVpwlx8fsuw7JfpKDz004LRJA&oe=680C2EBA", + "profileId": "pfbid02zQNG7DV69zCm66MAAtBpAcCp9yrTtkU6FTT4eBsUphZuta26GtAuWdcpStA6Cuf8l", + "profileName": "Mercedes Samaniego", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=924488949642078", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzkyNDQ4ODk0OTY0MjA3OA==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV85MjQ0ODg5NDk2NDIwNzg=", + "date": "2025-03-26T20:16:51.000Z", + "text": "Gracias, gracias a Dios 🙏🏼 y gracias a ti por el mantenimiento y conservación de las presas ❤️", + "profileUrl": "https://www.facebook.com/people/Lupita-Reyes/pfbid0itFw4pDg8GPhsDEy6oNAqnupKtWKqi7ns7EmdcEBF4Xwu6kES9EgUPxCdGsDghh4l/", + "profilePicture": "https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-1/234885910_332713788567574_5118981278945458032_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=103&ccb=1-7&_nc_sid=e99d92&_nc_ohc=evNRnbPgmk0Q7kNvgG5fwpM&_nc_oc=Adk4sGPE3B6TgzqlJY0r7uvePi2DeU_cLp-8a2U4mL0IIVqNHx5ovFxk8SxXStGTRjo&_nc_zt=24&_nc_ht=scontent-lax3-2.xx&_nc_gid=krzFu2-tQZnbEk_R-dGxCg&oh=00_AYEM6O7Bfk5PaSiezMij7gCzzTYN5zfviYgiqK87QhuXbw&oe=67EA7F95", + "profileId": "pfbid0itFw4pDg8GPhsDEy6oNAqnupKtWKqi7ns7EmdcEBF4Xwu6kES9EgUPxCdGsDghh4l", + "profileName": "Lupita Reyes", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=2042054786298323", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzIwNDIwNTQ3ODYyOTgzMjM=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8yMDQyMDU0Nzg2Mjk4MzIz", + "date": "2025-03-26T20:40:52.000Z", + "text": "Mañana no habrá clases??🤣🤣🤣", + "profileUrl": "https://www.facebook.com/people/Salomon-Hernandez/pfbid0x78cZBcCyWnv2HzSbugYM2z6XCd8ze7zFK2XvPFgjyFPRCByDnwpw3x2bRGVMyvUl/", + "profilePicture": "https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-1/474873597_594639000090262_2670230089976985526_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=100&ccb=1-7&_nc_sid=e99d92&_nc_ohc=UaxgItH6iWIQ7kNvgG419gs&_nc_oc=Admo7IqtEEU6rzwlHfz0MjUYknpt4NgI4EEdosbqq-CkCrfVMrG9jS3I6gzR0Me9Vlg&_nc_zt=24&_nc_ht=scontent-lax3-2.xx&_nc_gid=krzFu2-tQZnbEk_R-dGxCg&oh=00_AYEV7uQiiqJFUSz4iMn8ctLREfeJ1wRhdlyGsUYHBsAKHA&oe=67EA7555", + "profileId": "pfbid0x78cZBcCyWnv2HzSbugYM2z6XCd8ze7zFK2XvPFgjyFPRCByDnwpw3x2bRGVMyvUl", + "profileName": "Salomon Hernandez", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1354847425835805", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzEzNTQ4NDc0MjU4MzU4MDU=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xMzU0ODQ3NDI1ODM1ODA1", + "date": "2025-03-26T20:12:19.000Z", + "text": "Gracias por informar 👍", + "profilePicture": "https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-1/485877029_3823656911279828_2036293005648462617_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=107&ccb=1-7&_nc_sid=e99d92&_nc_ohc=uloJgAxHpFIQ7kNvgEMbF7G&_nc_oc=AdlZxIh8-0xsuMurRbeh3Z1CbVooe1k-ICCc_5M_ZYCl6SCk1cgKKsr8q2UVYuJkMfE&_nc_zt=24&_nc_ht=scontent-lax3-2.xx&_nc_gid=krzFu2-tQZnbEk_R-dGxCg&oh=00_AYGTSDeLYxoqoU-RfAD-hIjXV2VX_2B0SuY9MCXlXx-Rzg&oe=67EA7046", + "profileId": "pfbid02eSdXZVuAfkc4mZG31kCMWdcLr8Zr2zLMtFgihqgrU5af9W6uAQcPXdxe5riPRVx8l", + "profileName": "Raquel de la Cruz", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=2089185604837011", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzIwODkxODU2MDQ4MzcwMTE=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8yMDg5MTg1NjA0ODM3MDEx", + "date": "2025-03-27T01:29:07.000Z", + "text": "Gad. Animo.", + "profileUrl": "https://www.facebook.com/people/Chapa-Guti%C3%A9rrez/pfbid02uAes6MvkX2RTrJwGFdRmBFsXbMNhSkqQhM2HtsJAE2Ap8AJwZwTJagaQkA6satdfl/", + "profilePicture": "https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-1/483899129_683674857338134_6019266779543561259_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=104&ccb=1-7&_nc_sid=e99d92&_nc_ohc=uy-F_upYrkgQ7kNvgFm0UhN&_nc_oc=AdmsE5kE1mE88uQclHfq6Qi1xUK6Eyy1pbUuYymuorSA0TEuEGmSnSR5Z2U-hn978SQ&_nc_zt=24&_nc_ht=scontent-lax3-1.xx&_nc_gid=krzFu2-tQZnbEk_R-dGxCg&oh=00_AYHw-cXTDixtF2BggBiJWHenKnkAX2q1tJKlfW-P0DHjPQ&oe=67EA7A32", + "profileId": "pfbid02uAes6MvkX2RTrJwGFdRmBFsXbMNhSkqQhM2HtsJAE2Ap8AJwZwTJagaQkA6satdfl", + "profileName": "Chapa Gutiérrez", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=4249013172001951", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzQyNDkwMTMxNzIwMDE5NTE=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV80MjQ5MDEzMTcyMDAxOTUx", + "date": "2025-03-26T20:06:28.000Z", + "text": "Que buena onda gobernador", + "profileUrl": "https://www.facebook.com/people/San-Juana-Castorena/pfbid026cDSUzbYizMa7NUpoyMiJjbHqk9Vk9Nu7GuZSuuAGvL5Z5jq6UEDZeUgmUkvkRycl/", + "profilePicture": "https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-1/328999822_705924097863825_6634907209298667333_n.jpg?stp=c0.0.632.632a_cp0_dst-jpg_s32x32_tt6&_nc_cat=108&ccb=1-7&_nc_sid=e99d92&_nc_ohc=4lz552AVyMgQ7kNvgGAjsDO&_nc_oc=AdnlouwuA9Ges_olzlGgJm8JhOPEdjMy1RDGQ03NKWiQ027qKo6gjLl06hMUyHh6BSk&_nc_zt=24&_nc_ht=scontent-lax3-1.xx&_nc_gid=krzFu2-tQZnbEk_R-dGxCg&oh=00_AYHoEPRu2xFtpdLV1DocfbXj2Gnls_Hx4nPh1IT-I5PCWA&oe=67EA6E9E", + "profileId": "pfbid026cDSUzbYizMa7NUpoyMiJjbHqk9Vk9Nu7GuZSuuAGvL5Z5jq6UEDZeUgmUkvkRycl", + "profileName": "San Juana Castorena", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1387208452270457", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzEzODcyMDg0NTIyNzA0NTc=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xMzg3MjA4NDUyMjcwNDU3", + "date": "2025-03-26T18:15:20.000Z", + "text": "Excelente al pendiente dlb", + "profileUrl": "https://www.facebook.com/people/Herlinda-Gallegos/pfbid0298zcQcbkHsePHbp588etC9UCP9kDvFoGtfJj4ChMgFHxk7Ta5GvGmyZAjwcZj4uLl/", + "profilePicture": "https://scontent.frec4-1.fna.fbcdn.net/v/t1.30497-1/453178253_471506465671661_2781666950760530985_n.png?stp=cp0_dst-png_s32x32&_nc_cat=1&ccb=1-7&_nc_sid=136b72&_nc_ohc=N52xZ5eAv9QQ7kNvgEQ0Trp&_nc_zt=24&_nc_ht=scontent.frec4-1.fna&oh=00_AYFleLJI4wDkGz3E6R4pVvOqUYhLfg-CzjBGu3-M4C0jGg&oe=680C2EBA", + "profileId": "pfbid0298zcQcbkHsePHbp588etC9UCP9kDvFoGtfJj4ChMgFHxk7Ta5GvGmyZAjwcZj4uLl", + "profileName": "Herlinda Gallegos", + "likesCount": "1", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=622533950769255", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzYyMjUzMzk1MDc2OTI1NQ==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV82MjI1MzM5NTA3NjkyNTU=", + "date": "2025-03-26T18:36:23.000Z", + "text": "Deja q llueva primero", + "profileUrl": "https://www.facebook.com/sergior.sanchezmartinez", + "profilePicture": "https://scontent.frec4-1.fna.fbcdn.net/v/t39.30808-1/484899753_9980053328705383_3669713722134285368_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=105&ccb=1-7&_nc_sid=e99d92&_nc_ohc=Gsq_rCn2Ui0Q7kNvgFuWTEA&_nc_zt=24&_nc_ht=scontent.frec4-1.fna&_nc_gid=_KAt-y0lKIVI7z0Kfp6jbg&oh=00_AYElArGvw3yRSbIoIg8TFzbXtYlTHezAM0wloARnc3IFSA&oe=67EA90D2", + "profileId": "pfbid0UK5gybxwK3U2iVd5wAvEsFxzrdgP8SuU79ycUJMjnzPNkJ44g5AjfjX1kEd8qDMAl", + "profileName": "Sergio R Sanchez Martinez", + "likesCount": "0", + "commentsCount": 1, + "comments": [], + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=601560802843414", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzYwMTU2MDgwMjg0MzQxNA==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV82MDE1NjA4MDI4NDM0MTQ=", + "date": "2025-03-26T18:32:27.000Z", + "text": "Buenos días gober🙏", + "profilePicture": "https://scontent.frec4-1.fna.fbcdn.net/v/t39.30808-1/481786503_10235037305212682_6696278376243802913_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=111&ccb=1-7&_nc_sid=1d2534&_nc_ohc=2obveuAlaOMQ7kNvgFaGhZn&_nc_zt=24&_nc_ht=scontent.frec4-1.fna&_nc_gid=_KAt-y0lKIVI7z0Kfp6jbg&oh=00_AYGM1wjWbwHudLFMgIrygFRvDv7moPjJ06fu-7UCPB0FVA&oe=67EA7BE6", + "profileId": "1206711275", + "profileName": "Carmen Mancilla Suarez", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=880721770794549", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1Xzg4MDcyMTc3MDc5NDU0OQ==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV84ODA3MjE3NzA3OTQ1NDk=", + "date": "2025-03-26T20:24:23.000Z", + "attachments": [ + { + "__typename": "Sticker", + "frame_count": 1, + "frame_rate": 83, + "frames_per_column": 1, + "frames_per_row": 1, + "label": "Avatar is grinning with their teeth showing and holding one hand up in a wave.", + "pack": null, + "sprite_image": null, + "image": { + "uri": "https://scontent.frec4-1.fna.fbcdn.net/v/t39.1997-6/480140274_1355276065645083_2927643193108639059_n.webp?_nc_cat=109&ccb=1-7&_nc_sid=72b077&_nc_ohc=lzxyQ7r_zXsQ7kNvgHbuipH&_nc_zt=26&_nc_ht=scontent.frec4-1.fna&_nc_gid=_KAt-y0lKIVI7z0Kfp6jbg&oh=00_AYHA_-iu57_B-39mKF5czCAmAdfEi8eFBmLNiLM-u604EQ&oe=67EA9B9C", + "width": 120, + "height": 120 + }, + "id": "1175243773990640" + } + ], + "profileUrl": "https://www.facebook.com/macrina.castilloloredo", + "profilePicture": "https://scontent.frec4-1.fna.fbcdn.net/v/t39.30808-1/485278108_1127408452468433_4959254512858814837_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=108&ccb=1-7&_nc_sid=1d2534&_nc_ohc=WKKluYWPG4oQ7kNvgG7umw-&_nc_zt=24&_nc_ht=scontent.frec4-1.fna&_nc_gid=_KAt-y0lKIVI7z0Kfp6jbg&oh=00_AYFvvw0LagB8d23vneWnnczgHF8nrVZWxHiCJ9hn2vlGvg&oe=67EA9919", + "profileId": "100055978666720", + "profileName": "Macri Castillo", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1022679723052136", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzEwMjI2Nzk3MjMwNTIxMzY=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xMDIyNjc5NzIzMDUyMTM2", + "date": "2025-03-26T18:51:26.000Z", + "text": "Gracias A Dios!!", + "profileUrl": "https://www.facebook.com/juanita.gonzalez.18062533", + "profilePicture": "https://scontent.frec4-1.fna.fbcdn.net/v/t39.30808-1/473279141_3959862627593251_3625601290447960060_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=102&ccb=1-7&_nc_sid=e99d92&_nc_ohc=BjUda77I4PoQ7kNvgGiUKPc&_nc_zt=24&_nc_ht=scontent.frec4-1.fna&_nc_gid=_KAt-y0lKIVI7z0Kfp6jbg&oh=00_AYGODGYY-_4dxUhQQ4crGYPKg-PZaS7FCY72-pv9OXcvJA&oe=67EA96A3", + "profileId": "pfbid0wbD3feoxbruoesDNv46KNarrBh6L6eUxPF1Y2SUAPwA6hUSq3o7krabziC66t55el", + "profileName": "Juanita Gonzalez", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1216034016606127", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzEyMTYwMzQwMTY2MDYxMjc=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xMjE2MDM0MDE2NjA2MTI3", + "date": "2025-03-26T18:32:40.000Z", + "text": "Buen día. Gracias a Dios por la bendita lluvia.", + "profileUrl": "https://www.facebook.com/people/Lupita-Guel/pfbid02amnCUVvRGtPznPDtRmVhLENFXnNvdujDjDz4KDjnh5UXsCQ5Dx3F8CFc4DdmC9Gsl/", + "profilePicture": "https://scontent.frec4-1.fna.fbcdn.net/v/t39.30808-1/352790275_103058089503691_4807077160498405809_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=108&ccb=1-7&_nc_sid=e99d92&_nc_ohc=xg-gTNGYwCwQ7kNvgE186yK&_nc_zt=24&_nc_ht=scontent.frec4-1.fna&_nc_gid=_KAt-y0lKIVI7z0Kfp6jbg&oh=00_AYE3fI6QG0rsViO-Xup9gCiKYZ_lrLcG9-eTHaD-R3AJPg&oe=67EA8CA2", + "profileId": "pfbid02amnCUVvRGtPznPDtRmVhLENFXnNvdujDjDz4KDjnh5UXsCQ5Dx3F8CFc4DdmC9Gsl", + "profileName": "Lupita Guel", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=1782952458949123", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzE3ODI5NTI0NTg5NDkxMjM=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV8xNzgyOTUyNDU4OTQ5MTIz", + "date": "2025-03-26T19:30:08.000Z", + "text": "Saludos señor gobernador Dios lo bendiga siempre", + "profileUrl": "https://www.facebook.com/Lili.RT.2234", + "profilePicture": "https://scontent.frec4-1.fna.fbcdn.net/v/t39.30808-1/468567699_122129800184446061_1705964903930055585_n.jpg?stp=c0.0.1010.1010a_cp0_dst-jpg_s32x32_tt6&_nc_cat=107&ccb=1-7&_nc_sid=111fe6&_nc_ohc=EcFrg2d6GnYQ7kNvgGjChQr&_nc_zt=24&_nc_ht=scontent.frec4-1.fna&_nc_gid=_KAt-y0lKIVI7z0Kfp6jbg&oh=00_AYFVJloftz-Eta8f7iiOdOd8X4vWX6GubLGAT1At0lVcWg&oe=67EA6A53", + "profileId": "61563381855685", + "profileName": "Lili RT", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=963514925898261", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1Xzk2MzUxNDkyNTg5ODI2MQ==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV85NjM1MTQ5MjU4OTgyNjE=", + "date": "2025-03-26T18:46:46.000Z", + "text": "Saludoa señoron gobernador!!", + "profileUrl": "https://www.facebook.com/people/Jonathan-Vera/pfbid0BrNEVt59WBDhe9gPkG36iRdiakunriUvEPRKa6aKTyBNis4n24R9Pwc13Yhi9qAGl/", + "profilePicture": "https://scontent.frec4-1.fna.fbcdn.net/v/t39.30808-1/470131307_1475219459841016_4691830007427513807_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=104&ccb=1-7&_nc_sid=e99d92&_nc_ohc=2o2RbX2zaVYQ7kNvgECbOqC&_nc_zt=24&_nc_ht=scontent.frec4-1.fna&_nc_gid=_KAt-y0lKIVI7z0Kfp6jbg&oh=00_AYE3jVCgolCdXr0mZat5_oxe32e5_MaKUbTr5xusIaFFAg&oe=67EA9714", + "profileId": "pfbid0BrNEVt59WBDhe9gPkG36iRdiakunriUvEPRKa6aKTyBNis4n24R9Pwc13Yhi9qAGl", + "profileName": "Jonathan Vera", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=4005635693092070", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzQwMDU2MzU2OTMwOTIwNzA=", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV80MDA1NjM1NjkzMDkyMDcw", + "date": "2025-03-26T18:13:46.000Z", + "text": "Gracias Samuel 🙏🏼", + "profilePicture": "https://scontent.frec4-1.fna.fbcdn.net/v/t39.30808-1/470681833_4053832721568467_8893151821826638897_n.jpg?stp=cp0_dst-jpg_s32x32_tt6&_nc_cat=101&ccb=1-7&_nc_sid=e99d92&_nc_ohc=ur5tH8cCEDAQ7kNvgGFmaMR&_nc_zt=24&_nc_ht=scontent.frec4-1.fna&_nc_gid=_KAt-y0lKIVI7z0Kfp6jbg&oh=00_AYHsYTng7YddIwhts_Tn7r4Qx82db47XznW-fmsuHNjffg&oe=67EA7393", + "profileId": "pfbid02MUYB7Fz18WhT48k1FVLXxVcULQTD6LJGazuCYGLUeQvJn8SpYiPajHogiLKx8jhil", + "profileName": "Zekiel Rodriguez", + "likesCount": "1", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008", + "commentUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/?comment_id=597645386639925", + "id": "Y29tbWVudDoxMjIxNzA3NTc5MzI2NjU1XzU5NzY0NTM4NjYzOTkyNQ==", + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NV81OTc2NDUzODY2Mzk5MjU=", + "date": "2025-03-26T18:40:27.000Z", + "text": "Gracias adios", + "profileUrl": "https://www.facebook.com/genoveva.lopez.505523", + "profilePicture": "https://scontent.frec4-1.fna.fbcdn.net/v/t39.30808-1/433468313_957472306026221_9196766637026973663_n.jpg?stp=c0.125.720.720a_cp0_dst-jpg_s32x32_tt6&_nc_cat=102&ccb=1-7&_nc_sid=1d2534&_nc_ohc=lcBzL887lr4Q7kNvgEPY9LN&_nc_zt=24&_nc_ht=scontent.frec4-1.fna&_nc_gid=_KAt-y0lKIVI7z0Kfp6jbg&oh=00_AYHpm6FHJ8VsZKgoyqYrgst8JUoYoeKEwqD2n5H2Vy2ilw&oe=67EA91D9", + "profileId": "100052903806281", + "profileName": "Genoveva Lopez", + "likesCount": "0", + "threadingDepth": 0, + "facebookId": "1221707579326655", + "postTitle": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para...", + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008" + } +] \ No newline at end of file diff --git a/backend/app/testing/data/facebook/post_samples.json b/backend/app/testing/data/facebook/post_samples.json new file mode 100644 index 0000000000..31bf2adc79 --- /dev/null +++ b/backend/app/testing/data/facebook/post_samples.json @@ -0,0 +1,602 @@ +[ + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA", + "postId": "1221921445971935", + "pageName": "SAMUELGARCIASEPULVEDA", + "url": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/posts/pfbid02zw49EvvfBgyyTrxVZqzwDHNvudEc6dCzYSf3xRsfENcEbJBPhwnemE5rUQjSPcEQl", + "time": "2025-03-27T01:40:11.000Z", + "timestamp": 1743039611, + "user": { + "id": "100044622720400", + "name": "Samuel García", + "profileUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA", + "profilePic": "https://scontent.fpos3-1.fna.fbcdn.net/v/t39.30808-1/462468035_1103295834501164_3633703158841014008_n.jpg?stp=cp0_dst-jpg_s40x40_tt6&_nc_cat=1&ccb=1-7&_nc_sid=2d3e12&_nc_ohc=Hv51ddbfe1UQ7kNvgFqKiYt&_nc_oc=AdmCYVr8R4mvZR85naoaQFE96o97nllo0loimUSLWU2QqQNg-Pmz90NqD6fqS83e2IY&_nc_zt=24&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYEmimH3S8gkq0qFHNFRsbFiWPyiJnWYFqP_PcI_h-Ba9w&oe=67EA71C3" + }, + "text": "No quiero que termine el día sin felicitar al Consejo Nuevo León en su décimo aniversario. Son 10 años de hacer equipo con los gobiernos de Nuevo León para garantizar que nuestro estado sea siendo el más vanguardista de México. \n\nDesde el Gobierno del Nuevo Nuevo León les aseguro que los próximos tres años vamos con más coordinación consolidarnos como el mejor gobierno en la historia de nuestro estado. Cuenten con nosotros, porque nosotros contamos con ustedes Consejo Nuevo León. ¡Ánimo! 🦁👊🏻", + "textReferences": [ + { + "id": "100064272986191", + "url": "https://www.facebook.com/ConsejoNL", + "profile_url": "https://www.facebook.com/ConsejoNL", + "short_name": "Consejo Nuevo León", + "work_info": null, + "work_foreign_entity_info": null, + "mobileUrl": "https://m.facebook.com/ConsejoNL" + } + ], + "likes": 76, + "comments": 10, + "shares": 128, + "topReactionsCount": 5, + "media": [ + { + "thumbnail": "https://scontent.fpos3-1.fna.fbcdn.net/v/t39.30808-6/486396102_1221921149305298_5733171674520172134_n.jpg?stp=dst-jpg_p180x540_tt6&_nc_cat=105&ccb=1-7&_nc_sid=833d8c&_nc_ohc=InX-NH5VUCcQ7kNvgHpRefe&_nc_oc=Adl3YL7UIDl0rySwRyFLXFzKkKAf3zIpuvn-aC1Rlm-CidU7o_8Q-pQH54ggfHLUXyg&_nc_zt=23&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYFC7ywdgkL0jM0j8-JgWj5SxgL-kokMSsuNJWE1VfsUjA&oe=67EA8D88", + "__typename": "Photo", + "photo_image": { + "uri": "https://scontent.fpos3-1.fna.fbcdn.net/v/t39.30808-6/486396102_1221921149305298_5733171674520172134_n.jpg?stp=dst-jpg_p180x540_tt6&_nc_cat=105&ccb=1-7&_nc_sid=833d8c&_nc_ohc=InX-NH5VUCcQ7kNvgHpRefe&_nc_oc=Adl3YL7UIDl0rySwRyFLXFzKkKAf3zIpuvn-aC1Rlm-CidU7o_8Q-pQH54ggfHLUXyg&_nc_zt=23&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYFC7ywdgkL0jM0j8-JgWj5SxgL-kokMSsuNJWE1VfsUjA&oe=67EA8D88", + "height": 540, + "width": 810 + }, + "__isMedia": "Photo", + "accent_color": "FF8C4C04", + "photo_product_tags": [], + "url": "https://www.facebook.com/photo/?fbid=1221921145971965&set=a.540627630767990", + "id": "1221921145971965", + "ocrText": "May be an image of 7 people and text" + } + ], + "feedbackId": "ZmVlZGJhY2s6MTIyMTkyMTQ0NTk3MTkzNQ==", + "topLevelUrl": "https://www.facebook.com/100044622720400/posts/1221921445971935", + "facebookId": "100044622720400", + "pageAdLibrary": { + "is_business_page_active": false, + "id": "384214801745089" + }, + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA", + "postId": "1221782112652535", + "pageName": "SAMUELGARCIASEPULVEDA", + "url": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/634398716179575/", + "time": "2025-03-26T20:49:23.000Z", + "timestamp": 1743022163, + "user": { + "id": "100044622720400", + "name": "Samuel García", + "profileUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA", + "profilePic": "https://scontent.fpos3-1.fna.fbcdn.net/v/t39.30808-1/462468035_1103295834501164_3633703158841014008_n.jpg?stp=cp0_dst-jpg_s40x40_tt6&_nc_cat=1&ccb=1-7&_nc_sid=2d3e12&_nc_ohc=Hv51ddbfe1UQ7kNvgFqKiYt&_nc_oc=AdmCYVr8R4mvZR85naoaQFE96o97nllo0loimUSLWU2QqQNg-Pmz90NqD6fqS83e2IY&_nc_zt=24&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYEmimH3S8gkq0qFHNFRsbFiWPyiJnWYFqP_PcI_h-Ba9w&oe=67EA71C3" + }, + "text": "ATENCIÓN \n\nFuerza Civil acaba de ubicar y detener al agresor del tlacuache del video que circuló en redes hace días. Estaremos muy atentos para ver que le caiga todo el peso de la ley y la pena máxima. CERO TOLERANCIA contra estos criminales y el maltrato animal. Mariana Rodríguez Glen V. Zambrano Arturo Islas Allende", + "textReferences": [ + { + "id": "100044397700594", + "url": "https://www.facebook.com/marianardzcantu1", + "profile_url": "https://www.facebook.com/marianardzcantu1", + "short_name": "Mariana Rodríguez", + "work_info": null, + "work_foreign_entity_info": null, + "mobileUrl": "https://m.facebook.com/marianardzcantu1" + }, + { + "id": "100047085616511", + "url": "https://www.facebook.com/glenvzambrano", + "profile_url": "https://www.facebook.com/glenvzambrano", + "short_name": "Glen V. Zambrano", + "work_info": null, + "work_foreign_entity_info": null, + "mobileUrl": "https://m.facebook.com/glenvzambrano" + }, + { + "id": "100044468344971", + "url": "https://www.facebook.com/arturoislasallende", + "profile_url": "https://www.facebook.com/arturoislasallende", + "short_name": "Arturo Islas Allende", + "work_info": null, + "work_foreign_entity_info": null, + "mobileUrl": "https://m.facebook.com/arturoislasallende" + } + ], + "likes": 9728, + "comments": 3572, + "shares": 1385, + "topReactionsCount": 7, + "isVideo": true, + "viewsCount": 148908, + "media": [ + { + "thumbnail": "https://scontent.fpos3-1.fna.fbcdn.net/v/t15.5256-10/487057924_1315032386222842_20529222440590398_n.jpg?stp=dst-jpg_s960x960_tt6&_nc_cat=1&ccb=1-7&_nc_sid=7965db&_nc_ohc=rDmOJgmHla8Q7kNvgGkp40A&_nc_oc=Adm-j9NLcJvbQ0w_apLPlqmBEODC733PHeaMI3U9pYSz9VlBAd20v4cbqX-gg9LBNjQ&_nc_zt=23&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYENR7zdwwLrq0lHhwqmq3HgbDGEl3THxqiFGMK4U0zivw&oe=67EA9736", + "__typename": "Video", + "thumbnailImage": { + "uri": "https://scontent.fpos3-1.fna.fbcdn.net/v/t15.5256-10/487057924_1315032386222842_20529222440590398_n.jpg?stp=dst-jpg_s960x960_tt6&_nc_cat=1&ccb=1-7&_nc_sid=7965db&_nc_ohc=rDmOJgmHla8Q7kNvgGkp40A&_nc_oc=Adm-j9NLcJvbQ0w_apLPlqmBEODC733PHeaMI3U9pYSz9VlBAd20v4cbqX-gg9LBNjQ&_nc_zt=23&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYENR7zdwwLrq0lHhwqmq3HgbDGEl3THxqiFGMK4U0zivw&oe=67EA9736" + }, + "id": "634398716179575", + "is_clipping_enabled": false, + "live_rewind_enabled": false, + "owner": { + "__typename": "User", + "id": "100044622720400", + "__isVideoOwner": "User", + "has_professional_features_for_watch": true + }, + "playable_duration_in_ms": 104071, + "is_huddle": false, + "url": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/634398716179575/", + "if_viewer_can_use_latency_menu": null, + "if_viewer_can_use_latency_menu_toggle": null, + "captions_url": "https://scontent.fpos3-1.fna.fbcdn.net/v/t39.2093-6/486782807_634413496178097_1284340799086231621_n.srt?_nc_cat=104&ccb=1-7&_nc_sid=c211c2&_nc_ohc=nWXghYbasoQQ7kNvgG5sKj1&_nc_oc=AdlzL-HdHdr1_sh1cbLzKTXJuUu5nzT2lvyH4ElowvwuqS0GIFH3nZTiBPfAflMZGzU&_nc_zt=14&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYHgrnEk7huhyZTPR0OOIw-ULwOMM0LFrpGx5R-5_3_Law&oe=67EAA132", + "video_available_captions_locales": [ + { + "localized_creation_method": "خودکار ترجمہ کردہ", + "captions_url": "https://scontent.fpos3-1.fna.fbcdn.net/v/t39.2093-6/486491230_634427492843364_3079811441245870784_n.srt?_nc_cat=102&ccb=1-7&_nc_sid=c211c2&_nc_ohc=eWtiFJBrDG0Q7kNvgEmh5Xu&_nc_oc=AdmCJd0J8ZDNriv4MgDPNsd2YyJ1eWrcAPSuGupaGEwrzYUPUNVTQx5VWY2L2hLlKfw&_nc_zt=14&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYEBkWBUYcWMS21Bw3r7pYaOhKLxT5JraG08nAWYcl_I0Q&oe=67EA7DB3", + "locale": "ur_PK", + "localized_language": "اردو", + "localized_country": null + }, + { + "localized_creation_method": "Terjemahan otomatis", + "captions_url": "https://scontent.fpos3-1.fna.fbcdn.net/v/t39.2093-6/486520237_634413499511430_4552026513773708159_n.srt?_nc_cat=103&ccb=1-7&_nc_sid=c211c2&_nc_ohc=nJITbG4e1TYQ7kNvgGyMZle&_nc_oc=AdnMEBFl_P0oxSyDEZozYWwVXsiD_7KM4oHD0yfVf8fcxKutP828iNhSRCtQF5Oewgk&_nc_zt=14&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYHV_Y2ryca8hDiybUh12T9OzPzjJ-K52Dk-1SWpNhWpNw&oe=67EA72F4", + "locale": "id_ID", + "localized_language": "Bahasa Indonesia", + "localized_country": null + }, + { + "localized_creation_method": "Auto-translated", + "captions_url": "https://scontent.fpos3-1.fna.fbcdn.net/v/t39.2093-6/486782807_634413496178097_1284340799086231621_n.srt?_nc_cat=104&ccb=1-7&_nc_sid=c211c2&_nc_ohc=nWXghYbasoQQ7kNvgG5sKj1&_nc_oc=AdlzL-HdHdr1_sh1cbLzKTXJuUu5nzT2lvyH4ElowvwuqS0GIFH3nZTiBPfAflMZGzU&_nc_zt=14&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYHgrnEk7huhyZTPR0OOIw-ULwOMM0LFrpGx5R-5_3_Law&oe=67EAA132", + "locale": "en_US", + "localized_language": "English", + "localized_country": null + }, + { + "localized_creation_method": "Dịch tự động", + "captions_url": "https://scontent.fpos3-1.fna.fbcdn.net/v/t39.2093-6/486654950_634413492844764_6217430134219902338_n.srt?_nc_cat=111&ccb=1-7&_nc_sid=c211c2&_nc_ohc=SYssp4_EMHUQ7kNvgGEe9_2&_nc_oc=AdnI4msElvq_m6wf5p9ulmMzZymvj5OSRthtj2j2ZQ0HaUhEY90g_zyyhtAk0n47seI&_nc_zt=14&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYEiTQV1f-SdJMuykXKrdRJp2as60imJTbxzvBBvxDRYZw&oe=67EA93C2", + "locale": "vi_VN", + "localized_language": "Tiếng Việt", + "localized_country": null + }, + { + "localized_creation_method": "Traduzido automaticamente", + "captions_url": "https://scontent.fpos3-1.fna.fbcdn.net/v/t39.2093-6/486683493_634413502844763_6816822772875874318_n.srt?_nc_cat=109&ccb=1-7&_nc_sid=c211c2&_nc_ohc=V5Y0ObcHJqoQ7kNvgGBxcfC&_nc_oc=AdnY7Bodwxigm4bTwmuXENwT_frX5b5cKq4NbRt4wh5ZEGy-hC1P2KfDAXBBCdFl47U&_nc_zt=14&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYHuO1XcLiUwc6BeErXGTuPgueVfu0xQIHLvzZibsroNEw&oe=67EA810F", + "locale": "pt_PT", + "localized_language": "Português", + "localized_country": null + }, + { + "localized_creation_method": "แปลอัตโนมัติ", + "captions_url": "https://scontent.fpos3-1.fna.fbcdn.net/v/t39.2093-6/486951280_634413506178096_2359262222767672099_n.srt?_nc_cat=111&ccb=1-7&_nc_sid=c211c2&_nc_ohc=m-wNZn1uKjIQ7kNvgEuqxDx&_nc_oc=AdlyzlY3ndqsSfbRkMMTHW8WHZsemOK5mjMgi2VLQt2phiDH4mmBbLA6N8VGHyJNxc4&_nc_zt=14&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYEv3zjcMt19kK9vr0HrCC9oErPH3h1EipWCHp_FrlHEQA&oe=67EA739B", + "locale": "th_TH", + "localized_language": "ภาษาไทย", + "localized_country": null + }, + { + "localized_creation_method": "Automático", + "captions_url": "https://scontent.fpos3-1.fna.fbcdn.net/v/t39.2093-6/486565147_634400289512751_6550860451294423586_n.srt?_nc_cat=101&ccb=1-7&_nc_sid=c211c2&_nc_ohc=cmYEuSmdHFkQ7kNvgEtheDC&_nc_oc=Adl4FTJjhf_Hsd3PSsdi7LYqDSk7ORfCcuvpIyGTnK7uwAZMao79TLH8YBwVDmsACJ8&_nc_zt=14&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYGxqp8EpmQcI8pXJyaYS2iBrcD4cObKZ8SKs8nJ4LMsbg&oe=67EA8BD2", + "locale": "es_CL", + "localized_language": "Español", + "localized_country": null + } + ], + "if_viewer_can_see_community_moderation_tools": null, + "if_viewer_can_use_live_rewind": null, + "if_viewer_can_use_clipping": null, + "if_viewer_can_see_costreaming_tools": null, + "video_player_scrubber_base_content_renderer": null, + "video_player_scrubber_preview_renderer": { + "__typename": "XFBVideoPlayerScrubberDefaultPreviewRenderer", + "video": { + "scrubber_preview_thumbnail_information": { + "sprite_uris": [ + "https://scontent.fpos3-1.fna.fbcdn.net/v/t15.6481-10/486697638_2407404806266465_2289976237029878754_n.jpg?_nc_cat=102&ccb=1-7&_nc_sid=62976d&_nc_ohc=dYNijTPgGOoQ7kNvgGgHYyY&_nc_oc=AdnLLdGwQYJIk9HQdZ0bhUms3wImL-HLhvq_D9zbZNezfHlKtg1eeBZ0E7JSKPXB_8s&_nc_zt=23&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYG0iCX37ssKLgpJd-NpxY3Qo0Cz8XC-RiAn1Cs0ep3YcQ&oe=67EA7D3D", + "https://scontent.fpos3-1.fna.fbcdn.net/v/t15.6481-10/486759592_975954877938133_5420302362458194941_n.jpg?_nc_cat=107&ccb=1-7&_nc_sid=62976d&_nc_ohc=l5FzzshuDBoQ7kNvgEkFyTl&_nc_oc=Adkuy5mk9HfcqVIDLvKdv28GvtofHE1XfWjuCvflzRAoR3Xu6ZX878LL-8R5Whtw72E&_nc_zt=23&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYHITe4W7GUhFMP08JJX0yWaIN30SGi1il_BAeBdoGmY3Q&oe=67EA777B" + ], + "thumbnail_width": 100, + "thumbnail_height": 176, + "has_preview_thumbnails": true, + "num_images_per_row": 10, + "max_number_of_images_per_sprite": 100, + "time_interval_between_image": 1 + }, + "id": "634398716179575" + }, + "__module_operation_VideoPlayerScrubberPreview_video": { + "__dr": "VideoPlayerScrubberDefaultPreview_video$normalization.graphql" + }, + "__module_component_VideoPlayerScrubberPreview_video": { + "__dr": "VideoPlayerScrubberDefaultPreview.react" + } + }, + "recipient_group": null, + "music_attachment_metadata": null, + "video_container_type": "CONTAINED_POST_ATTACHMENT", + "breakingStatus": false, + "videoId": "634398716179575", + "isPremiere": false, + "liveViewerCount": 0, + "rehearsalInfo": null, + "is_gaming_video": false, + "is_live_audio_room_v2_broadcast": false, + "publish_time": 1743021984, + "live_speaker_count_indicator": null, + "can_viewer_share": false, + "end_cards_channel_info": { + "video_chaining_caller": "CHANNEL_VIEW_FROM_END_CARD", + "video_channel_entry_point": "PROFILE", + "video": { + "id": "634398716179575", + "can_viewer_share": false, + "creation_story": { + "shareable": null, + "id": "UzpfSTEwMDA0NDYyMjcyMDQwMDpWSzo2MzQzOTg3MTYxNzk1NzU=" + } + }, + "__module_operation_useVideoPlayerWatchEndScreenWithActions_video": { + "__dr": "VideoPlayerWatchInlineEndScreen_info$normalization.graphql" + }, + "__module_component_useVideoPlayerWatchEndScreenWithActions_video": { + "__dr": "VideoPlayerWatchInlineEndScreen.react" + } + }, + "is_soundbites_video": false, + "is_looping": false, + "info": { + "video_chaining_caller": "CHANNEL_VIEW_FROM_END_CARD", + "video_channel_entry_point": "PROFILE", + "video_id": "634398716179575", + "__module_operation_useVideoPlayerWatchPauseScreenWithActions_video": { + "__dr": "VideoPlayerWatchInlinePauseScreen_info$normalization.graphql" + }, + "__module_component_useVideoPlayerWatchPauseScreenWithActions_video": { + "__dr": "VideoPlayerWatchInlinePauseScreen.react" + } + }, + "animated_image_caption": null, + "width": 720, + "height": 1280, + "broadcaster_origin": null, + "broadcast_id": null, + "broadcast_status": null, + "is_live_streaming": false, + "is_live_trace_enabled": false, + "is_video_broadcast": false, + "is_podcast_video": false, + "loop_count": 0, + "is_spherical": false, + "is_spherical_enabled": true, + "unsupported_browser_message": null, + "can_play_undiscoverable": true, + "pmv_metadata": null, + "latency_sensitive_config": null, + "live_playback_instrumentation_configs": null, + "is_ncsr": false, + "permalink_url": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/634398716179575/", + "dash_prefetch_experimental": [ + "660804756540714v", + "1149398303319325a" + ], + "video_status_type": "OK", + "can_use_oz": true, + "dash_manifest": "\nhttps://video.fpos3-1.fna.fbcdn.net/o1/v/t2/f2/m69/AQMIg4DspZCOH0f7E56AvFGmheoqdp-T3ubps4XlfLLRmTe_aR6kCFBG0kcMMseaABcREZZ424s9w1n9QVWjmXgC.mp4?strext=1&_nc_cat=1&_nc_oc=Adl3WUqNkZB6kN5hPCybrEAAwcetn_8CkQJue-Utd8lMvwj4bsRFX223WkZnk_jHydw&_nc_sid=9ca052&_nc_ht=video.fpos3-1.fna.fbcdn.net&_nc_ohc=e8148oVByJoQ7kNvgFJBiBu&efg=eyJ2ZW5jb2RlX3RhZyI6ImRhc2hfcjJhdjEtcjFnZW4ydnA5X3E4MCIsInZpZGVvX2lkIjo2MzQzOTg3MTYxNzk1NzUsImNsaWVudF9uYW1lIjoidW5rbm93biIsIm9pbF91cmxnZW5fYXBwX2lkIjowLCJ4cHZfYXNzZXRfaWQiOjEwMDY0OTA2MzgyNDgzNDIsInZpX3VzZWNhc2VfaWQiOjEwMTIyLCJkdXJhdGlvbl9zIjoxMDQsInVybGdlbl9zb3VyY2UiOiJ3d3cifQ%3D%3D&ccb=17-1&_nc_zt=28&oh=00_AYGOVndGPeGcAfwvFfQEXhJzrYQ3EBRJU_vyt06oHxA4_w&oe=67EA8EBEhttps://video.fpos3-1.fna.fbcdn.net/o1/v/t2/f2/m69/AQMB97cfEtRo5nm2j23LEejG0FX0_j9Y_h5nh5oLptZr7_KG8q4PHLXKzAEJePNgZhjoyYNfXuDHEvCXezGwCsGg.mp4?strext=1&_nc_cat=1&_nc_oc=AdnRQ7KwwqVrKMWVn6uCNrUuksGg2kjzy1VobKEDUxTkYEshEu-XzSYOjjSiVfMx0RE&_nc_sid=9ca052&_nc_ht=video.fpos3-1.fna.fbcdn.net&_nc_ohc=dgnhTWNLxhMQ7kNvgHPzkQq&efg=eyJ2ZW5jb2RlX3RhZyI6ImRhc2hfcjJhdjEtcjFnZW4ydnA5X3E5MCIsInZpZGVvX2lkIjo2MzQzOTg3MTYxNzk1NzUsImNsaWVudF9uYW1lIjoidW5rbm93biIsIm9pbF91cmxnZW5fYXBwX2lkIjowLCJ4cHZfYXNzZXRfaWQiOjEwMDY0OTA2MzgyNDgzNDIsInZpX3VzZWNhc2VfaWQiOjEwMTIyLCJkdXJhdGlvbl9zIjoxMDQsInVybGdlbl9zb3VyY2UiOiJ3d3cifQ%3D%3D&ccb=17-1&_nc_zt=28&oh=00_AYFPjVHDJMObSvD2TKJ7IbQ3TW1DkVldgJAcDCrNOjLGew&oe=67EA79DBhttps://video.fpos3-1.fna.fbcdn.net/o1/v/t2/f2/m69/AQM_V7yDPzhrq8wi17hanWne6DW-yqVXY9PD6mdEeMCHulwMtansPAcRNnatsSC7qMS08ssqM0I1VKBa8yA8jnJV.mp4?strext=1&_nc_cat=1&_nc_oc=Admtp4f0jLx8xFfi-LBVQEsKVMQZLde8g7sUPFQ9hrAjV6fqyif8F1hJm6r2K0kW0cY&_nc_sid=9ca052&_nc_ht=video.fpos3-1.fna.fbcdn.net&_nc_ohc=8GFxnGdJZSkQ7kNvgF6d8kl&efg=eyJ2ZW5jb2RlX3RhZyI6ImRhc2hfbG5faGVhYWNfdmJyM19hdWRpbyIsInZpZGVvX2lkIjo2MzQzOTg3MTYxNzk1NzUsImNsaWVudF9uYW1lIjoidW5rbm93biIsIm9pbF91cmxnZW5fYXBwX2lkIjowLCJ4cHZfYXNzZXRfaWQiOjEwMDY0OTA2MzgyNDgzNDIsInZpX3VzZWNhc2VfaWQiOjEwMTIyLCJkdXJhdGlvbl9zIjoxMDQsInVybGdlbl9zb3VyY2UiOiJ3d3cifQ%3D%3D&ccb=17-1&_nc_zt=28&oh=00_AYHkMedHro-MH29Vgp8U2oVnX-APtJmHl6paQ-HSAR5Q_A&oe=67EA96CF\n", + "dash_manifest_url": "https://www.facebook.com/dash_mpd_debug.mpd?v=634398716179575&dummy=.mpd", + "min_quality_preference": null, + "audio_user_preferred_language": "en", + "is_rss_podcast_video": false, + "browser_native_sd_url": "https://video.fpos3-1.fna.fbcdn.net/o1/v/t2/f2/m69/AQM1nuu13M6gqLWz9nbYqIL4hS092kW2iOLtQkRBeEZXgaUMYNvc6kPVL-tuuEv63KWhJleEqKq56R7S7Wx0WAbb.mp4?strext=1&_nc_cat=107&_nc_oc=AdlKUz2hMzFeKjtaU1OAclQ_z88HQ0i7xbfu6Zj0kg-FlmUU5ci5LL92vtEjZ3dEygQ&_nc_sid=8bf8fe&_nc_ht=video.fpos3-1.fna.fbcdn.net&_nc_ohc=ZWYql-xVHlYQ7kNvgEukaAR&efg=eyJ2ZW5jb2RlX3RhZyI6Inhwdl9wcm9ncmVzc2l2ZS5GQUNFQk9PSy4uQzMuMzYwLnN2ZV9zZCIsInhwdl9hc3NldF9pZCI6MTAwNjQ5MDYzODI0ODM0MiwidmlfdXNlY2FzZV9pZCI6MTAxMjIsImR1cmF0aW9uX3MiOjEwNCwidXJsZ2VuX3NvdXJjZSI6Ind3dyJ9&ccb=17-1&_nc_zt=28&oh=00_AYGnDJllza5A1OaO3AffrddvneXn8Mnch7UyFsymWahcrQ&oe=67EA8B33", + "browser_native_hd_url": "https://video.fpos3-1.fna.fbcdn.net/o1/v/t2/f2/m69/AQOoOpG6AjsiBfQ_oVSFLzZQUVbDLma3huwyzLbs5JYgPJephYMR_z9IpoHOPcW16nKqRcxpKYwHeqfnRvF-gzqT.mp4?strext=1&_nc_cat=102&_nc_oc=AdkdyjHpTdSar7brjYp17f2z0vUPY7CjcpZErDlOP3YC-ogav92kb-VsuzYciTtQNv8&_nc_sid=5e9851&_nc_ht=video.fpos3-1.fna.fbcdn.net&_nc_ohc=PeV9zKF8xXEQ7kNvgGEjIE3&efg=eyJ2ZW5jb2RlX3RhZyI6Inhwdl9wcm9ncmVzc2l2ZS5GQUNFQk9PSy4uQzMuNzIwLmRhc2hfaDI2NC1iYXNpYy1nZW4yXzcyMHAiLCJ4cHZfYXNzZXRfaWQiOjEwMDY0OTA2MzgyNDgzNDIsInZpX3VzZWNhc2VfaWQiOjEwMTIyLCJkdXJhdGlvbl9zIjoxMDQsInVybGdlbl9zb3VyY2UiOiJ3d3cifQ%3D%3D&ccb=17-1&vs=5e03360d07c3ed83&_nc_vs=HBksFQIYOnBhc3N0aHJvdWdoX2V2ZXJzdG9yZS9HSnZhQVIxZm1aRUh2VjhEQUl1bVRWRERrOUJuYm1kakFBQUYVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dJNTFBeDNVRjZ3UTRQSUZBTWdkV3RMSXJ6Yy1ickZxQUFBRhUCAsgBACgAGAAbAogHdXNlX29pbAExEnByb2dyZXNzaXZlX3JlY2lwZQExFQAAJqyms8-72ckDFQIoAkMzLBdAWgQIMSbpeRgZZGFzaF9oMjY0LWJhc2ljLWdlbjJfNzIwcBEAdQIA&_nc_zt=28&oh=00_AYFEkv7R7Ywjb0EHPGFxQYHHWscU5oqfDKWFb-dcWLNYBQ&oe=67EA9FD8", + "spherical_video_fallback_urls": null, + "is_latency_menu_enabled": false, + "fbls_tier": null, + "is_latency_sensitive_broadcast": false, + "comet_video_player_static_config": "{}", + "comet_video_player_context_sensitive_config": "{}", + "video_player_shaka_performance_logger_init": { + "__typename": "VideoPlayerShakaPerformanceLoggerInit", + "__module_operation_useVideoPlayerShakaPerformanceLoggerRelayImpl_video": { + "__dr": "useVideoPlayerShakaPerformanceLoggerRelayImpl_init$normalization.graphql" + }, + "__module_component_useVideoPlayerShakaPerformanceLoggerRelayImpl_video": { + "__dr": "VideoPlayerShakaPerformanceLogger" + } + }, + "video_player_shaka_performance_logger_should_sample": false, + "video_player_shaka_performance_logger_init2": { + "__typename": "VideoPlayerShakaPerformanceLoggerInit", + "__module_operation_useVideoPlayerShakaPerformanceLoggerBuilder_video": { + "__dr": "useVideoPlayerShakaPerformanceLoggerBuilder_init$normalization.graphql" + }, + "__module_component_useVideoPlayerShakaPerformanceLoggerBuilder_video": { + "__dr": "VideoPlayerShakaPerformanceLoggerBuilder" + }, + "per_session_sampling_rate": null + }, + "autoplay_gating_result": "all_page_organic_allowed", + "viewer_autoplay_setting": "default_autoplay", + "can_autoplay": true, + "drm_info": "{\"video_license_uri_map\":{},\"graph_api_video_license_uri\":null,\"fairplay_cert\":null,\"widevine_cert\":\"CsECCAMSEBcFuRfMEgSGiwYzOi93KowYgrSCkgUijgIwggEKAoIBAQCZ7Vs7Mn2rXiTvw7YqlbWYUgrVvMs3UD4GRbgU2Ha430BRBEGtjOOtsRu4jE5yWl5KngeVKR1YWEAjp+GvDjipEnk5MAhhC28VjIeMfiG\\/+\\/7qd+EBnh5XgeikX0YmPRTmDoBYqGB63OBPrIRXsTeo1nzN6zNwXZg6IftO7L1KEMpHSQykfqpdQ4IY3brxyt4zkvE9b\\/tkQv0x4b9AsMYE0cS6TJUgpL+X7r1gkpr87vVbuvVk4tDnbNfFXHOggrmWEguDWe3OJHBwgmgNb2fG2CxKxfMTRJCnTuw3r0svAQxZ6ChD4lgvC2ufXbD8Xm7fZPvTCLRxG88SUAGcn1oJAgMBAAE6FGxpY2Vuc2Uud2lkZXZpbmUuY29tEoADrjRzFLWoNSl\\/JxOI+3u4y1J30kmCPN3R2jC5MzlRHrPMveoEuUS5J8EhNG79verJ1BORfm7BdqEEOEYKUDvBlSubpOTOD8S\\/wgqYCKqvS\\/zRnB3PzfV0zKwo0bQQQWz53ogEMBy9szTK\\/NDUCXhCOmQuVGE98K\\/PlspKkknYVeQrOnA+8XZ\\/apvTbWv4K+drvwy6T95Z0qvMdv62Qke4XEMfvKUiZrYZ\\/DaXlUP8qcu9u\\/r6DhpV51Wjx7zmVflkb1gquc9wqgi5efhn9joLK3\\/bNixbxOzVVdhbyqnFk8ODyFfUnaq3fkC3hR3f0kmYgI41sljnXXjqwMoW9wRzBMINk+3k6P8cbxfmJD4\\/Paj8FwmHDsRfuoI6Jj8M76H3CTsZCZKDJjM3BQQ6Kb2m+bQ0LMjfVDyxoRgvfF\\/\\/M\\/EEkPrKWyU2C3YBXpxaBquO4C8A0ujVmGEEqsxN1HX9lu6c5OMm8huDxwWFd7OHMs3avGpr7RP7DUnTikXrh6X0\"}", + "p2p_settings": null, + "audio_settings": null, + "captions_settings": null, + "broadcast_low_latency_config": null, + "audio_availability": "AVAILABLE", + "muted_segments": [], + "spherical_video_renderer": null, + "preferred_thumbnail": { + "image": { + "uri": "https://scontent.fpos3-1.fna.fbcdn.net/v/t15.5256-10/487057924_1315032386222842_20529222440590398_n.jpg?stp=dst-jpg_s960x960_tt6&_nc_cat=1&ccb=1-7&_nc_sid=be8305&_nc_ohc=rDmOJgmHla8Q7kNvgGkp40A&_nc_oc=Adm-j9NLcJvbQ0w_apLPlqmBEODC733PHeaMI3U9pYSz9VlBAd20v4cbqX-gg9LBNjQ&_nc_zt=23&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYENR7zdwwLrq0lHhwqmq3HgbDGEl3THxqiFGMK4U0zivw&oe=67EA9736" + }, + "image_preview_payload": "ABgqNUhMezZnB3A4/P0P8qyh5nq3+f8AgNacrN5YVjuA5A788YHqP5VAvnx/MUDL6d/8alWKs2VFMxYDD4yPpjv2FFdJFskQOowGGaKBFb7LsGWPIbg8/kPb19aY27gFueSeO1QRvNdNuJIjU5OOgA5wPU4/xNRzzgKXX6D60rFprqbEQ2Iq+gorEt9UYfLL8w/vDqPr6/zop2INKYjAt4eP4fp6/wCJNY97KHfYn3I/lHv6mr0H3mPojfyrFqhCYopTRQB//9k=", + "id": "1315032382889509" + }, + "video_imf_data": null, + "original_width": 720, + "original_height": 1280, + "original_rotation": "NO_ROTATE", + "if_viewer_can_see_pay_to_access_paywall": null, + "comet_video_player_audio_overlay_renderer": null, + "comet_video_player_audio_background_renderer": null, + "comet_video_player_music_sprout_background_renderer": null, + "clip_fallback_cover": null, + "is_clip": false, + "matcha_related_keywords_links": [ + "" + ], + "is_music_clip": false, + "video_collaborator_page_or_delegate_page": null, + "video_anchor_tag_info": null, + "image": { + "uri": "https://scontent.fpos3-1.fna.fbcdn.net/v/t15.5256-10/487057924_1315032386222842_20529222440590398_n.jpg?stp=dst-jpg_p296x100_tt6&_nc_cat=1&ccb=1-7&_nc_sid=7965db&_nc_ohc=rDmOJgmHla8Q7kNvgGkp40A&_nc_oc=Adm-j9NLcJvbQ0w_apLPlqmBEODC733PHeaMI3U9pYSz9VlBAd20v4cbqX-gg9LBNjQ&_nc_zt=23&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYGI77J_JfIPQYp2qyH1elSOexwS-h4NEIw0oxlOmNtHLg&oe=67EA9736" + }, + "canonical_uri_with_fallback": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/atenci%C3%B3n-fuerza-civil-acaba-de-ubicar-y-detener-al-agresor-del-tlacuache-del-vid/634398716179575/" + } + ], + "feedbackId": "ZmVlZGJhY2s6MTIyMTc4MjExMjY1MjUzNQ==", + "topLevelUrl": "https://www.facebook.com/100044622720400/posts/1221782112652535", + "facebookId": "100044622720400", + "pageAdLibrary": { + "is_business_page_active": false, + "id": "384214801745089" + }, + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA" + }, + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA", + "postId": "1221707579326655", + "pageName": "SAMUELGARCIASEPULVEDA", + "url": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/", + "time": "2025-03-26T18:11:23.000Z", + "timestamp": 1743012683, + "user": { + "id": "100044622720400", + "name": "Samuel García", + "profileUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA", + "profilePic": "https://scontent.fpos3-1.fna.fbcdn.net/v/t39.30808-1/462468035_1103295834501164_3633703158841014008_n.jpg?stp=cp0_dst-jpg_s40x40_tt6&_nc_cat=1&ccb=1-7&_nc_sid=2d3e12&_nc_ohc=Hv51ddbfe1UQ7kNvgFqKiYt&_nc_oc=AdmCYVr8R4mvZR85naoaQFE96o97nllo0loimUSLWU2QqQNg-Pmz90NqD6fqS83e2IY&_nc_zt=24&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYEmimH3S8gkq0qFHNFRsbFiWPyiJnWYFqP_PcI_h-Ba9w&oe=67EA71C3" + }, + "text": "¡Atentos!\n\nLas lluvias se desplazan desde la Región Citrícola hacia la zona metropolitana. Esta agua será de gran ayuda para nuestras presas y para combatir incendios, pero hay que estar al pendiente de las recomendaciones de Protección Civil Nuevo León para evitar tragedias.", + "textReferences": [ + { + "id": "100064721526723", + "url": "https://www.facebook.com/proteccioncivilnuevoleon", + "profile_url": "https://www.facebook.com/proteccioncivilnuevoleon", + "short_name": "Protección Civil Nuevo León", + "work_info": null, + "work_foreign_entity_info": null, + "mobileUrl": "https://m.facebook.com/proteccioncivilnuevoleon" + } + ], + "likes": 1286, + "comments": 207, + "shares": 202, + "topReactionsCount": 6, + "isVideo": true, + "viewsCount": 20411, + "media": [ + { + "thumbnail": "https://scontent.fpos3-1.fna.fbcdn.net/v/t15.5256-10/487324535_719107404015175_1457509433992962812_n.jpg?stp=dst-jpg_s960x960_tt6&_nc_cat=107&ccb=1-7&_nc_sid=7965db&_nc_ohc=FO2CaU6PnTIQ7kNvgFkzqEi&_nc_oc=AdkV-FfMetoyiAoPq_MHChOpmDaVuKG2flveHx8S2AUhJA8Zc1m239bNh4Xa0l4jZdc&_nc_zt=23&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYHdPq_RzVV_lRgmB3VTa6oVjD87-osu8ejAKg2C91cD3Q&oe=67EA9E97", + "__typename": "Video", + "thumbnailImage": { + "uri": "https://scontent.fpos3-1.fna.fbcdn.net/v/t15.5256-10/487324535_719107404015175_1457509433992962812_n.jpg?stp=dst-jpg_s960x960_tt6&_nc_cat=107&ccb=1-7&_nc_sid=7965db&_nc_ohc=FO2CaU6PnTIQ7kNvgFkzqEi&_nc_oc=AdkV-FfMetoyiAoPq_MHChOpmDaVuKG2flveHx8S2AUhJA8Zc1m239bNh4Xa0l4jZdc&_nc_zt=23&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYHdPq_RzVV_lRgmB3VTa6oVjD87-osu8ejAKg2C91cD3Q&oe=67EA9E97" + }, + "id": "559443289882008", + "is_clipping_enabled": false, + "live_rewind_enabled": false, + "owner": { + "__typename": "User", + "id": "100044622720400", + "__isVideoOwner": "User", + "has_professional_features_for_watch": true + }, + "playable_duration_in_ms": 49108, + "is_huddle": false, + "url": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/", + "if_viewer_can_use_latency_menu": null, + "if_viewer_can_use_latency_menu_toggle": null, + "captions_url": null, + "video_available_captions_locales": [ + { + "localized_creation_method": "Automático", + "captions_url": "https://scontent.fpos3-1.fna.fbcdn.net/v/t39.2093-6/486086207_559446429881694_4827010444387277402_n.srt?_nc_cat=105&ccb=1-7&_nc_sid=c211c2&_nc_ohc=vlo8FO0sTXkQ7kNvgFz_I5B&_nc_oc=AdnghYhEl49uoiKfZYoENZHO1yVhkcJkILuMcnlY2lxbIFUHV6TH2p7-_XJEubWsEYs&_nc_zt=14&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYHJhO5hGomz8yd1Qhxqjq9ItK_-AsNFGnnVoi4gUA72eA&oe=67EA78BA", + "locale": "es_CL", + "localized_language": "Español", + "localized_country": null + } + ], + "if_viewer_can_see_community_moderation_tools": null, + "if_viewer_can_use_live_rewind": null, + "if_viewer_can_use_clipping": null, + "if_viewer_can_see_costreaming_tools": null, + "video_player_scrubber_base_content_renderer": null, + "video_player_scrubber_preview_renderer": { + "__typename": "XFBVideoPlayerScrubberDefaultPreviewRenderer", + "video": { + "scrubber_preview_thumbnail_information": { + "sprite_uris": [ + "https://scontent.fpos3-1.fna.fbcdn.net/v/t15.6481-10/486859568_505398099293546_2386886126898894494_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=62976d&_nc_ohc=_P_o9Z9cc6IQ7kNvgHYqZ56&_nc_oc=AdnGSJr6gHP8EziL9vdUBT0xs7Cq8Ecz2GXT8FF8hGrcZ-1S2nnqNSq9rZxbHu2_Opg&_nc_zt=23&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYFAJKGq9alur8A96_IGRNSQt0q8T3IqESUYLFEFHZEgxQ&oe=67EA7DE2" + ], + "thumbnail_width": 100, + "thumbnail_height": 176, + "has_preview_thumbnails": true, + "num_images_per_row": 10, + "max_number_of_images_per_sprite": 100, + "time_interval_between_image": 1 + }, + "id": "559443289882008" + }, + "__module_operation_VideoPlayerScrubberPreview_video": { + "__dr": "VideoPlayerScrubberDefaultPreview_video$normalization.graphql" + }, + "__module_component_VideoPlayerScrubberPreview_video": { + "__dr": "VideoPlayerScrubberDefaultPreview.react" + } + }, + "recipient_group": null, + "music_attachment_metadata": null, + "video_container_type": "CONTAINED_POST_ATTACHMENT", + "breakingStatus": false, + "videoId": "559443289882008", + "isPremiere": false, + "liveViewerCount": 0, + "rehearsalInfo": null, + "is_gaming_video": false, + "is_live_audio_room_v2_broadcast": false, + "publish_time": 1743012287, + "live_speaker_count_indicator": null, + "can_viewer_share": false, + "end_cards_channel_info": { + "video_chaining_caller": "CHANNEL_VIEW_FROM_END_CARD", + "video_channel_entry_point": "PROFILE", + "video": { + "id": "559443289882008", + "can_viewer_share": false, + "creation_story": { + "shareable": null, + "id": "UzpfSTEwMDA0NDYyMjcyMDQwMDpWSzo1NTk0NDMyODk4ODIwMDg=" + } + }, + "__module_operation_useVideoPlayerWatchEndScreenWithActions_video": { + "__dr": "VideoPlayerWatchInlineEndScreen_info$normalization.graphql" + }, + "__module_component_useVideoPlayerWatchEndScreenWithActions_video": { + "__dr": "VideoPlayerWatchInlineEndScreen.react" + } + }, + "is_soundbites_video": false, + "is_looping": false, + "info": { + "video_chaining_caller": "CHANNEL_VIEW_FROM_END_CARD", + "video_channel_entry_point": "PROFILE", + "video_id": "559443289882008", + "__module_operation_useVideoPlayerWatchPauseScreenWithActions_video": { + "__dr": "VideoPlayerWatchInlinePauseScreen_info$normalization.graphql" + }, + "__module_component_useVideoPlayerWatchPauseScreenWithActions_video": { + "__dr": "VideoPlayerWatchInlinePauseScreen.react" + } + }, + "animated_image_caption": null, + "width": 720, + "height": 1280, + "broadcaster_origin": null, + "broadcast_id": null, + "broadcast_status": null, + "is_live_streaming": false, + "is_live_trace_enabled": false, + "is_video_broadcast": false, + "is_podcast_video": false, + "loop_count": 0, + "is_spherical": false, + "is_spherical_enabled": true, + "unsupported_browser_message": null, + "can_play_undiscoverable": true, + "pmv_metadata": null, + "latency_sensitive_config": null, + "live_playback_instrumentation_configs": null, + "is_ncsr": false, + "permalink_url": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/559443289882008/", + "dash_prefetch_experimental": [ + "1339635660655224v", + "1740965536775697a" + ], + "video_status_type": "OK", + "can_use_oz": true, + "dash_manifest": "\nhttps://video.fpos3-1.fna.fbcdn.net/o1/v/t2/f2/m69/AQNNH-iaV534Rae2Dkn4VGbjrSZGCqGh2KQCzxjZFOpQtdxi4rL93_eesJWdMtK7dLTanmDXQxooWJiRu7EaDh3B.mp4?strext=1&_nc_cat=111&_nc_oc=AdlBfdqQUY_SXxyFSyHtIcQowCXhurcD07gxuDtv4vWf20I77tizcQw0Ozx-tZWP8rs&_nc_sid=9ca052&_nc_ht=video.fpos3-1.fna.fbcdn.net&_nc_ohc=Q0r_iat27K0Q7kNvgG6AR7P&efg=eyJ2ZW5jb2RlX3RhZyI6ImRhc2hfcjJhdjEtcjFnZW4ydnA5X3EzMCIsInZpZGVvX2lkIjo1NTk0NDMyODk4ODIwMDgsImNsaWVudF9uYW1lIjoidW5rbm93biIsIm9pbF91cmxnZW5fYXBwX2lkIjowLCJ4cHZfYXNzZXRfaWQiOjUyMTAxMzAzNzQzNjA5MCwidmlfdXNlY2FzZV9pZCI6MTAxMjIsImR1cmF0aW9uX3MiOjQ5LCJ1cmxnZW5fc291cmNlIjoid3d3In0%3D&ccb=17-1&_nc_zt=28&oh=00_AYEOqf4lqZXaWWp49C2c-hwwiLNqGLDk55AAAafo8u8H_g&oe=67EA6C2Fhttps://video.fpos3-1.fna.fbcdn.net/o1/v/t2/f2/m69/AQOAM8VRUQ6Bg4SQPI7de4GZIl4VAcIvKUDLaeaFstlAg733OUkyM7tlpvFGvZZyrpPwQoA6YHVjY61268XmZ2Wl.mp4?strext=1&_nc_cat=102&_nc_oc=Adm0nmCa6hYehGgCxNxr4k5EajlEUkDtSkcGF1M7j9AOUc0pw-GY3okcScCgK0lw1UM&_nc_sid=9ca052&_nc_ht=video.fpos3-1.fna.fbcdn.net&_nc_ohc=4SYFsCqk0lIQ7kNvgGPbJtk&efg=eyJ2ZW5jb2RlX3RhZyI6ImRhc2hfcjJhdjEtcjFnZW4ydnA5X3E0MCIsInZpZGVvX2lkIjo1NTk0NDMyODk4ODIwMDgsImNsaWVudF9uYW1lIjoidW5rbm93biIsIm9pbF91cmxnZW5fYXBwX2lkIjowLCJ4cHZfYXNzZXRfaWQiOjUyMTAxMzAzNzQzNjA5MCwidmlfdXNlY2FzZV9pZCI6MTAxMjIsImR1cmF0aW9uX3MiOjQ5LCJ1cmxnZW5fc291cmNlIjoid3d3In0%3D&ccb=17-1&_nc_zt=28&oh=00_AYGUeRTN3OQzMz6ywjRKU3AfhmiLQr7qDcphq2SdfTBNcw&oe=67EA9F40https://video.fpos3-1.fna.fbcdn.net/o1/v/t2/f2/m69/AQMEOwTHrfUi-wjDVsnHaBizA9hDjl76ZaqTs1C_e_ADW8aNJHRNOZRKAH5uk7PLXZeP9P-iMo-WGEsHtnzPzTVB.mp4?strext=1&_nc_cat=105&_nc_oc=AdmF5ExE-txNZFDW2owF04OXcAA9LAOyZD3pWt6ebA_yokGob8LrmKpafKd0gy1WFhA&_nc_sid=9ca052&_nc_ht=video.fpos3-1.fna.fbcdn.net&_nc_ohc=gBd79MWIZ6MQ7kNvgEBhDT0&efg=eyJ2ZW5jb2RlX3RhZyI6ImRhc2hfcjJhdjEtcjFnZW4ydnA5X3E1MCIsInZpZGVvX2lkIjo1NTk0NDMyODk4ODIwMDgsImNsaWVudF9uYW1lIjoidW5rbm93biIsIm9pbF91cmxnZW5fYXBwX2lkIjowLCJ4cHZfYXNzZXRfaWQiOjUyMTAxMzAzNzQzNjA5MCwidmlfdXNlY2FzZV9pZCI6MTAxMjIsImR1cmF0aW9uX3MiOjQ5LCJ1cmxnZW5fc291cmNlIjoid3d3In0%3D&ccb=17-1&_nc_zt=28&oh=00_AYGZIrWdMExeF7TCcHZVmXuCDLmOAo4BzzuSNsMznYanxw&oe=67EA96DDhttps://video.fpos3-1.fna.fbcdn.net/o1/v/t2/f2/m69/AQMSE_64KyDXVvtd4AP0CGEBrO8X0laMerQg9Y4-mX8SqJ00Tb5E5c3kB1gVaMeUpSHbjUZQPzmx9F97VTWGLgwc.mp4?strext=1&_nc_cat=110&_nc_oc=AdmyTzqNCAwo9HK03zbRRaHHNL6UrGBTPNd5ceB8Jbh0PFKJ7Cx9ZahsnrGv8XSGThE&_nc_sid=9ca052&_nc_ht=video.fpos3-1.fna.fbcdn.net&_nc_ohc=Ifd1eccEYVEQ7kNvgFzxHYU&efg=eyJ2ZW5jb2RlX3RhZyI6ImRhc2hfcjJhdjEtcjFnZW4ydnA5X3E2MCIsInZpZGVvX2lkIjo1NTk0NDMyODk4ODIwMDgsImNsaWVudF9uYW1lIjoidW5rbm93biIsIm9pbF91cmxnZW5fYXBwX2lkIjowLCJ4cHZfYXNzZXRfaWQiOjUyMTAxMzAzNzQzNjA5MCwidmlfdXNlY2FzZV9pZCI6MTAxMjIsImR1cmF0aW9uX3MiOjQ5LCJ1cmxnZW5fc291cmNlIjoid3d3In0%3D&ccb=17-1&_nc_zt=28&oh=00_AYG9AuLsxBui_7j2x5S4x9OfCDBgCZOcsipDg9tPF_X7hg&oe=67EA863Chttps://video.fpos3-1.fna.fbcdn.net/o1/v/t2/f2/m69/AQONNbfDHbPogmMlEf9y4ZZAfoiFDa1CLfyiqW_dDHgIz8I_MlOEAAnjPHdqAjZpoQDu9f3oi9uqg1B4wk-mdD5N.mp4?strext=1&_nc_cat=106&_nc_oc=AdmtcKOGcwDwvg-uoIz-F4Jxmb_7DUgTB-5js8B7QmzELKHVP6yn8YP9e7PvRxySPmA&_nc_sid=9ca052&_nc_ht=video.fpos3-1.fna.fbcdn.net&_nc_ohc=qqgTFEqvK2oQ7kNvgHt4_H-&efg=eyJ2ZW5jb2RlX3RhZyI6ImRhc2hfcjJhdjEtcjFnZW4ydnA5X3E3MCIsInZpZGVvX2lkIjo1NTk0NDMyODk4ODIwMDgsImNsaWVudF9uYW1lIjoidW5rbm93biIsIm9pbF91cmxnZW5fYXBwX2lkIjowLCJ4cHZfYXNzZXRfaWQiOjUyMTAxMzAzNzQzNjA5MCwidmlfdXNlY2FzZV9pZCI6MTAxMjIsImR1cmF0aW9uX3MiOjQ5LCJ1cmxnZW5fc291cmNlIjoid3d3In0%3D&ccb=17-1&_nc_zt=28&oh=00_AYGX1ZYjQ8gkaU785R9bvmTBeTYXPrvkfjpGyJo31rYf7Q&oe=67EA8578https://video.fpos3-1.fna.fbcdn.net/o1/v/t2/f2/m69/AQOIUPiTYqfg6asrebxRCdXv0DNtgCY9hxxbS-GneTCq8vspzia34TkdIKpTUeiWLyL92FRGo1bHhFezeXN7sJUN.mp4?strext=1&_nc_cat=102&_nc_oc=AdlEiODvu47O1ulWGlTCAfe1-X00Q4rtZ0Q9G0wRNUAXy1rsUcUAZ_mUDGQ7e0zLa4c&_nc_sid=9ca052&_nc_ht=video.fpos3-1.fna.fbcdn.net&_nc_ohc=oy8nkn9z9ycQ7kNvgE75ZDU&efg=eyJ2ZW5jb2RlX3RhZyI6ImRhc2hfcjJhdjEtcjFnZW4ydnA5X3E4MCIsInZpZGVvX2lkIjo1NTk0NDMyODk4ODIwMDgsImNsaWVudF9uYW1lIjoidW5rbm93biIsIm9pbF91cmxnZW5fYXBwX2lkIjowLCJ4cHZfYXNzZXRfaWQiOjUyMTAxMzAzNzQzNjA5MCwidmlfdXNlY2FzZV9pZCI6MTAxMjIsImR1cmF0aW9uX3MiOjQ5LCJ1cmxnZW5fc291cmNlIjoid3d3In0%3D&ccb=17-1&_nc_zt=28&oh=00_AYHTzVozKdBYC7fqYG5O7Bo_5oqT9qYWZp5c_tTsgGqwZA&oe=67EA9338https://video.fpos3-1.fna.fbcdn.net/o1/v/t2/f2/m69/AQPZ1_cxbfCAlKF28y9CmHfEtTio4BlwpvBsGK1USptejU-isK3ki7p1ML_fswrPjHIczRIZM9tBbvH32S-US18y.mp4?strext=1&_nc_cat=108&_nc_oc=AdkSVbhfXNhhszBTgFxF-T3wbghe3VNtsEkM0fUVZkwZW76xD3nztoXoB6HYMPVgsSE&_nc_sid=9ca052&_nc_ht=video.fpos3-1.fna.fbcdn.net&_nc_ohc=z5dy_sa_8JQQ7kNvgFCtFjH&efg=eyJ2ZW5jb2RlX3RhZyI6ImRhc2hfcjJhdjEtcjFnZW4ydnA5X3E5MCIsInZpZGVvX2lkIjo1NTk0NDMyODk4ODIwMDgsImNsaWVudF9uYW1lIjoidW5rbm93biIsIm9pbF91cmxnZW5fYXBwX2lkIjowLCJ4cHZfYXNzZXRfaWQiOjUyMTAxMzAzNzQzNjA5MCwidmlfdXNlY2FzZV9pZCI6MTAxMjIsImR1cmF0aW9uX3MiOjQ5LCJ1cmxnZW5fc291cmNlIjoid3d3In0%3D&ccb=17-1&_nc_zt=28&oh=00_AYGi7sYVgxChGwyjPrvXHIGY9mNlP8v8dcVUX4MaC7JYBA&oe=67EA8F46https://video.fpos3-1.fna.fbcdn.net/o1/v/t2/f2/m69/AQMhG4A7OWgz7MXRLqt_Ry_zuPrPufRsVz_bWw4oINVBT_bWfRhE8lC2DH9vlgHjHXL_tZpYSi69SeyG_C11uICx.mp4?strext=1&_nc_cat=111&_nc_oc=AdlJOYl2UzKMaO5oX5aMIl-KXVebOlxZcSX-25MuKux5pBb4QbXmsYXam53E0GgPrdw&_nc_sid=9ca052&_nc_ht=video.fpos3-1.fna.fbcdn.net&_nc_ohc=n_uXrVzY01cQ7kNvgHrk_AB&efg=eyJ2ZW5jb2RlX3RhZyI6ImRhc2hfbG5faGVhYWNfdmJyM19hdWRpbyIsInZpZGVvX2lkIjo1NTk0NDMyODk4ODIwMDgsImNsaWVudF9uYW1lIjoidW5rbm93biIsIm9pbF91cmxnZW5fYXBwX2lkIjowLCJ4cHZfYXNzZXRfaWQiOjUyMTAxMzAzNzQzNjA5MCwidmlfdXNlY2FzZV9pZCI6MTAxMjIsImR1cmF0aW9uX3MiOjQ5LCJ1cmxnZW5fc291cmNlIjoid3d3In0%3D&ccb=17-1&_nc_zt=28&oh=00_AYGEwIdb6eQg8IowT4aAQDCMfmOsSbp0ajWPl39JnehJdw&oe=67EA9937\n", + "dash_manifest_url": "https://www.facebook.com/dash_mpd_debug.mpd?v=559443289882008&dummy=.mpd", + "min_quality_preference": null, + "audio_user_preferred_language": "en", + "is_rss_podcast_video": false, + "browser_native_sd_url": "https://video.fpos3-1.fna.fbcdn.net/o1/v/t2/f2/m69/AQMPeIjsd-9ujKFt_KxY9uvDdv1v0wRBVXnPRSloB7gN2g_LlgZK2oqiKvaU7wl2C18oCymBexz27sY4oenjIEcn.mp4?strext=1&_nc_cat=106&_nc_oc=Adl8rL29GuY5d6BJc-mujAUfZ-PiaOV8r4Bm_38Psjq_keKkcklhLZurpOU-IvfoHz4&_nc_sid=8bf8fe&_nc_ht=video.fpos3-1.fna.fbcdn.net&_nc_ohc=AQzk2dUkdZAQ7kNvgFcwoO6&efg=eyJ2ZW5jb2RlX3RhZyI6Inhwdl9wcm9ncmVzc2l2ZS5GQUNFQk9PSy4uQzMuMzYwLnN2ZV9zZCIsInhwdl9hc3NldF9pZCI6NTIxMDEzMDM3NDM2MDkwLCJ2aV91c2VjYXNlX2lkIjoxMDEyMiwiZHVyYXRpb25fcyI6NDksInVybGdlbl9zb3VyY2UiOiJ3d3cifQ%3D%3D&ccb=17-1&_nc_zt=28&oh=00_AYH40MoVWKBXrsHjY1vSONEH55Mcs_qCIFxd2UB3UJNCbA&oe=67EA7C28", + "browser_native_hd_url": "https://video.fpos3-1.fna.fbcdn.net/o1/v/t2/f2/m69/AQMCJ4p3YZK3s8KSNUDajHRGGWQ4syuhAL1EWTiWN7H9gfuCe876SVf7iiszCJUjhdVsGuqI0087wCE4-Aq8IdOV.mp4?strext=1&_nc_cat=111&_nc_oc=Adla33Z1uYeCO3lj4PPuvA3D078qzH_l1gKr84o-q28mQ7Cd4Jzqj34c_fz32LhxPRU&_nc_sid=5e9851&_nc_ht=video.fpos3-1.fna.fbcdn.net&_nc_ohc=RZajZAzGj1AQ7kNvgETS-wW&efg=eyJ2ZW5jb2RlX3RhZyI6Inhwdl9wcm9ncmVzc2l2ZS5GQUNFQk9PSy4uQzMuNzIwLmRhc2hfaDI2NC1iYXNpYy1nZW4yXzcyMHAiLCJ4cHZfYXNzZXRfaWQiOjUyMTAxMzAzNzQzNjA5MCwidmlfdXNlY2FzZV9pZCI6MTAxMjIsImR1cmF0aW9uX3MiOjQ5LCJ1cmxnZW5fc291cmNlIjoid3d3In0%3D&ccb=17-1&vs=f22dd3e951a9f759&_nc_vs=HBksFQIYOnBhc3N0aHJvdWdoX2V2ZXJzdG9yZS9HQ2N1OGh6VE10TVVSWjBFQUhVV0lkbjQzRndKYm1kakFBQUYVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dHcUVfUnlXLXVWWnBSOEdBSjcyUDFqczdCQU1ickZxQUFBRhUCAsgBACgAGAAbAogHdXNlX29pbAExEnByb2dyZXNzaXZlX3JlY2lwZQExFQAAJvSyqND59uwBFQIoAkMzLBdASIHKwIMSbxgZZGFzaF9oMjY0LWJhc2ljLWdlbjJfNzIwcBEAdQIA&_nc_zt=28&oh=00_AYGpwT1OeGEGBkcoeQXjs7-XgYMvG4a9WSBmF8VzTExhpw&oe=67EA7063", + "spherical_video_fallback_urls": null, + "is_latency_menu_enabled": false, + "fbls_tier": null, + "is_latency_sensitive_broadcast": false, + "comet_video_player_static_config": "{}", + "comet_video_player_context_sensitive_config": "{}", + "video_player_shaka_performance_logger_init": { + "__typename": "VideoPlayerShakaPerformanceLoggerInit", + "__module_operation_useVideoPlayerShakaPerformanceLoggerRelayImpl_video": { + "__dr": "useVideoPlayerShakaPerformanceLoggerRelayImpl_init$normalization.graphql" + }, + "__module_component_useVideoPlayerShakaPerformanceLoggerRelayImpl_video": { + "__dr": "VideoPlayerShakaPerformanceLogger" + } + }, + "video_player_shaka_performance_logger_should_sample": false, + "video_player_shaka_performance_logger_init2": { + "__typename": "VideoPlayerShakaPerformanceLoggerInit", + "__module_operation_useVideoPlayerShakaPerformanceLoggerBuilder_video": { + "__dr": "useVideoPlayerShakaPerformanceLoggerBuilder_init$normalization.graphql" + }, + "__module_component_useVideoPlayerShakaPerformanceLoggerBuilder_video": { + "__dr": "VideoPlayerShakaPerformanceLoggerBuilder" + }, + "per_session_sampling_rate": null + }, + "autoplay_gating_result": "all_page_organic_allowed", + "viewer_autoplay_setting": "default_autoplay", + "can_autoplay": true, + "drm_info": "{\"video_license_uri_map\":{},\"graph_api_video_license_uri\":null,\"fairplay_cert\":null,\"widevine_cert\":\"CsECCAMSEBcFuRfMEgSGiwYzOi93KowYgrSCkgUijgIwggEKAoIBAQCZ7Vs7Mn2rXiTvw7YqlbWYUgrVvMs3UD4GRbgU2Ha430BRBEGtjOOtsRu4jE5yWl5KngeVKR1YWEAjp+GvDjipEnk5MAhhC28VjIeMfiG\\/+\\/7qd+EBnh5XgeikX0YmPRTmDoBYqGB63OBPrIRXsTeo1nzN6zNwXZg6IftO7L1KEMpHSQykfqpdQ4IY3brxyt4zkvE9b\\/tkQv0x4b9AsMYE0cS6TJUgpL+X7r1gkpr87vVbuvVk4tDnbNfFXHOggrmWEguDWe3OJHBwgmgNb2fG2CxKxfMTRJCnTuw3r0svAQxZ6ChD4lgvC2ufXbD8Xm7fZPvTCLRxG88SUAGcn1oJAgMBAAE6FGxpY2Vuc2Uud2lkZXZpbmUuY29tEoADrjRzFLWoNSl\\/JxOI+3u4y1J30kmCPN3R2jC5MzlRHrPMveoEuUS5J8EhNG79verJ1BORfm7BdqEEOEYKUDvBlSubpOTOD8S\\/wgqYCKqvS\\/zRnB3PzfV0zKwo0bQQQWz53ogEMBy9szTK\\/NDUCXhCOmQuVGE98K\\/PlspKkknYVeQrOnA+8XZ\\/apvTbWv4K+drvwy6T95Z0qvMdv62Qke4XEMfvKUiZrYZ\\/DaXlUP8qcu9u\\/r6DhpV51Wjx7zmVflkb1gquc9wqgi5efhn9joLK3\\/bNixbxOzVVdhbyqnFk8ODyFfUnaq3fkC3hR3f0kmYgI41sljnXXjqwMoW9wRzBMINk+3k6P8cbxfmJD4\\/Paj8FwmHDsRfuoI6Jj8M76H3CTsZCZKDJjM3BQQ6Kb2m+bQ0LMjfVDyxoRgvfF\\/\\/M\\/EEkPrKWyU2C3YBXpxaBquO4C8A0ujVmGEEqsxN1HX9lu6c5OMm8huDxwWFd7OHMs3avGpr7RP7DUnTikXrh6X0\"}", + "p2p_settings": null, + "audio_settings": null, + "captions_settings": null, + "broadcast_low_latency_config": null, + "audio_availability": "AVAILABLE", + "muted_segments": [], + "spherical_video_renderer": null, + "preferred_thumbnail": { + "image": { + "uri": "https://scontent.fpos3-1.fna.fbcdn.net/v/t15.5256-10/487324535_719107404015175_1457509433992962812_n.jpg?stp=dst-jpg_s960x960_tt6&_nc_cat=107&ccb=1-7&_nc_sid=be8305&_nc_ohc=FO2CaU6PnTIQ7kNvgFkzqEi&_nc_oc=AdkV-FfMetoyiAoPq_MHChOpmDaVuKG2flveHx8S2AUhJA8Zc1m239bNh4Xa0l4jZdc&_nc_zt=23&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYHdPq_RzVV_lRgmB3VTa6oVjD87-osu8ejAKg2C91cD3Q&oe=67EA9E97" + }, + "image_preview_payload": "ABgqufYYW5Cj8P8A6xo+woPull+jGoAohQmToO+SP8moF1FT8rbsd+f8n+dZ819kXa3UvfZ3H3ZHH1IP8xRTICV5ycH8j/Sipc4roPlZVlmWcBApGGBxkHIGeOuakO0oB5ftjArEikIkB7ZH61tfPn16/wCf8avltoik77kcbpEoUh8jg4Bx+HtRVaK9baAXIP14orJpX1TGvJozAcVuqoWZXJOHAOc9yP8APFYFbEp/dRn/AGB/MV1I52VLuHyZWXt1H0NFWtU+8v0P86KHuNH/2Q==", + "id": "719107400681842" + }, + "video_imf_data": null, + "original_width": 720, + "original_height": 1280, + "original_rotation": "NO_ROTATE", + "if_viewer_can_see_pay_to_access_paywall": null, + "comet_video_player_audio_overlay_renderer": null, + "comet_video_player_audio_background_renderer": null, + "comet_video_player_music_sprout_background_renderer": null, + "clip_fallback_cover": null, + "is_clip": false, + "matcha_related_keywords_links": [ + "" + ], + "is_music_clip": false, + "video_collaborator_page_or_delegate_page": null, + "video_anchor_tag_info": null, + "image": { + "uri": "https://scontent.fpos3-1.fna.fbcdn.net/v/t15.5256-10/487324535_719107404015175_1457509433992962812_n.jpg?stp=dst-jpg_p296x100_tt6&_nc_cat=107&ccb=1-7&_nc_sid=7965db&_nc_ohc=FO2CaU6PnTIQ7kNvgFkzqEi&_nc_oc=AdkV-FfMetoyiAoPq_MHChOpmDaVuKG2flveHx8S2AUhJA8Zc1m239bNh4Xa0l4jZdc&_nc_zt=23&_nc_ht=scontent.fpos3-1.fna&_nc_gid=eIGgN7KjwmcnH57eCFRegw&oh=00_AYF7VBX9eEO0FIe5SVWxuY-f_EsKMEBvs7zIWVvg2bxuIw&oe=67EA9E97" + }, + "canonical_uri_with_fallback": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/videos/atentoslas-lluvias-se-desplazan-desde-la-regi%C3%B3n-citr%C3%ADcola-hacia-la-zona-metropol/559443289882008/" + } + ], + "feedbackId": "ZmVlZGJhY2s6MTIyMTcwNzU3OTMyNjY1NQ==", + "topLevelUrl": "https://www.facebook.com/100044622720400/posts/1221707579326655", + "facebookId": "100044622720400", + "pageAdLibrary": { + "is_business_page_active": false, + "id": "384214801745089" + }, + "inputUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA" + } +] \ No newline at end of file diff --git a/backend/app/testing/data/facebook/profile_samples.json b/backend/app/testing/data/facebook/profile_samples.json new file mode 100644 index 0000000000..402dcc52b9 --- /dev/null +++ b/backend/app/testing/data/facebook/profile_samples.json @@ -0,0 +1,33 @@ +[ + { + "facebookUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/", + "categories": [ + "Page", + "Politician" + ], + "info": [ + "Samuel García. 2,405,575 likes", + "169,290 talking about this. Esposo de Mariana, papá de Mariel y gobernador de Nuevo León." + ], + "likes": 2405575, + "messenger": null, + "title": "Samuel García", + "pageId": "100044622720400", + "pageName": "SAMUELGARCIASEPULVEDA", + "pageUrl": "https://www.facebook.com/SAMUELGARCIASEPULVEDA/", + "intro": "Esposo de Mariana, papá de Mariel y gobernador de Nuevo León.", + "websites": [], + "followers": 2744689, + "followings": 38, + "profilePictureUrl": "https://scontent-atl3-1.xx.fbcdn.net/v/t39.30808-1/462468035_1103295834501164_3633703158841014008_n.jpg?stp=dst-jpg_s200x200_tt6&_nc_cat=1&ccb=1-7&_nc_sid=f907e8&_nc_ohc=Hv51ddbfe1UQ7kNvgFSivlX&_nc_zt=24&_nc_ht=scontent-atl3-1.xx&_nc_gid=c8OS5Nn-i3Z7nqwWsfj4dA&oh=00_AYEqMJ6rTZF_ag5d_SVx9YTaqKPEkh-Uyafd5yjkXYHPvA&oe=67EA71C3", + "coverPhotoUrl": "https://scontent-atl3-2.xx.fbcdn.net/v/t39.30808-6/483977736_1210612180436195_4847182822794722596_n.jpg?stp=cp6_dst-jpg_s960x960_tt6&_nc_cat=105&ccb=1-7&_nc_sid=cc71e4&_nc_ohc=INYUAkCALFEQ7kNvgGXAcZT&_nc_zt=23&_nc_ht=scontent-atl3-2.xx&_nc_gid=uKRl_mkYmeDlQfCJvlJ5ng&oh=00_AYHnUh7rfpp5M91_dvYDZb39Nx8_FphUWDdWOo7C5DFypQ&oe=67EA748E", + "profilePhoto": "https://www.facebook.com/photo/?fbid=1103295827834498&set=a.540627620767991", + "creation_date": "December 12, 2014", + "ad_status": "This Page is currently running ads.", + "facebookId": "100044622720400", + "pageAdLibrary": { + "is_business_page_active": false, + "id": "384214801745089" + } + } +] \ No newline at end of file diff --git a/backend/app/testing/data/instagram/actors.md b/backend/app/testing/data/instagram/actors.md new file mode 100644 index 0000000000..efe3074b39 --- /dev/null +++ b/backend/app/testing/data/instagram/actors.md @@ -0,0 +1,9 @@ +# instagram profile +apify/instagram-profile-scraper + +# instagram posts +apify/instagram-profile-scraper +latestPosts key + +# instagram comments +apify/instagram-comment-scraper \ No newline at end of file diff --git a/backend/app/testing/data/instagram/comment_samples.json b/backend/app/testing/data/instagram/comment_samples.json new file mode 100644 index 0000000000..f28e9e5cda --- /dev/null +++ b/backend/app/testing/data/instagram/comment_samples.json @@ -0,0 +1,621 @@ +[ + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "18021118748474094", + "text": "👏👏", + "ownerUsername": "oscarcuellocoronado", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/453036863_994371468987224_3689516965341685068_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=108&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=wZRmQXoIK2sQ7kNvgGpZN5A&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHAIbZcp_WRQnvGDLcxoOD-TjGDdEYqohQaanhutkMZCA&oe=67EA6DE4&_nc_sid=f5838a", + "timestamp": "2025-02-27T01:31:50.000Z", + "repliesCount": 1, + "replies": [ + { + "id": "17917498377065887", + "text": "@oscarcuellocoronado 🙌🏼😄", + "ownerUsername": "adriandelagarzas", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "timestamp": "2025-02-27T01:39:10.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 0, + "owner": { + "fbid_v2": 17841401575893178, + "full_name": "Adrián de la Garza", + "id": "1483444529", + "is_mentionable": true, + "is_private": false, + "is_verified": true, + "latest_reel_media": 1743026392, + "profile_pic_id": "3560700728985414937", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "username": "adriandelagarzas" + } + } + ], + "likesCount": 2, + "owner": { + "fbid_v2": 17841415278525780, + "full_name": "Oscar Cuello", + "id": "15166237284", + "is_mentionable": true, + "is_private": false, + "is_verified": false, + "latest_reel_media": 0, + "profile_pic_id": "3422370971825093335", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/453036863_994371468987224_3689516965341685068_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=108&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=wZRmQXoIK2sQ7kNvgGpZN5A&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHAIbZcp_WRQnvGDLcxoOD-TjGDdEYqohQaanhutkMZCA&oe=67EA6DE4&_nc_sid=f5838a", + "username": "oscarcuellocoronado" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "17868037254324841", + "text": "👏👏", + "ownerUsername": "oscarcuellocoronado", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/453036863_994371468987224_3689516965341685068_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=108&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=wZRmQXoIK2sQ7kNvgGpZN5A&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHAIbZcp_WRQnvGDLcxoOD-TjGDdEYqohQaanhutkMZCA&oe=67EA6DE4&_nc_sid=f5838a", + "timestamp": "2025-02-26T22:44:00.000Z", + "repliesCount": 1, + "replies": [ + { + "id": "18058927034060457", + "text": "@oscarcuellocoronado 👊🏼👏🏼", + "ownerUsername": "adriandelagarzas", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "timestamp": "2025-02-26T23:13:59.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 0, + "owner": { + "fbid_v2": 17841401575893178, + "full_name": "Adrián de la Garza", + "id": "1483444529", + "is_mentionable": true, + "is_private": false, + "is_verified": true, + "latest_reel_media": 1743026392, + "profile_pic_id": "3560700728985414937", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "username": "adriandelagarzas" + } + } + ], + "likesCount": 2, + "owner": { + "fbid_v2": 17841415278525780, + "full_name": "Oscar Cuello", + "id": "15166237284", + "is_mentionable": true, + "is_private": false, + "is_verified": false, + "latest_reel_media": 0, + "profile_pic_id": "3422370971825093335", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/453036863_994371468987224_3689516965341685068_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=108&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=wZRmQXoIK2sQ7kNvgGpZN5A&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHAIbZcp_WRQnvGDLcxoOD-TjGDdEYqohQaanhutkMZCA&oe=67EA6DE4&_nc_sid=f5838a", + "username": "oscarcuellocoronado" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "18491955697030787", + "text": "Excelente el trabajo que hace como alcalde 👏👏👏", + "ownerUsername": "paomiravalle11", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/49907479_370241533707237_1425593172750237696_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=109&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=tpyD-GRDr3EQ7kNvgHqyDLE&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYGDoEm0jgBnhjP5cdO3d8P-iqv3m0NXcnekLN0p7X_d9g&oe=67EA4DF8&_nc_sid=f5838a", + "timestamp": "2025-02-27T13:53:44.000Z", + "repliesCount": 1, + "replies": [ + { + "id": "18035915462614659", + "text": "@paomiravalle11 🙌🏼❤", + "ownerUsername": "adriandelagarzas", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "timestamp": "2025-02-27T16:48:56.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 0, + "owner": { + "fbid_v2": 17841401575893178, + "full_name": "Adrián de la Garza", + "id": "1483444529", + "is_mentionable": true, + "is_private": false, + "is_verified": true, + "latest_reel_media": 1743026392, + "profile_pic_id": "3560700728985414937", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "username": "adriandelagarzas" + } + } + ], + "likesCount": 2, + "owner": { + "fbid_v2": 17841403619356648, + "full_name": "Paola Miravalle", + "id": "3653192645", + "is_mentionable": true, + "is_private": true, + "is_verified": false, + "profile_pic_id": "1984739790102077680", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/49907479_370241533707237_1425593172750237696_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=109&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=tpyD-GRDr3EQ7kNvgHqyDLE&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYGDoEm0jgBnhjP5cdO3d8P-iqv3m0NXcnekLN0p7X_d9g&oe=67EA4DF8&_nc_sid=f5838a", + "username": "paomiravalle11" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "17957981861873435", + "text": "Un honor haber sido parte de este evento tan significativo.", + "ownerUsername": "javier_araujod", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/309017903_1832243043790757_3849318263673503066_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=105&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=CxEsH4QnsjAQ7kNvgGtksyZ&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYFMrbaE6RNvYxaCmT4Ng1vR7JmxW8YCpn5nWQ4ty49NIw&oe=67EA746D&_nc_sid=f5838a", + "timestamp": "2025-03-03T03:32:28.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 1, + "owner": { + "fbid_v2": 17841455760765116, + "full_name": "", + "id": "55720930692", + "is_mentionable": true, + "is_private": false, + "is_verified": false, + "latest_reel_media": 0, + "profile_pic_id": "2936058992402139456", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/309017903_1832243043790757_3849318263673503066_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=105&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=CxEsH4QnsjAQ7kNvgGtksyZ&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYFMrbaE6RNvYxaCmT4Ng1vR7JmxW8YCpn5nWQ4ty49NIw&oe=67EA746D&_nc_sid=f5838a", + "username": "javier_araujod" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "18151710361357057", + "text": "Muy bien así se trabaja 👍🙌", + "ownerUsername": "elsarodriguez0819", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/452864731_908211221115460_708702197581628371_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=104&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=u6mmvozinl4Q7kNvgF8ANRX&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYG1XrpY9sJ_mUUx5QVaPkLHaBr1F8jR67Bdo9xZcdptAQ&oe=67EA6552&_nc_sid=f5838a", + "timestamp": "2025-02-26T21:04:37.000Z", + "repliesCount": 1, + "replies": [ + { + "id": "17959277978761134", + "text": "@elsarodriguez0819 👊🏼😎", + "ownerUsername": "adriandelagarzas", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "timestamp": "2025-02-26T22:11:17.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 0, + "owner": { + "fbid_v2": 17841401575893178, + "full_name": "Adrián de la Garza", + "id": "1483444529", + "is_mentionable": true, + "is_private": false, + "is_verified": true, + "latest_reel_media": 1743026392, + "profile_pic_id": "3560700728985414937", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "username": "adriandelagarzas" + } + } + ], + "likesCount": 2, + "owner": { + "fbid_v2": 17841455930419584, + "full_name": "Elsa Rodriguez", + "id": "55996672347", + "is_mentionable": true, + "is_private": false, + "is_verified": false, + "latest_reel_media": 0, + "profile_pic_id": "3420519602801480975", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/452864731_908211221115460_708702197581628371_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=104&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=u6mmvozinl4Q7kNvgF8ANRX&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYG1XrpY9sJ_mUUx5QVaPkLHaBr1F8jR67Bdo9xZcdptAQ&oe=67EA6552&_nc_sid=f5838a", + "username": "elsarodriguez0819" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "17916898667960935", + "text": "Monterrey con mente de futuro, ¡así se hace! 💙🏡", + "ownerUsername": "_.ximen.a_", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/486280095_29209189365391963_2297865879938708411_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=107&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=cUyNnv4Z-xwQ7kNvgEcAdaj&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYF5630fBw5tBw5RS-RzpDBLJJTvBTv7CB9-IJOZHR9l2g&oe=67EA5724&_nc_sid=f5838a", + "timestamp": "2025-02-26T23:57:38.000Z", + "repliesCount": 1, + "replies": [ + { + "id": "18294399928169133", + "text": "@_.ximen.a_ 👊🏼😎", + "ownerUsername": "adriandelagarzas", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "timestamp": "2025-02-27T01:39:25.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 0, + "owner": { + "fbid_v2": 17841401575893178, + "full_name": "Adrián de la Garza", + "id": "1483444529", + "is_mentionable": true, + "is_private": false, + "is_verified": true, + "latest_reel_media": 1743026392, + "profile_pic_id": "3560700728985414937", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "username": "adriandelagarzas" + } + } + ], + "likesCount": 2, + "owner": { + "fbid_v2": 17841402598202776, + "full_name": "𝔛𝔦𝔪𝔢𝔫𝔞", + "id": "2582214606", + "is_mentionable": true, + "is_private": true, + "is_verified": false, + "profile_pic_id": "3596790481582546740", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/486280095_29209189365391963_2297865879938708411_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=107&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=cUyNnv4Z-xwQ7kNvgEcAdaj&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYF5630fBw5tBw5RS-RzpDBLJJTvBTv7CB9-IJOZHR9l2g&oe=67EA5724&_nc_sid=f5838a", + "username": "_.ximen.a_" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "17895115017169740", + "text": "Que le vaya muy bien alcalde", + "ownerUsername": "carlossolisss1", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/426370205_381935361134509_8172899079234576112_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=0mPlkF7fbR0Q7kNvgEP-nQV&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYEQxEXjNmpoqKbwmopfVn-qUOxVKxHVnJECvxuI2IK7wg&oe=67EA4AC2&_nc_sid=f5838a", + "timestamp": "2025-02-27T13:41:21.000Z", + "repliesCount": 1, + "replies": [ + { + "id": "17929974806898942", + "text": "@carlossolisss1 ¡Un abrazo! 🙌🏼😄", + "ownerUsername": "adriandelagarzas", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "timestamp": "2025-02-27T16:47:58.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 0, + "owner": { + "fbid_v2": 17841401575893178, + "full_name": "Adrián de la Garza", + "id": "1483444529", + "is_mentionable": true, + "is_private": false, + "is_verified": true, + "latest_reel_media": 1743026392, + "profile_pic_id": "3560700728985414937", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "username": "adriandelagarzas" + } + } + ], + "likesCount": 2, + "owner": { + "fbid_v2": 17841407543339276, + "full_name": "Carlos Solis", + "id": "7594219444", + "is_mentionable": true, + "is_private": false, + "is_verified": false, + "latest_reel_media": 0, + "profile_pic_id": "3302065383862690509", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/426370205_381935361134509_8172899079234576112_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=0mPlkF7fbR0Q7kNvgEP-nQV&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYEQxEXjNmpoqKbwmopfVn-qUOxVKxHVnJECvxuI2IK7wg&oe=67EA4AC2&_nc_sid=f5838a", + "username": "carlossolisss1" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "18035289641619813", + "text": "Bendiciones en todo 🙌🙌🙌", + "ownerUsername": "alialitorres", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/311311244_1237770440128054_6246520910048233938_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=60zsPGPoAJgQ7kNvgEoBb9U&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHqBad2UXsmWd_Z9lVSF42UPWXm9F1PKrOak-XCtdW89A&oe=67EA5E2D&_nc_sid=f5838a", + "timestamp": "2025-02-27T13:41:38.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 2, + "owner": { + "fbid_v2": 17841454714079186, + "full_name": "Alicia Torres", + "id": "54633495802", + "is_mentionable": true, + "is_private": false, + "is_verified": false, + "latest_reel_media": 0, + "profile_pic_id": "2946772739558316176", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/311311244_1237770440128054_6246520910048233938_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=60zsPGPoAJgQ7kNvgEoBb9U&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHqBad2UXsmWd_Z9lVSF42UPWXm9F1PKrOak-XCtdW89A&oe=67EA5E2D&_nc_sid=f5838a", + "username": "alialitorres" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "17891981346187952", + "text": "Eso! Por eso buscan al que sabe!👏👏", + "ownerUsername": "norma_river", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/413235101_1756711698174316_2434257879176111642_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=104&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=-f0TfULGGfIQ7kNvgFRe2Wk&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHJ3UDikITiGKE2N838ybh86iehw2OHmP1L6kSwI2F5xw&oe=67EA44B7&_nc_sid=f5838a", + "timestamp": "2025-02-27T03:03:07.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 2, + "owner": { + "fbid_v2": 17841402081410268, + "full_name": "", + "id": "2002149380", + "is_mentionable": true, + "is_private": true, + "is_verified": false, + "profile_pic_id": "3267553267494846059", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/413235101_1756711698174316_2434257879176111642_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=104&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=-f0TfULGGfIQ7kNvgFRe2Wk&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHJ3UDikITiGKE2N838ybh86iehw2OHmP1L6kSwI2F5xw&oe=67EA44B7&_nc_sid=f5838a", + "username": "norma_river" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "18117565510401365", + "text": "Proyectos como este son esenciales para el futuro de Monterrey.", + "ownerUsername": "javierramirez_09", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/295825809_103290319112303_6073336335810837523_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=111&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=VPFVoziCcokQ7kNvgHu2uo_&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHejQ-DtafxCCyfMs49Ia7aKuud1hNhRrVAlhiR5-H5yw&oe=67EA69B6&_nc_sid=f5838a", + "timestamp": "2025-03-03T03:31:52.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 1, + "owner": { + "fbid_v2": 17841447738731530, + "full_name": "javier ramirez", + "id": "47532247291", + "is_mentionable": true, + "is_private": false, + "is_verified": false, + "latest_reel_media": 0, + "profile_pic_id": "2892820897959274921", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/295825809_103290319112303_6073336335810837523_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=111&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=VPFVoziCcokQ7kNvgHu2uo_&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHejQ-DtafxCCyfMs49Ia7aKuud1hNhRrVAlhiR5-H5yw&oe=67EA69B6&_nc_sid=f5838a", + "username": "javierramirez_09" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "18035912885577873", + "text": "Con usted si están viendo resultados", + "ownerUsername": "sergiomendez240", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/483599304_988131326146581_1406521373085442114_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=109&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=43U84HfvorUQ7kNvgH5c5-0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYFUgCf8ndUecx5TABlBOIdCcYkb9L7FiZRUNDutcE5Wbw&oe=67EA7504&_nc_sid=f5838a", + "timestamp": "2025-02-27T16:36:45.000Z", + "repliesCount": 1, + "replies": [ + { + "id": "18021165305669278", + "text": "@sergiomendez240 👊🏼😎", + "ownerUsername": "adriandelagarzas", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "timestamp": "2025-02-27T16:49:12.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 0, + "owner": { + "fbid_v2": 17841401575893178, + "full_name": "Adrián de la Garza", + "id": "1483444529", + "is_mentionable": true, + "is_private": false, + "is_verified": true, + "latest_reel_media": 1743026392, + "profile_pic_id": "3560700728985414937", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "username": "adriandelagarzas" + } + } + ], + "likesCount": 2, + "owner": { + "fbid_v2": 17841408605314364, + "full_name": "Sergio Mendez", + "id": "8594918843", + "is_mentionable": true, + "is_private": true, + "is_verified": false, + "profile_pic_id": "3585662518006344597", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/483599304_988131326146581_1406521373085442114_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=109&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=43U84HfvorUQ7kNvgH5c5-0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYFUgCf8ndUecx5TABlBOIdCcYkb9L7FiZRUNDutcE5Wbw&oe=67EA7504&_nc_sid=f5838a", + "username": "sergiomendez240" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "17999040833596779", + "text": "Un futuro mejor para Monterrey con un alcalde que si tiene ganas de trabajar", + "ownerUsername": "floredemariaaguirre", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/403101302_186361437800750_6166068293985838227_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=102&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=7mg-VXlStVUQ7kNvgFOSqk4&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHqw7rFByzpsQCwsEtf_oE65F02B6UWNuo4T7lXmn_ELg&oe=67EA4D7A&_nc_sid=f5838a", + "timestamp": "2025-02-27T13:55:48.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 2, + "owner": { + "fbid_v2": 17841463280714412, + "full_name": "Flore De Maria Aguirre", + "id": "63155301357", + "is_mentionable": true, + "is_private": false, + "is_verified": false, + "latest_reel_media": 0, + "profile_pic_id": "3238904536553248107", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/403101302_186361437800750_6166068293985838227_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=102&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=7mg-VXlStVUQ7kNvgFOSqk4&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHqw7rFByzpsQCwsEtf_oE65F02B6UWNuo4T7lXmn_ELg&oe=67EA4D7A&_nc_sid=f5838a", + "username": "floredemariaaguirre" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "17875875735274187", + "text": "Tenemos un alcalde que si trabaja", + "ownerUsername": "arelis.castillo_", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/309378165_191502073252835_1749598796171349600_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=111&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=muHkXRLQ2RAQ7kNvgEXWJCE&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYEEGmK846TT3OTsJf8y6subnuNCngTlmYcPLpPB6YXA7w&oe=67EA7B86&_nc_sid=f5838a", + "timestamp": "2025-02-27T13:53:01.000Z", + "repliesCount": 2, + "replies": [ + { + "id": "18264263128277228", + "text": "@arelis.castillo_ 👊🏼😎", + "ownerUsername": "adriandelagarzas", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "timestamp": "2025-02-27T16:48:25.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 0, + "owner": { + "fbid_v2": 17841401575893178, + "full_name": "Adrián de la Garza", + "id": "1483444529", + "is_mentionable": true, + "is_private": false, + "is_verified": true, + "latest_reel_media": 1743026392, + "profile_pic_id": "3560700728985414937", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "username": "adriandelagarzas" + } + } + ], + "likesCount": 2, + "owner": { + "fbid_v2": 17841445821882328, + "full_name": "Andrea Herrera", + "id": "45618631396", + "is_mentionable": true, + "is_private": true, + "is_verified": false, + "profile_pic_id": "2938925441252886068", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/309378165_191502073252835_1749598796171349600_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=111&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=muHkXRLQ2RAQ7kNvgEXWJCE&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYEEGmK846TT3OTsJf8y6subnuNCngTlmYcPLpPB6YXA7w&oe=67EA7B86&_nc_sid=f5838a", + "username": "arelis.castillo_" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "18265100251257806", + "text": "👏👏👏👏👏👏", + "ownerUsername": "juaniboniitha", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/26184586_1646238795418898_5898729662242095104_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=102&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=Jmc0aNujmbQQ7kNvgGlczWb&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYH_kUKYU1vgbtGcBABPzQz7XB-Ldkz1R45FNOTbg9Y0Fw&oe=67EA6C04&_nc_sid=f5838a", + "timestamp": "2025-02-27T15:21:42.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 2, + "owner": { + "fbid_v2": 17841406893064444, + "full_name": "Juani Boniitha", + "id": "6915565768", + "is_mentionable": true, + "is_private": false, + "is_verified": false, + "latest_reel_media": 0, + "profile_pic_id": "1691145204823623949", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/26184586_1646238795418898_5898729662242095104_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=102&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=Jmc0aNujmbQQ7kNvgGlczWb&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYH_kUKYU1vgbtGcBABPzQz7XB-Ldkz1R45FNOTbg9Y0Fw&oe=67EA6C04&_nc_sid=f5838a", + "username": "juaniboniitha" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "18052429106473530", + "text": "🔥🔥🔥🔥🔥🔥🔥 #aquisiseresuelve #imparable @adriandelagarzas and team #porunfuturomejor", + "ownerUsername": "cristinalankenau", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/387737979_3498918596988276_1489871350949530058_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=100&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=a-vgsLuLqhIQ7kNvgHMIDlR&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHZmFroaUi67cgepfNWqsKiOHLYAYDfzFkXXos3UOyoCw&oe=67EA5DB5&_nc_sid=f5838a", + "timestamp": "2025-02-26T21:46:53.000Z", + "repliesCount": 2, + "replies": [ + { + "id": "18037679246421898", + "text": "@cristinalankenau 👊🏼🙌🏼", + "ownerUsername": "adriandelagarzas", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "timestamp": "2025-02-26T22:11:25.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 0, + "owner": { + "fbid_v2": 17841401575893178, + "full_name": "Adrián de la Garza", + "id": "1483444529", + "is_mentionable": true, + "is_private": false, + "is_verified": true, + "latest_reel_media": 1743026392, + "profile_pic_id": "3560700728985414937", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=ZMSDWcCyjm8Q7kNvgENE8H0&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHKUG9OsvEGGm9zWVCV2YIZlzwz-7owGgH07TQvzNx2vQ&oe=67EA5E67&_nc_sid=f5838a", + "username": "adriandelagarzas" + } + } + ], + "likesCount": 2, + "owner": { + "fbid_v2": 17841404477736836, + "full_name": "Cristina Lankenau", + "id": "4534693257", + "is_mentionable": true, + "is_private": false, + "is_verified": false, + "latest_reel_media": 1743029657, + "profile_pic_id": "3211347532561846144", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/387737979_3498918596988276_1489871350949530058_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=100&_nc_oc=Q6cZ2QErDkYwJhJ7LWdAOrF67Vn8dW-myhb6fNaMsH3WGfgUK4qCcWhLVqYGZaK2zqXbnLU&_nc_ohc=a-vgsLuLqhIQ7kNvgHMIDlR&_nc_gid=bL8WgcEVyStMul70MLIQLA&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHZmFroaUi67cgepfNWqsKiOHLYAYDfzFkXXos3UOyoCw&oe=67EA5DB5&_nc_sid=f5838a", + "username": "cristinalankenau" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "18034706258621235", + "text": "Ojalá salgan muchas ideas buenas de este foro", + "ownerUsername": "rube_nto.g", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/444152168_1088730885521933_240115646059767695_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=105&_nc_oc=Q6cZ2QEiMlBrRRHgQcTXssZZ7ox2pQEuuUpn_wVP0mZL8j-3Xb_WkTVKfT8yh4uvm1cGSdg&_nc_ohc=4DdubMtxdfsQ7kNvgG_E4Ai&_nc_gid=QMAcBeRb_twfSR_r3B_ngg&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYEp8HKJ3XFiS0XMVaQg2oUAHgOcwvBRWBSDpiN2TfoyTA&oe=67EA4A40&_nc_sid=f5838a", + "timestamp": "2025-02-26T23:58:07.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 2, + "owner": { + "fbid_v2": 17841466977643116, + "full_name": "Rube", + "id": "66984920588", + "is_mentionable": true, + "is_private": false, + "is_verified": false, + "latest_reel_media": 0, + "profile_pic_id": "3369421178499506590", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/444152168_1088730885521933_240115646059767695_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=105&_nc_oc=Q6cZ2QEiMlBrRRHgQcTXssZZ7ox2pQEuuUpn_wVP0mZL8j-3Xb_WkTVKfT8yh4uvm1cGSdg&_nc_ohc=4DdubMtxdfsQ7kNvgG_E4Ai&_nc_gid=QMAcBeRb_twfSR_r3B_ngg&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYEp8HKJ3XFiS0XMVaQg2oUAHgOcwvBRWBSDpiN2TfoyTA&oe=67EA4A40&_nc_sid=f5838a", + "username": "rube_nto.g" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "18489558331050796", + "text": "¡Gracias por acompañarnos, alcalde! 🟣", + "ownerUsername": "uerre", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/482524266_1862062431231193_1589654281659826910_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QEiMlBrRRHgQcTXssZZ7ox2pQEuuUpn_wVP0mZL8j-3Xb_WkTVKfT8yh4uvm1cGSdg&_nc_ohc=Ir0-4Bk0D5kQ7kNvgF_ry8q&_nc_gid=QMAcBeRb_twfSR_r3B_ngg&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHRycAYrfI_O45hoeo_WHhquVW3iF7VYK4yoq5VDJZDMw&oe=67EA5F3D&_nc_sid=f5838a", + "timestamp": "2025-02-26T23:59:02.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 3, + "owner": { + "fbid_v2": 17841400252815336, + "full_name": "UERRE Universidad Regiomontana", + "id": "341448427", + "is_mentionable": true, + "is_private": false, + "is_verified": false, + "latest_reel_media": 1743023918, + "profile_pic_id": "3580430880009266888", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/482524266_1862062431231193_1589654281659826910_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2QEiMlBrRRHgQcTXssZZ7ox2pQEuuUpn_wVP0mZL8j-3Xb_WkTVKfT8yh4uvm1cGSdg&_nc_ohc=Ir0-4Bk0D5kQ7kNvgF_ry8q&_nc_gid=QMAcBeRb_twfSR_r3B_ngg&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYHRycAYrfI_O45hoeo_WHhquVW3iF7VYK4yoq5VDJZDMw&oe=67EA5F3D&_nc_sid=f5838a", + "username": "uerre" + } + }, + { + "postUrl": "https://www.instagram.com/p/DGjLVkdJQij/", + "id": "18043991810364956", + "text": "Oportunidad única para mejorar la calidad de vida de los regiomontanos.", + "ownerUsername": "maria_isabedw", + "ownerProfilePicUrl": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/288394400_718509812703018_2434286732239125632_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=106&_nc_oc=Q6cZ2QEiMlBrRRHgQcTXssZZ7ox2pQEuuUpn_wVP0mZL8j-3Xb_WkTVKfT8yh4uvm1cGSdg&_nc_ohc=kgQsUlfyprcQ7kNvgGFH2Mb&_nc_gid=QMAcBeRb_twfSR_r3B_ngg&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYElwZWeOT68vV1ZPTbWIRtcQNqahbqWLbkwnYjy8IyhhA&oe=67EA6EDA&_nc_sid=f5838a", + "timestamp": "2025-03-03T03:31:25.000Z", + "repliesCount": 0, + "replies": [], + "likesCount": 1, + "owner": { + "fbid_v2": 17841453600807276, + "full_name": "", + "id": "53658202693", + "is_mentionable": true, + "is_private": false, + "is_verified": false, + "latest_reel_media": 0, + "profile_pic_id": "2860754807897601530", + "profile_pic_url": "https://scontent-ber1-1.cdninstagram.com/v/t51.2885-19/288394400_718509812703018_2434286732239125632_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-ber1-1.cdninstagram.com&_nc_cat=106&_nc_oc=Q6cZ2QEiMlBrRRHgQcTXssZZ7ox2pQEuuUpn_wVP0mZL8j-3Xb_WkTVKfT8yh4uvm1cGSdg&_nc_ohc=kgQsUlfyprcQ7kNvgGFH2Mb&_nc_gid=QMAcBeRb_twfSR_r3B_ngg&edm=AId3EpQBAAAA&ccb=7-5&oh=00_AYElwZWeOT68vV1ZPTbWIRtcQNqahbqWLbkwnYjy8IyhhA&oe=67EA6EDA&_nc_sid=f5838a", + "username": "maria_isabedw" + } + }, + { + "id": "DGjLVkdJQij" + } +] \ No newline at end of file diff --git a/backend/app/testing/data/instagram/post_samples.json b/backend/app/testing/data/instagram/post_samples.json new file mode 100644 index 0000000000..6b29a4e55b --- /dev/null +++ b/backend/app/testing/data/instagram/post_samples.json @@ -0,0 +1,1417 @@ +[ + { + "id": "3576752389826611363", + "type": "Sidecar", + "shortCode": "DGjLVkdJQij", + "caption": "Hoy tuve el honor de participar en la inauguración del Foro de Alianzas para el Hábitat capítulo Monterrey, un espacio clave para construir la ciudad del futuro.\n\nJunto a expertos y estudiantes, buscamos soluciones reales para tener una ciudad más sustentable, ordenada y con mejor calidad de vida.\n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGjLVkdJQij/", + "commentsCount": 16, + "dimensionsHeight": 717, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481585399_18485585482052530_5292288465090866871_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=NMNioo7jz30Q7kNvgGULYOQ&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTE1NzA2OA%3D%3D.3-ccb7-5&oh=00_AYAioR1aSMWxpc5zOFAEDd4SA7llmfT2zK2ccx_sDPp9LA&oe=67C5AE7C&_nc_sid=8b3546", + "images": [ + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481585399_18485585482052530_5292288465090866871_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=NMNioo7jz30Q7kNvgGULYOQ&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTE1NzA2OA%3D%3D.3-ccb7-5&oh=00_AYAioR1aSMWxpc5zOFAEDd4SA7llmfT2zK2ccx_sDPp9LA&oe=67C5AE7C&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481896407_18485585485052530_434092325039799309_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=T6lR9-KuVTIQ7kNvgFFNydq&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTAwNjAzMw%3D%3D.3-ccb7-5&oh=00_AYDjHrNDGGtXqrDm00Z5cmikcDDW6r6vCcuOcK6HpKRLCg&oe=67C5810B&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481160795_18485585488052530_1539507575987271556_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=UeGqIKsxTeMQ7kNvgEvSnDc&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTE5OTYwNw%3D%3D.3-ccb7-5&oh=00_AYA_APTXyJ_EQcCqjiZ_MGTRxVeifJNMUz5N-CEkYRqUqw&oe=67C59DE1&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481700662_18485585509052530_2638589110064831842_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=uJZG70AJoEsQ7kNvgFDKkab&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTI0NTE3OQ%3D%3D.3-ccb7-5&oh=00_AYBPk6MhKHVdtym50vJfKxzeiMvpcwkM8k1z7JfjZSFwNg&oe=67C592D9&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481701725_18485585497052530_3587554920053704889_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7u1fTHIwl-kQ7kNvgEtbPyO&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzMDgzMjYxMw%3D%3D.3-ccb7-5&oh=00_AYCgg5aHMYa99aDXCtUXBz_t4kW4O0-uTbU1kmrQyV8kDw&oe=67C595D4&_nc_sid=8b3546" + ], + "alt": "Photo by Adrián de la Garza on February 26, 2025. May be an image of 10 people and text that says 'U-ERRE U-ERRE 55 ក្ Aniver Prepa ۲( Provoca 平 futuro Ed. Prof'.", + "likesCount": 153, + "timestamp": "2025-02-26T20:35:33.000Z", + "childPosts": [ + { + "id": "3576752376539157068", + "type": "Image", + "shortCode": "DGjLVYFJpJM", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGjLVYFJpJM/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 717, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481585399_18485585482052530_5292288465090866871_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=NMNioo7jz30Q7kNvgGULYOQ&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTE1NzA2OA%3D%3D.3-ccb7-5&oh=00_AYAioR1aSMWxpc5zOFAEDd4SA7llmfT2zK2ccx_sDPp9LA&oe=67C5AE7C&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 26, 2025. May be an image of 10 people and text that says 'U-ERRE U-ERRE 55 ក្ Aniver Prepa ۲( Provoca 平 futuro Ed. Prof'.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3576752376539006033", + "type": "Image", + "shortCode": "DGjLVYFJERR", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGjLVYFJERR/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481896407_18485585485052530_434092325039799309_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=T6lR9-KuVTIQ7kNvgFFNydq&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTAwNjAzMw%3D%3D.3-ccb7-5&oh=00_AYDjHrNDGGtXqrDm00Z5cmikcDDW6r6vCcuOcK6HpKRLCg&oe=67C5810B&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 26, 2025. May be a black-and-white image of 4 people, suit, dinner jacket and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3576752376539199607", + "type": "Image", + "shortCode": "DGjLVYFJzh3", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGjLVYFJzh3/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481160795_18485585488052530_1539507575987271556_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=UeGqIKsxTeMQ7kNvgEvSnDc&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTE5OTYwNw%3D%3D.3-ccb7-5&oh=00_AYA_APTXyJ_EQcCqjiZ_MGTRxVeifJNMUz5N-CEkYRqUqw&oe=67C59DE1&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 26, 2025. May be an image of 2 people, dinner jacket, suit and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3576752376539245179", + "type": "Image", + "shortCode": "DGjLVYFJ-p7", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGjLVYFJ-p7/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481700662_18485585509052530_2638589110064831842_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=uJZG70AJoEsQ7kNvgFDKkab&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTI0NTE3OQ%3D%3D.3-ccb7-5&oh=00_AYBPk6MhKHVdtym50vJfKxzeiMvpcwkM8k1z7JfjZSFwNg&oe=67C592D9&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 26, 2025.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3576752376530832613", + "type": "Image", + "shortCode": "DGjLVYEp4zl", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGjLVYEp4zl/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481701725_18485585497052530_3587554920053704889_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7u1fTHIwl-kQ7kNvgEtbPyO&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzMDgzMjYxMw%3D%3D.3-ccb7-5&oh=00_AYCgg5aHMYa99aDXCtUXBz_t4kW4O0-uTbU1kmrQyV8kDw&oe=67C595D4&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 26, 2025. May be an image of 10 people, people standing, suit, office and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + } + ], + "locationName": "U-ERRE Universidad Regiomontana", + "locationId": "1954214947989485", + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3576105336452698938", + "type": "Sidecar", + "shortCode": "DGg4NtCpFM6", + "caption": "Si en tu calle hay luminarias apagadas, ¡avísanos! Servicios Públicos trabaja todos los días, las 24 horas, para que nuestra ciudad esté iluminada y segura 💡.\n\nLlama al 072 o envíanos tu reporte por redes sociales y resolvemos.\n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGg4NtCpFM6/", + "commentsCount": 60, + "dimensionsHeight": 937, + "dimensionsWidth": 750, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481496181_18485412331052530_741626016313520267_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=LgbMZ-DBxOkQ7kNvgEqtJ5r&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCRahWthB8cOAss722myUNpekmNvpGV0mnuXnLmt_-EXg&oe=67C5AC62&_nc_sid=8b3546", + "images": [ + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481496181_18485412331052530_741626016313520267_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=LgbMZ-DBxOkQ7kNvgEqtJ5r&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCRahWthB8cOAss722myUNpekmNvpGV0mnuXnLmt_-EXg&oe=67C5AC62&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481580721_18485412301052530_8820974216780361429_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7VIqbtahrhcQ7kNvgE06mTv&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NjEwNTMyNzA1NzYxNTU2MQ%3D%3D.3-ccb7-5&oh=00_AYB-jeXVbi2ICbSAsQBZOgMQPYAJ8v219T6xwYrSaJqFBQ&oe=67C5A2B5&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481757909_18485412310052530_6700093297804895280_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=tlbOqZPUZ94Q7kNvgFPCbWU&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NjEwNTMyNjk5MDQyNTM0Ng%3D%3D.3-ccb7-5&oh=00_AYB-dVNijzmgaglbD--Xu3kmP_Ns9qmxNax0N4dgyYoiSw&oe=67C57955&_nc_sid=8b3546" + ], + "alt": null, + "likesCount": 290, + "timestamp": "2025-02-25T23:09:58.000Z", + "childPosts": [ + { + "id": "3576105163865591971", + "type": "Video", + "shortCode": "DGg4LMTphCj", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGg4LMTphCj/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 937, + "dimensionsWidth": 750, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481496181_18485412331052530_741626016313520267_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=LgbMZ-DBxOkQ7kNvgEqtJ5r&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCRahWthB8cOAss722myUNpekmNvpGV0mnuXnLmt_-EXg&oe=67C5AC62&_nc_sid=8b3546", + "images": [], + "videoUrl": "https://scontent-iad3-2.cdninstagram.com/o1/v/t16/f2/m367/AQPQ4r_kCZ59yx27bg4PSChQdME4lNBEzb7UWfNUow5iNrnD55OlFFR4EJQZ7V2IfuItrtt0Nx1i2YynDyFVHgnjx_0Zc9iC0j9jT88.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uY2Fyb3VzZWxfaXRlbS5jMi4xMDgwLmJhc2VsaW5lIn0&_nc_cat=105&vs=1706388450292559_2664043447&_nc_vs=HBksFQIYQGlnX2VwaGVtZXJhbC80MjREQTkwRjRBNjIxQURDNDcxOUI2M0IwQUJDRDlBNV92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dDZ011UndyZ0J2anJ3MEVBQUhPSWhCLTZzOThia1lMQUFBRhUCAsgBACgAGAAbABUAACbO%2FefG2LCVQBUCKAJDMywXQC4AAAAAAAAYFmRhc2hfYmFzZWxpbmVfMTA4MHBfdjERAHXuBwA%3D&ccb=9-4&oh=00_AYC3ryYMKSjwTVFam4C9uqVPlc-K3e7JODbsleRTBVQeEg&oe=67C1855C&_nc_sid=8b3546", + "alt": null, + "likesCount": null, + "videoViewCount": 2123, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3576105327057615561", + "type": "Image", + "shortCode": "DGg4NkSprrJ", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGg4NkSprrJ/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1350, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481580721_18485412301052530_8820974216780361429_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7VIqbtahrhcQ7kNvgE06mTv&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NjEwNTMyNzA1NzYxNTU2MQ%3D%3D.3-ccb7-5&oh=00_AYB-jeXVbi2ICbSAsQBZOgMQPYAJ8v219T6xwYrSaJqFBQ&oe=67C5A2B5&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 25, 2025. May be a Twitter screenshot of text that says 'Haz tu reporte vía redes sociales ο al 072'.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3576105326990425346", + "type": "Image", + "shortCode": "DGg4NkOpX0C", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGg4NkOpX0C/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1350, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481757909_18485412310052530_6700093297804895280_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=tlbOqZPUZ94Q7kNvgFPCbWU&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NjEwNTMyNjk5MDQyNTM0Ng%3D%3D.3-ccb7-5&oh=00_AYB-dVNijzmgaglbD--Xu3kmP_Ns9qmxNax0N4dgyYoiSw&oe=67C57955&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 25, 2025. May be an image of poster and text that says 'La Secretaría aría de Servicios Públicos trabaja de día y de noche para que Monterrey esté iluminado. ADRIÁN DE LA LA GARZA Alcalde de Monterrey'.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + } + ], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575920731370056578", + "type": "Video", + "shortCode": "DGgOPWKRGuC", + "caption": "Esta es la historia de Capi.\n\nFue abandonado, sin nadie que lo cuidara, pero su historia no terminó ahí. Fue rescatado por el Centro de Bienestar Animal, donde recibió cuidados, cariño y una segunda oportunidad.\n\nAsí como Capi, muchos perros y gatos llegan a este refugio en busca de una nueva oportunidad. Aquí son atendidos con amor hasta encontrar la familia que siempre merecieron.\n\nHoy, Capi ya está en su nuevo hogar, con su familia, recibiendo amor que deja huella. 🐾", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGgOPWKRGuC/", + "commentsCount": 52, + "dimensionsHeight": 1917, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-15/481417553_17887288152209277_5059280715899732461_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=109&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=bTX3irZPj_MQ7kNvgFf5M-_&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCCCFr1Llistr8BoAQyVtD59Z7eo2nPDPJqCvshvjAH8Q&oe=67C59138&_nc_sid=8b3546", + "images": [], + "videoUrl": "https://scontent-iad3-1.cdninstagram.com/o1/v/t16/f2/m86/AQP6BVuMrkzBHQuR_k90tG8eEs8npUo-aTTR12sAj0z9y91lyhGla9Rz0GvGJK73UYjuqeXOuWrkasbI49d8HI_cMk6Vgd-8o42Z_Jg.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uY2xpcHMuYzIuNzIwLmJhc2VsaW5lIn0&_nc_cat=104&vs=1771757866889879_818784534&_nc_vs=HBksFQIYUmlnX3hwdl9yZWVsc19wZXJtYW5lbnRfc3JfcHJvZC81RTQ3RkREMURFRDgxMDU0NTFBQTQyNTJCM0ZGN0M5NV92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dHWGFzaHduOVl5MVFqRUVBRVhJQUVibG9JOTlicV9FQUFBRhUCAsgBACgAGAAbABUAACaU1crr3az7QBUCKAJDMywXQFPMzMzMzM0YEmRhc2hfYmFzZWxpbmVfMV92MREAdf4HAA%3D%3D&ccb=9-4&oh=00_AYDdttqkXywlLEhOR6PnhiUPBcY3yjn2LWg-PxEaOVW7NA&oe=67C1B1CE&_nc_sid=8b3546", + "alt": null, + "likesCount": 644, + "videoViewCount": 3528, + "timestamp": "2025-02-25T17:08:13.000Z", + "childPosts": [], + "locationName": "Parque España, Monterrey", + "locationId": "320083772038467", + "ownerUsername": "gabyoyervides_", + "ownerId": "66397953276", + "productType": "clips", + "taggedUsers": [ + { + "full_name": "Adrián de la Garza", + "id": "1483444529", + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7qU3J4SFzmsQ7kNvgFA1d_N&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBwX2Z48Y05vz3DmEeO9hDWESNvQQdR94D2bHmCXmUeUA&oe=67C5ACA7&_nc_sid=8b3546", + "username": "adriandelagarzas" + } + ] + }, + { + "id": "3575403831769863067", + "type": "Sidecar", + "shortCode": "DGeYtd5NpOb", + "caption": "Esta tarde tuve la grata visita en el Palacio Municipal del Cónsul General de España, Vicente J. Mas Taladriz, con quien platicamos sobre inversiones, innovación y desarrollo en sectores clave como la tecnología, la industria automotriz y las energías renovables.\n\nMonterrey sigue consolidándose como un punto estratégico para la inversión extranjera y este tipo de encuentros nos ayudan a fortalecer los lazos de cooperación. \n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGeYtd5NpOb/", + "commentsCount": 30, + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481658267_18485241997052530_2711310963634757696_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=0j8h1nwFnlwQ7kNvgF9kcS7&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTQwMzgyNTM5NDU3ODE0Ng%3D%3D.3-ccb7-5&oh=00_AYBHVANhLkdTWhn8TImooTBOlxW7x8F7wZw9GXrGMWMZ5g&oe=67C58EFA&_nc_sid=8b3546", + "images": [ + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481658267_18485241997052530_2711310963634757696_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=0j8h1nwFnlwQ7kNvgF9kcS7&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTQwMzgyNTM5NDU3ODE0Ng%3D%3D.3-ccb7-5&oh=00_AYBHVANhLkdTWhn8TImooTBOlxW7x8F7wZw9GXrGMWMZ5g&oe=67C58EFA&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481580382_18485242006052530_1951527192361425823_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=wpJ1QOmQP90Q7kNvgEfKC8O&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTQwMzgyNTM5NDQyODIyNg%3D%3D.3-ccb7-5&oh=00_AYC8jzEMkv942kALk0tYnZtXAskIbct85X7Kj-m5JuM8oQ&oe=67C5909E&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481867426_18485242015052530_8491007479040714172_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=ugO_ewLbxmgQ7kNvgG6Z10g&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTQwMzgyNTM5NDQ3MzY1NA%3D%3D.3-ccb7-5&oh=00_AYDZPb9otbtwqfeF9Nrkec7pjt29sNbeLKRfi76QHH7g7A&oe=67C59B6C&_nc_sid=8b3546" + ], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 2 people, office and text.", + "likesCount": 397, + "timestamp": "2025-02-24T23:56:12.000Z", + "childPosts": [ + { + "id": "3575403825394578146", + "type": "Image", + "shortCode": "DGeYtX9N3Li", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGeYtX9N3Li/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481658267_18485241997052530_2711310963634757696_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=0j8h1nwFnlwQ7kNvgF9kcS7&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTQwMzgyNTM5NDU3ODE0Ng%3D%3D.3-ccb7-5&oh=00_AYBHVANhLkdTWhn8TImooTBOlxW7x8F7wZw9GXrGMWMZ5g&oe=67C58EFA&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 2 people, office and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575403825394428226", + "type": "Image", + "shortCode": "DGeYtX9NSlC", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGeYtX9NSlC/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481580382_18485242006052530_1951527192361425823_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=wpJ1QOmQP90Q7kNvgEfKC8O&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTQwMzgyNTM5NDQyODIyNg%3D%3D.3-ccb7-5&oh=00_AYC8jzEMkv942kALk0tYnZtXAskIbct85X7Kj-m5JuM8oQ&oe=67C5909E&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 5 people and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575403825394473654", + "type": "Image", + "shortCode": "DGeYtX9Ndq2", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGeYtX9Ndq2/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481867426_18485242015052530_8491007479040714172_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=ugO_ewLbxmgQ7kNvgG6Z10g&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTQwMzgyNTM5NDQ3MzY1NA%3D%3D.3-ccb7-5&oh=00_AYDZPb9otbtwqfeF9Nrkec7pjt29sNbeLKRfi76QHH7g7A&oe=67C59B6C&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 2 people, flag and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + } + ], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575305859932618182", + "type": "Sidecar", + "shortCode": "DGeCbygp_3G", + "caption": "Junto a mi esposa @gabyoyervides_ hoy conocimos de cerca el gran trabajo de @destellosdeluzabp, una asociación que cambia vidas ayudando a personas con discapacidad visual.\nVamos a seguir trabajando de la mano para que más regiomontanos tengan acceso a atención oftalmológica y educación inclusiva.", + "hashtags": [], + "mentions": [ + "gabyoyervides_", + "destellosdeluzabp," + ], + "url": "https://www.instagram.com/p/DGeCbygp_3G/", + "commentsCount": 29, + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481243005_18485219815052530_4428621552175351336_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=2bdhxD2yY2kQ7kNvgFnRKcI&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDkyMzAyODI4Nw%3D%3D.3-ccb7-5&oh=00_AYASDO4_8TkmfIc7uV-HFvRnsWfooL2c1DjEABT6p4i-NA&oe=67C59B22&_nc_sid=8b3546", + "images": [ + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481243005_18485219815052530_4428621552175351336_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=2bdhxD2yY2kQ7kNvgFnRKcI&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDkyMzAyODI4Nw%3D%3D.3-ccb7-5&oh=00_AYASDO4_8TkmfIc7uV-HFvRnsWfooL2c1DjEABT6p4i-NA&oe=67C59B22&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481314095_18485219800052530_6023066712220008104_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=rXv61-nE_MAQ7kNvgGOu3Iz&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDc4ODg2MjE4Mg%3D%3D.3-ccb7-5&oh=00_AYC2_zYYVVGBCBy5BtTFebmMHJpjF_fe8dsrMXcdyXi11g&oe=67C5AB32&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481395039_18485219818052530_7408749514796137990_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Z4M1W3pBzI4Q7kNvgH-JTU6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDk3MzUwMTM5Ng%3D%3D.3-ccb7-5&oh=00_AYBZNE7pv3eWj44yG3wbE5iHS2S71mJ3JJC-lJouIxQoQg&oe=67C5ADCA&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481742547_18485219782052530_7960299857388226454_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=EEVmuvzZLzwQ7kNvgH5b9ak&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDc5NzI3MTkwOQ%3D%3D.3-ccb7-5&oh=00_AYAn__a8irWX6aeGireTc5JnqwGmtGeASPy4asFsRtFuFQ&oe=67C57D5A&_nc_sid=8b3546" + ], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 6 people, people studying, people standing, office and text.", + "likesCount": 254, + "timestamp": "2025-02-24T20:41:33.000Z", + "childPosts": [ + { + "id": "3575305850923028287", + "type": "Image", + "shortCode": "DGeCbqHpI8_", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGeCbqHpI8_/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481243005_18485219815052530_4428621552175351336_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=2bdhxD2yY2kQ7kNvgFnRKcI&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDkyMzAyODI4Nw%3D%3D.3-ccb7-5&oh=00_AYASDO4_8TkmfIc7uV-HFvRnsWfooL2c1DjEABT6p4i-NA&oe=67C59B22&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 6 people, people studying, people standing, office and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575305850788862182", + "type": "Image", + "shortCode": "DGeCbp_pVjm", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGeCbp_pVjm/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481314095_18485219800052530_6023066712220008104_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=rXv61-nE_MAQ7kNvgGOu3Iz&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDc4ODg2MjE4Mg%3D%3D.3-ccb7-5&oh=00_AYC2_zYYVVGBCBy5BtTFebmMHJpjF_fe8dsrMXcdyXi11g&oe=67C5AB32&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 1 person, childrens toy, computer keyboard and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575305850973501396", + "type": "Image", + "shortCode": "DGeCbqKprfU", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGeCbqKprfU/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481395039_18485219818052530_7408749514796137990_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Z4M1W3pBzI4Q7kNvgH-JTU6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDk3MzUwMTM5Ng%3D%3D.3-ccb7-5&oh=00_AYBZNE7pv3eWj44yG3wbE5iHS2S71mJ3JJC-lJouIxQoQg&oe=67C5ADCA&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of ‎4 people, eyewear, printer, cash machine, screen, microscope, hospital, office and ‎text that says '‎ORADO JERPO MEDICO CON DESTELLOS DE LUZ Partes del εςό ojo 2okoDgc0om Esclenótica Carmas Pupila Iris- Vitreo Retina Criatatino Mue cliiares Man ilum Nervio optico Pestañas Reali form חמישיש 내영 ស្នាំ។ Prote delos delos gafas Conducto lagrimal Descan dur durant‎'‎‎.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575305850797271909", + "type": "Image", + "shortCode": "DGeCbqAJatl", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGeCbqAJatl/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481742547_18485219782052530_7960299857388226454_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=EEVmuvzZLzwQ7kNvgH5b9ak&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDc5NzI3MTkwOQ%3D%3D.3-ccb7-5&oh=00_AYAn__a8irWX6aeGireTc5JnqwGmtGeASPy4asFsRtFuFQ&oe=67C57D5A&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 5 people, people studying, people standing, newsroom, office and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + } + ], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575157746601071249", + "type": "Video", + "shortCode": "DGdgwdOJJKR", + "caption": "¡En Monterrey se Recicla y se Resuelve!\n\nMe dio mucho gusto ver y saludar a familias completas poniendo su granito de arena para nuestra ciudad. Los invitamos a que sigamos reciclando en los puntos fijos que tenemos: Parque España y Parque Canoas. \n\n¡Aquí se resuelve!", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGdgwdOJJKR/", + "commentsCount": 40, + "dimensionsHeight": 850, + "dimensionsWidth": 480, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481759656_18485179516052530_2758943356094999050_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=kElsX71VZZIQ7kNvgGrjEp_&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYAfBSMOwkf9HXqPW9UHm1Nod1PK31ewIm2lI4AdU9hLwA&oe=67C5917C&_nc_sid=8b3546", + "images": [], + "videoUrl": "https://scontent-iad3-1.cdninstagram.com/o1/v/t16/f2/m86/AQPtZ7qkIR11NCpplySxkIsU3smIfsgiFTFuRJQwUWJia9CkIqBc-KUn6cys1wNVDbZGo3cuQYrD5pKJOb93EoU2Ap6W_2hwYcpTuek.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uY2xpcHMuYzIuNzIwLmJhc2VsaW5lIn0&_nc_cat=101&vs=599855026257119_2400007891&_nc_vs=HBksFQIYUmlnX3hwdl9yZWVsc19wZXJtYW5lbnRfc3JfcHJvZC8wQzQwQzJFMzBCNkJGRDQ2MkI5ODgzMzQ3OUZFNDdCNF92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dQLUZzUnhSTGFzdXN5VUNBTXZrN2Z5QUtXTWhicV9FQUFBRhUCAsgBACgAGAAbABUAACb837jaqsC%2BPxUCKAJDMywXQFDAAAAAAAAYEmRhc2hfYmFzZWxpbmVfMV92MREAdf4HAA%3D%3D&ccb=9-4&oh=00_AYABpwn40sLbXCOH1l0Q7FxcWACtRsyqKF6Ooxn-J2fuuQ&oe=67C19055&_nc_sid=8b3546", + "alt": null, + "likesCount": 226, + "videoViewCount": 2337, + "timestamp": "2025-02-24T15:49:19.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "clips" + }, + { + "id": "3574711209371873982", + "type": "Video", + "shortCode": "DGb7OfBN7K-", + "caption": "Gracias a quienes abren su corazón y les dan una segunda oportunidad a estas mascotas.\nAdoptar es un acto de amor que deja huella❤️🐾\n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGb7OfBN7K-/", + "commentsCount": 45, + "dimensionsHeight": 1136, + "dimensionsWidth": 640, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481803383_1266972577732201_333100246445106975_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=2N4LBBs4xkoQ7kNvgHv_6s0&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYDHQO8Ovdvxs33PT5a-bUe0IRv0btsixrYBJMSdEM5l6w&oe=67C5A637&_nc_sid=8b3546", + "images": [], + "videoUrl": "https://scontent-iad3-2.cdninstagram.com/o1/v/t16/f2/m86/AQOMb3e5yBL-Whasd1HzjTOHUqCJ6H4TQbj6zZd2Obr8T3u61WJOE6WNFg5wh52B6GEoXWk0KE49fo4gbChjUPvE-HsfkdIoGC_FkDs.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uY2xpcHMuYzIuNzIwLmJhc2VsaW5lIn0&_nc_cat=103&vs=1650765472991661_3043598127&_nc_vs=HBksFQIYUmlnX3hwdl9yZWVsc19wZXJtYW5lbnRfc3JfcHJvZC8zRTQ2OTFCNTBFQUNFNjFDQzFBMjI1MDBGQ0U3N0I5RV92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dMR05vQndWTGN3X1lMY0VBR2lMdW1QcHJNY2RicV9FQUFBRhUCAsgBACgAGAAbABUAACaC5eiXiMS2PxUCKAJDMywXQEhzMzMzMzMYEmRhc2hfYmFzZWxpbmVfMV92MREAdf4HAA%3D%3D&ccb=9-4&oh=00_AYC2eNEX1RIwRu5fwau_ko6DOkMpQUFg8zdPR8EzO2Uf0g&oe=67C1BB72&_nc_sid=8b3546", + "alt": null, + "likesCount": 337, + "videoViewCount": 2578, + "timestamp": "2025-02-24T01:00:48.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "clips" + }, + { + "id": "3574625916672107235", + "type": "Sidecar", + "shortCode": "DGbn1UAJWLj", + "caption": "Este domingo mi esposa @gabyoyervides_ y yo vivimos una mañana llena de alegría en la Feria de Adopciones “Amor que deja huella”. \n\nGracias a las familias que adoptaron a 22 perritos y 5 gatos que hoy dormirán en un hogar llenos de cariño.\n\nAdoptar es cambiar una vida y, a la vez, llenar la nuestra de amor incondicional.\n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [ + "gabyoyervides_" + ], + "url": "https://www.instagram.com/p/DGbn1UAJWLj/", + "commentsCount": 86, + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481598628_18485050462052530_6943890238376007619_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=I9jwb-13R0QQ7kNvgH0moIC&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQzMTcwNjc5NA%3D%3D.3-ccb7-5&oh=00_AYDD57d0xNUY2NUF0dlpu4cB93TZJKM6mscUK5T9RI5t2g&oe=67C59830&_nc_sid=8b3546", + "images": [ + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481598628_18485050462052530_6943890238376007619_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=I9jwb-13R0QQ7kNvgH0moIC&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQzMTcwNjc5NA%3D%3D.3-ccb7-5&oh=00_AYDD57d0xNUY2NUF0dlpu4cB93TZJKM6mscUK5T9RI5t2g&oe=67C59830&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481179980_18485050471052530_4789787889477849374_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=rTIExQ2ujxkQ7kNvgHdArof&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQzMTcwNzIyMg%3D%3D.3-ccb7-5&oh=00_AYAfxJne4j0-OSjgAhb0INbt7MWy-j6QpzOMY6ilBOAgZA&oe=67C57B54&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481036850_18485050480052530_4263919696116033175_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=d5ESLIJu46IQ7kNvgE7lcPN&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM4OTk1NTY2NQ%3D%3D.3-ccb7-5&oh=00_AYCEr1TKW1lTq2Oaqso2YHSWD7RCXg9J9n22kkXjAuPexw&oe=67C585C2&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480868090_18485050489052530_9177445398933850852_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=YX0aCwViBQMQ7kNvgHnvLnj&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODM2NDU3MQ%3D%3D.3-ccb7-5&oh=00_AYBKOCIeazRGN1y_OrIztNy6ecypSr0mzhTFdYI2-HY-lA&oe=67C5AB89&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481386245_18485050498052530_8146167078313545440_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=bO3YIfa3518Q7kNvgGOPNBu&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQ0ODQ2NjIwNg%3D%3D.3-ccb7-5&oh=00_AYDIFhOojcVTsTbg4D_QHj1ITEdWG00DGnDsWWH3Vc4pOg&oe=67C5AE42&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481232883_18485050507052530_4811447304630849245_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=OIoSmYr0bRwQ7kNvgHmBDHW&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODM3MDgzNQ%3D%3D.3-ccb7-5&oh=00_AYDjxQeSjHSO--TOud6b_5M444GyxuN_tSzIxA3ZT9OGEw&oe=67C5A248&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481338978_18485050516052530_2521269563541274121_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=f1O-YNhao_EQ7kNvgGM20mu&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODI3NzU3OA%3D%3D.3-ccb7-5&oh=00_AYDU8rmqeeY0ebSel7iPW4_yd62Df2xyrnJgSqbcokuP_w&oe=67C57B1C&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481038573_18485050525052530_3975497264038752483_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=kSDh72mg6QIQ7kNvgHy9mVR&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQ0ODY1MjIyMg%3D%3D.3-ccb7-5&oh=00_AYDyEkzFX997_3LB4tWty6pT-IVXUU6B4Wk78zcbPdFKIA&oe=67C59A99&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481052680_18485050534052530_4887264455262851473_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=4YEAiPVCVxoQ7kNvgFjU9GM&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODE4ODE0MQ%3D%3D.3-ccb7-5&oh=00_AYAtkmHLCat-INhkpgMC7gaoRJO3L_NXKQhepWAjBT019Q&oe=67C582EA&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481015818_18485050543052530_6626002521994330049_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7dUocOjrI5oQ7kNvgE-AG68&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODMyODU1OQ%3D%3D.3-ccb7-5&oh=00_AYAgjRRmadmUWogDGD2EH_DSTl4R1nA1-WX8rmIdJWk2vg&oe=67C596E0&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481010469_18485050552052530_1032545387524868505_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=vKPDHTOGhH4Q7kNvgGVULJH&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM4MTQ0NzI0Mg%3D%3D.3-ccb7-5&oh=00_AYDmsAkCyAB4JiMx1fUyHH07YuUsclXP0QtoV_YNhw1i7Q&oe=67C592EF&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481237231_18485050561052530_5902898244339317184_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=8AqKlSnpLpUQ7kNvgE8TltX&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODM3NDgyMg%3D%3D.3-ccb7-5&oh=00_AYD-4SmXh9q1bZb2Z-BTjXUgMgG23ONerD75WLK2dNQaxQ&oe=67C593BD&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481311990_18485050570052530_4621891136551024473_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=8Y6W8crQxfAQ7kNvgHhkji6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQzMTkyODM3NQ%3D%3D.3-ccb7-5&oh=00_AYBXuyWntA4Jm7Nlvt_mNXVF4h665s7Yx7ljl-L9IcCqhQ&oe=67C589DE&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481056039_18485050579052530_5718361635390257069_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=ZuJ1nEirc9gQ7kNvgEgtn_T&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQ0ODQ1NDIyOQ%3D%3D.3-ccb7-5&oh=00_AYB2N9Lj2TR1gJ6Rx-s4l5k9L2gXZ1I5kmg3hb1gAkaiLA&oe=67C598CA&_nc_sid=8b3546" + ], + "alt": "Photo shared by Adrián de la Garza on February 23, 2025 tagging @gabyoyervides_. May be an image of 2 people and text.", + "likesCount": 1097, + "timestamp": "2025-02-23T22:10:37.000Z", + "childPosts": [ + { + "id": "3574625900431706794", + "type": "Image", + "shortCode": "DGbn1E4JIqq", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E4JIqq/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481598628_18485050462052530_6943890238376007619_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=I9jwb-13R0QQ7kNvgH0moIC&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQzMTcwNjc5NA%3D%3D.3-ccb7-5&oh=00_AYDD57d0xNUY2NUF0dlpu4cB93TZJKM6mscUK5T9RI5t2g&oe=67C59830&_nc_sid=8b3546", + "images": [], + "alt": "Photo shared by Adrián de la Garza on February 23, 2025 tagging @gabyoyervides_. May be an image of 2 people and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "taggedUsers": [ + { + "full_name": "Gaby Oyervides", + "id": "66397953276", + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/462483519_1201551891079625_5500174295318152454_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=105&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=44rsWGmzxpoQ7kNvgENMYq6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBFfhFavBUqpzo4ghlMpuB5SMPEBlcVoHS7981IKPxXTg&oe=67C5A89C&_nc_sid=8b3546", + "username": "gabyoyervides_" + } + ] + }, + { + "id": "3574625900431707222", + "type": "Image", + "shortCode": "DGbn1E4JIxW", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E4JIxW/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481179980_18485050471052530_4789787889477849374_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=rTIExQ2ujxkQ7kNvgHdArof&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQzMTcwNzIyMg%3D%3D.3-ccb7-5&oh=00_AYAfxJne4j0-OSjgAhb0INbt7MWy-j6QpzOMY6ilBOAgZA&oe=67C57B54&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of dog and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900389955665", + "type": "Image", + "shortCode": "DGbn1E1p3hR", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E1p3hR/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1350, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481036850_18485050480052530_4263919696116033175_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=d5ESLIJu46IQ7kNvgE7lcPN&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM4OTk1NTY2NQ%3D%3D.3-ccb7-5&oh=00_AYCEr1TKW1lTq2Oaqso2YHSWD7RCXg9J9n22kkXjAuPexw&oe=67C585C2&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 1 person, collie and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900398364571", + "type": "Image", + "shortCode": "DGbn1E2J8eb", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E2J8eb/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480868090_18485050489052530_9177445398933850852_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=YX0aCwViBQMQ7kNvgHnvLnj&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODM2NDU3MQ%3D%3D.3-ccb7-5&oh=00_AYBKOCIeazRGN1y_OrIztNy6ecypSr0mzhTFdYI2-HY-lA&oe=67C5AB89&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 1 person, chihuahua, collie and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900448466206", + "type": "Image", + "shortCode": "DGbn1E5JEUe", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E5JEUe/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481386245_18485050498052530_8146167078313545440_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=bO3YIfa3518Q7kNvgGOPNBu&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQ0ODQ2NjIwNg%3D%3D.3-ccb7-5&oh=00_AYDIFhOojcVTsTbg4D_QHj1ITEdWG00DGnDsWWH3Vc4pOg&oe=67C5AE42&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of corgi, collie, bandanna and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900398370835", + "type": "Image", + "shortCode": "DGbn1E2J-AT", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E2J-AT/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481232883_18485050507052530_4811447304630849245_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=OIoSmYr0bRwQ7kNvgHmBDHW&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODM3MDgzNQ%3D%3D.3-ccb7-5&oh=00_AYDjxQeSjHSO--TOud6b_5M444GyxuN_tSzIxA3ZT9OGEw&oe=67C5A248&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 2 people and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900398277578", + "type": "Image", + "shortCode": "DGbn1E2JnPK", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E2JnPK/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481338978_18485050516052530_2521269563541274121_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=f1O-YNhao_EQ7kNvgGM20mu&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODI3NzU3OA%3D%3D.3-ccb7-5&oh=00_AYDU8rmqeeY0ebSel7iPW4_yd62Df2xyrnJgSqbcokuP_w&oe=67C57B1C&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of chihuahua, bandanna and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900448652222", + "type": "Image", + "shortCode": "DGbn1E5Jxu-", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E5Jxu-/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481038573_18485050525052530_3975497264038752483_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=kSDh72mg6QIQ7kNvgHy9mVR&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQ0ODY1MjIyMg%3D%3D.3-ccb7-5&oh=00_AYDyEkzFX997_3LB4tWty6pT-IVXUU6B4Wk78zcbPdFKIA&oe=67C59A99&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of mastiff, collie, bandanna and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900398188141", + "type": "Image", + "shortCode": "DGbn1E2JRZt", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E2JRZt/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481052680_18485050534052530_4887264455262851473_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=4YEAiPVCVxoQ7kNvgFjU9GM&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODE4ODE0MQ%3D%3D.3-ccb7-5&oh=00_AYAtkmHLCat-INhkpgMC7gaoRJO3L_NXKQhepWAjBT019Q&oe=67C582EA&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 2 people, dog and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900398328559", + "type": "Image", + "shortCode": "DGbn1E2Jzrv", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E2Jzrv/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481015818_18485050543052530_6626002521994330049_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7dUocOjrI5oQ7kNvgE-AG68&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODMyODU1OQ%3D%3D.3-ccb7-5&oh=00_AYAgjRRmadmUWogDGD2EH_DSTl4R1nA1-WX8rmIdJWk2vg&oe=67C596E0&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 1 person, dog and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900381447242", + "type": "Image", + "shortCode": "DGbn1E1JaRK", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E1JaRK/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481010469_18485050552052530_1032545387524868505_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=vKPDHTOGhH4Q7kNvgGVULJH&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM4MTQ0NzI0Mg%3D%3D.3-ccb7-5&oh=00_AYDmsAkCyAB4JiMx1fUyHH07YuUsclXP0QtoV_YNhw1i7Q&oe=67C592EF&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 7 people, dog and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900398374822", + "type": "Image", + "shortCode": "DGbn1E2J--m", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E2J--m/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481237231_18485050561052530_5902898244339317184_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=8AqKlSnpLpUQ7kNvgE8TltX&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODM3NDgyMg%3D%3D.3-ccb7-5&oh=00_AYD-4SmXh9q1bZb2Z-BTjXUgMgG23ONerD75WLK2dNQaxQ&oe=67C593BD&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 1 person, collie, petfood and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900431928375", + "type": "Image", + "shortCode": "DGbn1E4J-w3", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E4J-w3/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481311990_18485050570052530_4621891136551024473_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=8Y6W8crQxfAQ7kNvgHhkji6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQzMTkyODM3NQ%3D%3D.3-ccb7-5&oh=00_AYBXuyWntA4Jm7Nlvt_mNXVF4h665s7Yx7ljl-L9IcCqhQ&oe=67C589DE&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 1 person, collie, corgi and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900448454229", + "type": "Image", + "shortCode": "DGbn1E5JBZV", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E5JBZV/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481056039_18485050579052530_5718361635390257069_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=ZuJ1nEirc9gQ7kNvgEgtn_T&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQ0ODQ1NDIyOQ%3D%3D.3-ccb7-5&oh=00_AYB2N9Lj2TR1gJ6Rx-s4l5k9L2gXZ1I5kmg3hb1gAkaiLA&oe=67C598CA&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 3 people, crowd and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + } + ], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "taggedUsers": [ + { + "full_name": "Gaby Oyervides", + "id": "66397953276", + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/462483519_1201551891079625_5500174295318152454_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=105&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=44rsWGmzxpoQ7kNvgENMYq6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBFfhFavBUqpzo4ghlMpuB5SMPEBlcVoHS7981IKPxXTg&oe=67C5A89C&_nc_sid=8b3546", + "username": "gabyoyervides_" + } + ] + }, + { + "id": "3573893024685937859", + "type": "Sidecar", + "shortCode": "DGZBMVJptTD", + "caption": "¡En Monterrey se recicla y se resuelve!♻️\n\nEste sábado sumamos esfuerzos con empresas locales y vecinos de la zona poniente, recolectando toneladas de materiales reciclables.\n\nSeguimos trabajando por un Monterrey más limpio y sustentable. \nVisita nuestros puntos fijos de reciclaje.\n📍 Parque Tucán \n📍 Parque España\n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMVJptTD/", + "commentsCount": 35, + "dimensionsHeight": 717, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481209676_18484859230052530_1587082404481791868_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=nDc0Y_h05y4Q7kNvgEs1HUi&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzQ1MjQ4Mg%3D%3D.3-ccb7-5&oh=00_AYBauhiag9AlOM3RLA1vsgWus5QEXgIiDjx8s26WbTyP8g&oe=67C5B0B6&_nc_sid=8b3546", + "images": [ + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481209676_18484859230052530_1587082404481791868_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=nDc0Y_h05y4Q7kNvgEs1HUi&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzQ1MjQ4Mg%3D%3D.3-ccb7-5&oh=00_AYBauhiag9AlOM3RLA1vsgWus5QEXgIiDjx8s26WbTyP8g&oe=67C5B0B6&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481011442_18484859158052530_7997298743970503748_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Av-C9YwPtMoQ7kNvgHjiWER&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTE5NjkyNjA0Ng%3D%3D.3-ccb7-5&oh=00_AYA5_ktS3oNl4oVOzkkPHS_ggJroyk5WBymfcAHRq6vv1Q&oe=67C5A26D&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480993782_18484859173052530_5548552597408632076_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=AHWrlkHK47UQ7kNvgHxnKwo&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIwNTQzMDA2OA%3D%3D.3-ccb7-5&oh=00_AYCwiib26y-h0_F9iJZlXYglyOaPyFxUPqCYSICQVzr_EQ&oe=67C58DAC&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481224022_18484859176052530_613921378657991458_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=gFFSxb1ta3AQ7kNvgFqxIQO&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzM2NTI3OA%3D%3D.3-ccb7-5&oh=00_AYBxXt8NomCXOc8nBInEkw2_gP03-EPTn5JkfjfvnPJYfQ&oe=67C59FAA&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481058596_18484859185052530_6222593389900532574_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=bPg6WSmb9JQQ7kNvgFNCM54&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI1NTcyNTcwMQ%3D%3D.3-ccb7-5&oh=00_AYDD8-I7_5V4qs3GkZ0e9A3CEeI9ShUn2xx0Wku8Fz9r2Q&oe=67C59518&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481005038_18484859221052530_7277507886567056117_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=HKUaR9ZcQwcQ7kNvgHzZOuu&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIwNTUwNzg4Ng%3D%3D.3-ccb7-5&oh=00_AYAkJ6P47rtsIJoyPjNtDhMcLdnDmZBjAGuGQLa-IDNPlw&oe=67C5AE32&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481025208_18484859206052530_8229519256527118304_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=5oug2TofT4IQ7kNvgGGIM52&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI1NTYyNzg0MQ%3D%3D.3-ccb7-5&oh=00_AYBAeHZScpnq4kJBzakH3RwlOQhlV0W3O_qP18hstLHwng&oe=67C57B4A&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481160160_18484859224052530_6677757374498163832_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Kx1tixMj6JUQ7kNvgFN6FYp&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIxMzgwNDMwMQ%3D%3D.3-ccb7-5&oh=00_AYD6jTZwVP-j4L2G0IfeakYtP6i0_ozaFl93LgH0QXBQfA&oe=67C5A8CD&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481757068_18484859239052530_7694872054091715576_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=s3u_AMY96CYQ7kNvgFCYhcR&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzI4NDIyMA%3D%3D.3-ccb7-5&oh=00_AYCstSJv8W1h6teJQkwjq2g4yjgRYzcuYZ-9379OibsdpA&oe=67C596A5&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481394608_18484859236052530_3558149599907401765_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=3MtJKn4LT-UQ7kNvgEYg2t6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIwNTQyOTMwNQ%3D%3D.3-ccb7-5&oh=00_AYCS-1n5PjOFCsCt3Jnng6Pk_RYNPrn1i1k5LmaCiAy4wQ&oe=67C57919&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481010738_18484859248052530_1612583066444476718_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=gMjd4I5eUkkQ7kNvgH8P1f0&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIxMzg4ODgxMQ%3D%3D.3-ccb7-5&oh=00_AYBy1noZZ4dGKUNYcLrihTyP_eGg14FjZNj5z76aZS4yqA&oe=67C5A176&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480879707_18484859257052530_8380999794156791663_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=nLLILkvDLwQQ7kNvgHwo2IB&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzQwNDQxMg%3D%3D.3-ccb7-5&oh=00_AYDJMK6B_-oegdfdkCv9ZDX_IXWTmAjNTdIvExFak0R81w&oe=67C59939&_nc_sid=8b3546" + ], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 4 people, people racing vehicles, plastic bag, garbage, road and text that says 'CIC BCC C MTY BO REI 279円 79'.", + "likesCount": 297, + "timestamp": "2025-02-22T21:54:30.000Z", + "childPosts": [ + { + "id": "3573893011247452482", + "type": "Image", + "shortCode": "DGZBMIop9FC", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMIop9FC/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 717, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481209676_18484859230052530_1587082404481791868_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=nDc0Y_h05y4Q7kNvgEs1HUi&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzQ1MjQ4Mg%3D%3D.3-ccb7-5&oh=00_AYBauhiag9AlOM3RLA1vsgWus5QEXgIiDjx8s26WbTyP8g&oe=67C5B0B6&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 4 people, people racing vehicles, plastic bag, garbage, road and text that says 'CIC BCC C MTY BO REI 279円 79'.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011196926046", + "type": "Image", + "shortCode": "DGZBMIlpNhe", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMIlpNhe/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481011442_18484859158052530_7997298743970503748_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Av-C9YwPtMoQ7kNvgHjiWER&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTE5NjkyNjA0Ng%3D%3D.3-ccb7-5&oh=00_AYA5_ktS3oNl4oVOzkkPHS_ggJroyk5WBymfcAHRq6vv1Q&oe=67C5A26D&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 5 people, people racing vehicles and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011205430068", + "type": "Image", + "shortCode": "DGZBMImJps0", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMImJps0/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480993782_18484859173052530_5548552597408632076_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=AHWrlkHK47UQ7kNvgHxnKwo&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIwNTQzMDA2OA%3D%3D.3-ccb7-5&oh=00_AYCwiib26y-h0_F9iJZlXYglyOaPyFxUPqCYSICQVzr_EQ&oe=67C58DAC&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 7 people, people standing and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011247365278", + "type": "Image", + "shortCode": "DGZBMIopnye", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMIopnye/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481224022_18484859176052530_613921378657991458_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=gFFSxb1ta3AQ7kNvgFqxIQO&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzM2NTI3OA%3D%3D.3-ccb7-5&oh=00_AYBxXt8NomCXOc8nBInEkw2_gP03-EPTn5JkfjfvnPJYfQ&oe=67C59FAA&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 4 people, people standing and text that says 'ALS Dark VE'.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011255725701", + "type": "Image", + "shortCode": "DGZBMIpJg6F", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMIpJg6F/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481058596_18484859185052530_6222593389900532574_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=bPg6WSmb9JQQ7kNvgFNCM54&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI1NTcyNTcwMQ%3D%3D.3-ccb7-5&oh=00_AYDD8-I7_5V4qs3GkZ0e9A3CEeI9ShUn2xx0Wku8Fz9r2Q&oe=67C59518&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 1 person, plastic bag, garbage and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011205507886", + "type": "Image", + "shortCode": "DGZBMImJ8su", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMImJ8su/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481005038_18484859221052530_7277507886567056117_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=HKUaR9ZcQwcQ7kNvgHzZOuu&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIwNTUwNzg4Ng%3D%3D.3-ccb7-5&oh=00_AYAkJ6P47rtsIJoyPjNtDhMcLdnDmZBjAGuGQLa-IDNPlw&oe=67C5AE32&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 5 people, people racing vehicles and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011255627841", + "type": "Image", + "shortCode": "DGZBMIpJJBB", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMIpJJBB/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481025208_18484859206052530_8229519256527118304_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=5oug2TofT4IQ7kNvgGGIM52&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI1NTYyNzg0MQ%3D%3D.3-ccb7-5&oh=00_AYBAeHZScpnq4kJBzakH3RwlOQhlV0W3O_qP18hstLHwng&oe=67C57B4A&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 7 people, minivan, windshield, wheel, sedan and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011213804301", + "type": "Image", + "shortCode": "DGZBMImpmMN", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMImpmMN/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481160160_18484859224052530_6677757374498163832_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Kx1tixMj6JUQ7kNvgFN6FYp&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIxMzgwNDMwMQ%3D%3D.3-ccb7-5&oh=00_AYD6jTZwVP-j4L2G0IfeakYtP6i0_ozaFl93LgH0QXBQfA&oe=67C5A8CD&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 3 people, people standing, newspaper, carton, road and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011247284220", + "type": "Image", + "shortCode": "DGZBMIopT_8", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMIopT_8/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481757068_18484859239052530_7694872054091715576_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=s3u_AMY96CYQ7kNvgFCYhcR&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzI4NDIyMA%3D%3D.3-ccb7-5&oh=00_AYCstSJv8W1h6teJQkwjq2g4yjgRYzcuYZ-9379OibsdpA&oe=67C596A5&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 4 people, speaker, camera, generator, telescope and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011205429305", + "type": "Image", + "shortCode": "DGZBMImJpg5", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMImJpg5/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481394608_18484859236052530_3558149599907401765_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=3MtJKn4LT-UQ7kNvgEYg2t6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIwNTQyOTMwNQ%3D%3D.3-ccb7-5&oh=00_AYCS-1n5PjOFCsCt3Jnng6Pk_RYNPrn1i1k5LmaCiAy4wQ&oe=67C57919&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of ‎1 person, garbage, carton, plastic bag and ‎text that says '‎خسو Este RECICLA RESUELVE | AQUI SE RESUELVE MTY un programa de MTY SOSTENIBLE‎'‎‎.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011213888811", + "type": "Image", + "shortCode": "DGZBMImp60r", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMImp60r/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481010738_18484859248052530_1612583066444476718_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=gMjd4I5eUkkQ7kNvgH8P1f0&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIxMzg4ODgxMQ%3D%3D.3-ccb7-5&oh=00_AYBy1noZZ4dGKUNYcLrihTyP_eGg14FjZNj5z76aZS4yqA&oe=67C5A176&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of ‎1 person, house plant, pitcher plant and ‎text that says '‎JELAVE RECICLA MANDE RECICLAJE RECICLA ده REGISTRO V ፈሃል SE‎'‎‎.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011247404412", + "type": "Image", + "shortCode": "DGZBMIopxV8", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMIopxV8/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480879707_18484859257052530_8380999794156791663_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=nLLILkvDLwQQ7kNvgHwo2IB&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzQwNDQxMg%3D%3D.3-ccb7-5&oh=00_AYDJMK6B_-oegdfdkCv9ZDX_IXWTmAjNTdIvExFak0R81w&oe=67C59939&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 3 people, garbage, petfood, trailer and text that says 'PAPEL ARTON RECICLA UTENS UTENSLIOS LIOS DECOCINA DE co SINA RECICLA PET 米 នល - NTY T リー RECICLA MTY ι RESUELVE \"\"\" n*ИT YNOSTENIR SOSTENIBLE'.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + } + ], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573206712391658984", + "type": "Sidecar", + "shortCode": "DGWlJLBJW3o", + "caption": "Hoy en Sesión Ordinaria de Cabildo aprobamos a las ganadoras del reconocimiento público “Mujer que Inspira 2025”, una medalla a ocho extraordinarias regiomontanas por su impacto en la ciencia, el arte, el emprendimiento y el compromiso social.\n\nLa entrega será en marzo en una Sesión Solemne. \n\nTambién aprobamos la convocatoria para la Medalla al Mérito Deportivo 2025, así que si conoces a alguien que haya dejado huella en el deporte, ¡postúlalo antes del 17 de abril! \n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGWlJLBJW3o/", + "commentsCount": 27, + "dimensionsHeight": 719, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481023355_18484679389052530_6140187690128957417_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=a1f72bmQ4uUQ7kNvgF77bu8&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI3MTU2MzM1Ng%3D%3D.3-ccb7-5&oh=00_AYC2-QtZtVs6m11dO1i8btXMsWbGr1N7jEspX_2qV9sYEg&oe=67C581F9&_nc_sid=8b3546", + "images": [ + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481023355_18484679389052530_6140187690128957417_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=a1f72bmQ4uUQ7kNvgF77bu8&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI3MTU2MzM1Ng%3D%3D.3-ccb7-5&oh=00_AYC2-QtZtVs6m11dO1i8btXMsWbGr1N7jEspX_2qV9sYEg&oe=67C581F9&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481021674_18484679401052530_4672475523107727628_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7IhtZupbkCUQ7kNvgF8_Os_&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI4ODI2Mjc4Mg%3D%3D.3-ccb7-5&oh=00_AYAnsx8dFbpFMPXmjdjHjLsak8tqlUs_4s65Qpb9iKTNcA&oe=67C5AAE9&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481609757_18484679434052530_1002587209572338076_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=hhpLXg4JEZQQ7kNvgGxmRlx&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDMxMzQwMzIwMA%3D%3D.3-ccb7-5&oh=00_AYAT3dqo8B8kW2WBIGNNV-3DBLRGypMnQ65dji1WrzTNCQ&oe=67C59443&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480769572_18484679410052530_2367166304843383149_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Uq8uYFpT1t8Q7kNvgGjjZ8e&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI4MDAxNTg5MA%3D%3D.3-ccb7-5&oh=00_AYDE43y_h6Q5dbW9EYI7_dk0uP5ioKKL94IvcYAGmEDnIg&oe=67C5AB6F&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481090656_18484679419052530_7908906074502398054_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Iup4puNVgE0Q7kNvgGeBkmP&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI5Njc0MjUxNA%3D%3D.3-ccb7-5&oh=00_AYAUVyYjvzZh-KW2ZZQgy3hTS60_v-NAKLetLb6oTyowXQ&oe=67C59AED&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481089515_18484679437052530_6214755354493708923_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Sa30U2quhqoQ7kNvgGUyP0m&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI0NjM4NzUwMA%3D%3D.3-ccb7-5&oh=00_AYD3lMJiiqS8uebFJxTO0c5JGsawcK2qKqc6bCyl-0Qcmw&oe=67C5821F&_nc_sid=8b3546" + ], + "alt": "Photo by Adrián de la Garza on February 21, 2025. May be an image of 3 people, people standing, suit, blazer, dinner jacket and text.", + "likesCount": 663, + "timestamp": "2025-02-21T23:10:55.000Z", + "childPosts": [ + { + "id": "3573206704271563356", + "type": "Image", + "shortCode": "DGWlJDdJppc", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGWlJDdJppc/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 719, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481023355_18484679389052530_6140187690128957417_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=a1f72bmQ4uUQ7kNvgF77bu8&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI3MTU2MzM1Ng%3D%3D.3-ccb7-5&oh=00_AYC2-QtZtVs6m11dO1i8btXMsWbGr1N7jEspX_2qV9sYEg&oe=67C581F9&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 21, 2025. May be an image of 3 people, people standing, suit, blazer, dinner jacket and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573206704288262782", + "type": "Image", + "shortCode": "DGWlJDeJWp-", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGWlJDeJWp-/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481021674_18484679401052530_4672475523107727628_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7IhtZupbkCUQ7kNvgF8_Os_&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI4ODI2Mjc4Mg%3D%3D.3-ccb7-5&oh=00_AYAnsx8dFbpFMPXmjdjHjLsak8tqlUs_4s65Qpb9iKTNcA&oe=67C5AAE9&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 21, 2025. May be an image of 3 people, office and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573206704313403200", + "type": "Image", + "shortCode": "DGWlJDfpQdA", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGWlJDfpQdA/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481609757_18484679434052530_1002587209572338076_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=hhpLXg4JEZQQ7kNvgGxmRlx&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDMxMzQwMzIwMA%3D%3D.3-ccb7-5&oh=00_AYAT3dqo8B8kW2WBIGNNV-3DBLRGypMnQ65dji1WrzTNCQ&oe=67C59443&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 21, 2025. May be an image of 12 people and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573206704280015890", + "type": "Image", + "shortCode": "DGWlJDdp5QS", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGWlJDdp5QS/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480769572_18484679410052530_2367166304843383149_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Uq8uYFpT1t8Q7kNvgGjjZ8e&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI4MDAxNTg5MA%3D%3D.3-ccb7-5&oh=00_AYDE43y_h6Q5dbW9EYI7_dk0uP5ioKKL94IvcYAGmEDnIg&oe=67C5AB6F&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 21, 2025. May be an image of 8 people and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573206704296742514", + "type": "Image", + "shortCode": "DGWlJDeps5y", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGWlJDeps5y/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481090656_18484679419052530_7908906074502398054_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Iup4puNVgE0Q7kNvgGeBkmP&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI5Njc0MjUxNA%3D%3D.3-ccb7-5&oh=00_AYAUVyYjvzZh-KW2ZZQgy3hTS60_v-NAKLetLb6oTyowXQ&oe=67C59AED&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 21, 2025. May be an image of 6 people, people standing, office and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573206704246387500", + "type": "Image", + "shortCode": "DGWlJDbpnMs", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGWlJDbpnMs/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481089515_18484679437052530_6214755354493708923_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Sa30U2quhqoQ7kNvgGUyP0m&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI0NjM4NzUwMA%3D%3D.3-ccb7-5&oh=00_AYD3lMJiiqS8uebFJxTO0c5JGsawcK2qKqc6bCyl-0Qcmw&oe=67C5821F&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 21, 2025. May be an image of 2 people, crowd and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + } + ], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3572663527829586595", + "type": "Video", + "shortCode": "DGUpoy-RsKj", + "caption": "Ciudad Deportiva se transforma para el bien de todos los regios.\n\nEl día de hoy supervisé las obras de remodelación para que Ciudad Deportiva cuente con espacios dignos y seguros para todos los atletas. \n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGUpoy-RsKj/", + "commentsCount": 50, + "dimensionsHeight": 850, + "dimensionsWidth": 480, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481398042_18484542001052530_8988623957150353881_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=ZHIP4YZZkhcQ7kNvgHQ6EM8&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBr5wTRTS9e9jNd-T-BfkPNsKO9zLqk7HUs6JnrYUuFBQ&oe=67C57C19&_nc_sid=8b3546", + "images": [], + "videoUrl": "https://scontent-iad3-2.cdninstagram.com/o1/v/t16/f2/m86/AQOhnxHcSxYyObhGvCIrJRyJowTaneahs5NRFeoW8po5s2pe4YYw1zZB_YlMA7OoX68dys7IyWnF1AK5uWR0o7m9G0jkBGkB7HnJ2TA.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uY2xpcHMuYzIuNzIwLmJhc2VsaW5lIn0&_nc_cat=111&vs=2745584712284435_625181183&_nc_vs=HBksFQIYUmlnX3hwdl9yZWVsc19wZXJtYW5lbnRfc3JfcHJvZC81NTQ2QzZBMjA3OTFGRTY2NkFCQThENkQ2MjhEQTZCNV92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dENjhwaHhQUC1CY3R5c0hBTTRkOEdncXBPeGRicV9FQUFBRhUCAsgBACgAGAAbABUAACa2t%2B6q%2Bs2pQRUCKAJDMywXQFmEOVgQYk4YEmRhc2hfYmFzZWxpbmVfMV92MREAdf4HAA%3D%3D&ccb=9-4&oh=00_AYDU6GrZOdJLvzhna_OwK8XaBqwdNNTqq3i7KHv9tKgoQg&oe=67C1A5AC&_nc_sid=8b3546", + "alt": null, + "likesCount": 792, + "videoViewCount": 10364, + "timestamp": "2025-02-21T05:13:06.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "clips", + "taggedUsers": [ + { + "full_name": "aldodenigris", + "id": "55883830", + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/371750700_852089726562390_1994334907073070657_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=106&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=WvhBgIuJwh4Q7kNvgHuaXzV&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYDo-Un_uUmPUf2aXtvkelUoSZmy-a7RGhq-muttU2o-Mg&oe=67C5851D&_nc_sid=8b3546", + "username": "aldodenigris" + } + ] + }, + { + "id": "3572453492912528761", + "type": "Video", + "shortCode": "DGT54Ytp515", + "caption": "Hoy recibí al Comisionado Omar Amador Escobar en la Academia de Policía y el C4 para seguir fortaleciendo nuestra estrategia de seguridad.\n\nCon esto seguimos trabajando en la coordinación con gobierno Federal, donde trabajaremos para combatir el crimen y proteger a las familias de Monterrey.\n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGT54Ytp515/", + "commentsCount": 79, + "dimensionsHeight": 1333, + "dimensionsWidth": 750, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480768883_18484496086052530_282359645328069450_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=zhpGLfTmBckQ7kNvgFalOLX&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCv4A3cx6s-dnB4du_dJT88wT8ERO79LSd6PotjwvLNNg&oe=67C58ACE&_nc_sid=8b3546", + "images": [], + "videoUrl": "https://scontent-iad3-1.cdninstagram.com/o1/v/t16/f2/m86/AQPuxNrdQ76Ieh46OUihSArNIWlcgWkpnhtgsh2L_aCM6dSCFdhcr6EDgagPMomRGXLESaMKI2AZyM2UG11rThXbbBQhB_SsBS-PHIU.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uY2xpcHMuYzIuNzIwLmJhc2VsaW5lIn0&_nc_cat=101&vs=1419145882406626_2561696239&_nc_vs=HBksFQIYUmlnX3hwdl9yZWVsc19wZXJtYW5lbnRfc3JfcHJvZC8zNzRFMjM4Njg5Qjc0QkQ3NDkwN0I0MzM5OTZBNDM4OF92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dJUUFzUnhMTktab2Zoa0dBTjR1UVp5a3VZOGFicV9FQUFBRhUCAsgBACgAGAAbABUAACbIoI2%2B8MKyPxUCKAJDMywXQE7u2RaHKwIYEmRhc2hfYmFzZWxpbmVfMV92MREAdf4HAA%3D%3D&ccb=9-4&oh=00_AYAbwCQxavV7tj89ePnRPJqSq7N2DoVqqY_V0rccfBBUAw&oe=67C19427&_nc_sid=8b3546", + "alt": null, + "likesCount": 1164, + "videoViewCount": 9282, + "timestamp": "2025-02-20T22:16:11.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "clips" + } +] \ No newline at end of file diff --git a/backend/app/testing/data/instagram/profile_samples.json b/backend/app/testing/data/instagram/profile_samples.json new file mode 100644 index 0000000000..40f13710ef --- /dev/null +++ b/backend/app/testing/data/instagram/profile_samples.json @@ -0,0 +1,2110 @@ +[ + { + "inputUrl": "https://www.instagram.com/adriandelagarzas", + "id": "1483444529", + "username": "adriandelagarzas", + "url": "https://www.instagram.com/adriandelagarzas", + "fullName": "Adrián de la Garza", + "biography": "Alcalde de Monterrey 2024 - 2027\nOrgullosamente regio 🌄", + "followersCount": 157144, + "followsCount": 155, + "hasChannel": false, + "highlightReelCount": 49, + "isBusinessAccount": true, + "joinedRecently": false, + "businessCategoryName": "None,Public figure", + "private": false, + "verified": true, + "profilePicUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7qU3J4SFzmsQ7kNvgFA1d_N&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBwX2Z48Y05vz3DmEeO9hDWESNvQQdR94D2bHmCXmUeUA&oe=67C5ACA7&_nc_sid=8b3546", + "profilePicUrlHD": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_s320x320_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7qU3J4SFzmsQ7kNvgFA1d_N&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBf9aV5DokV0WXQIQGRUj0HU5Kb9GpCf_RCCiSM3cxnmw&oe=67C5ACA7&_nc_sid=8b3546", + "igtvVideoCount": 199, + "relatedProfiles": [ + { + "id": "244811644", + "full_name": "San Pedro Garza García", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/472249747_2788472397990572_1977067771073275806_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=108&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=R4pqbw_yQuYQ7kNvgFIL-cn&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBeXV6MXN19SVm-fPjLQP1ZfxXwhwMWPm8BDk2uZqwh-A&oe=67C586DE&_nc_sid=8b3546", + "username": "sanpedroggnl" + }, + { + "id": "57904801962", + "full_name": "Auditorio Cumbres", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/419508790_1123033688698757_1251755062484443413_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=105&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=xqVoaX31EYsQ7kNvgE5DT4i&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYB8OG8Pq-NdjwwfZhWwjLPXqoR2dmcRPVzdBk6Sg7JTPA&oe=67C58883&_nc_sid=8b3546", + "username": "auditoriocumbresoficial" + }, + { + "id": "366729802", + "full_name": "Perla Villarreal", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/462027552_1047472886655765_1777155344126263876_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=109&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=sY7aXFV9DzcQ7kNvgH4kJEh&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBkyLu5KgmYpxMVHsvTwbXQeNtjha8H8W5RZ8NbA4_jjQ&oe=67C57C8F&_nc_sid=8b3546", + "username": "perlitavillarrealv" + }, + { + "id": "179851821", + "full_name": "Melisa Peña", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/461989419_1071153614734867_1821602222578454489_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=107&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=McCz6z9RI70Q7kNvgGSKgSR&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYAHc24nGS_oD3XfCo8Hi4mfNaDndL_H5ZBVH1DuYga57w&oe=67C59761&_nc_sid=8b3546", + "username": "melisapena.nl" + }, + { + "id": "217909956", + "full_name": "Daniel Acosta", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/480559357_659020403161060_598817105414733572_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=104&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Ki3bvfKmtuQQ7kNvgFrFjqN&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCgaS8eY0qpmwf8YPJ7ggNrEB-j6W0H4xyzxN4Ww_ka3A&oe=67C5921A&_nc_sid=8b3546", + "username": "danielacostafre" + }, + { + "id": "5082065", + "full_name": "Mauro Guerra", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/447361072_1136092744365514_4212813927185116114_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=LyUiC8PES0oQ7kNvgFO3xM-&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYDpM44byZQU4UdrkQEQwZ3O9iaH1OiRW20AtGRRxjmliw&oe=67C58F47&_nc_sid=8b3546", + "username": "mauroguerranl" + }, + { + "id": "9396816030", + "full_name": "Mauro Molano", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/461888698_455613837496418_5729713677281174399_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=102&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=rZ-XG-V-WdEQ7kNvgHerwq7&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCd-o2BYnNnAF4UXT_tHBuOEmv1dM0Oeu6TviWWj5oiGg&oe=67C5A3DD&_nc_sid=8b3546", + "username": "mauromolanon" + }, + { + "id": "7718154169", + "full_name": "Fiscalía General Justicia NL", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/43818106_697891230610547_6427914094610743296_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=111&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7gu2t84UEOsQ7kNvgGHpTTo&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCd287NtEepMWj-E51KaF8DD612E0_IYmVKghu30rTGfA&oe=67C5AB7F&_nc_sid=8b3546", + "username": "fiscalianl" + }, + { + "id": "40178275842", + "full_name": "Jose Luis Garza Garza", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/462786766_558415226581218_6693712797469666984_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=106&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=aPYkh1QboJwQ7kNvgGfTq0N&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYANWZuRa5kvMOEdOqPChcER8QAh1DTTPx0SZtZ7Pg8IPQ&oe=67C5A9A7&_nc_sid=8b3546", + "username": "garzajoseluisgarza" + }, + { + "id": "271146789", + "full_name": "PepeReyes 🐼", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/475837566_1544445612891854_5555917038425717977_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=102&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=bQHzu8c-dyoQ7kNvgFK2VR9&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBXm-rK2L5xrrqOl3KfB0a4b_8zCiCmK3c1czr1g-FjhA&oe=67C5A139&_nc_sid=8b3546", + "username": "soypepereyes" + }, + { + "id": "225677014", + "full_name": "Raúl Cantú de la Garza", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/446332102_336175472911051_4278050522867774505_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=1&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=x2CrFPBJjVEQ7kNvgFqPro8&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBO7n3tIe6cCC9zQr3wtiwsJVEQrF53boS5v5DMR6hlcA&oe=67C5914B&_nc_sid=8b3546", + "username": "raulcantudelagarza" + }, + { + "id": "1509468544", + "full_name": "Ale Morales", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/473720207_3810226099202222_7500361390976335454_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=102&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=CfaileFYjbQQ7kNvgFMnAfn&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCXINjwcNvUMJ2v4vTjBJiq-P5-OmSPq721q34iZQYDrA&oe=67C5A51B&_nc_sid=8b3546", + "username": "alemoralesmx" + }, + { + "id": "335551807", + "full_name": "PLAYERS of Life Monterrey | Negocios y estilo de vida", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/465525965_1904697023360464_5432514328525906075_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=104&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=hzKoqPBPemAQ7kNvgFlM6_b&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBp0ElsEC6PoMi1WGaOEz4jtmhOLeS0-Z_vbM0lOTB0PQ&oe=67C5939C&_nc_sid=8b3546", + "username": "playersmty" + }, + { + "id": "187521732", + "full_name": "Josué Becerra", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/337097912_722914256179345_2270986122095162093_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Eng-konooDwQ7kNvgHL7vui&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBXemMUiSFVNw2fy_FNxy9wkAdu-Ee7drYoxP3NvuIEdQ&oe=67C5A1B0&_nc_sid=8b3546", + "username": "josuebecerra18" + }, + { + "id": "8771368450", + "full_name": "Sandra Pámanes", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/461919974_1580154262597960_3048610830835806142_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=104&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=VrHmT6Z4vysQ7kNvgGezGYR&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYAiLHNm_ZCwok27G79AqlfWWXveFjG-uJaz_jJ3wnqayw&oe=67C5A565&_nc_sid=8b3546", + "username": "sandrapamanesnl" + }, + { + "id": "179083025", + "full_name": "Karla Navarro", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/466690316_511790645191265_6325753648599498487_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=105&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=4S32oQFthCIQ7kNvgGCrx1Z&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYAlbtoXMTGZopPZa43OQPc1MOfHVIX0To0fQvEmwanH4g&oe=67C586B1&_nc_sid=8b3546", + "username": "karlanavarromx" + }, + { + "id": "45396054341", + "full_name": "Leticia Guajardo", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/474662157_524888117277464_1427313009266630840_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=1&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=1f0b7Vx_Y9YQ7kNvgE3Kle1&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYAQyFbsmXKnVmD_KmRmrm0GNs_1gqCvactzHbfFTKt7AQ&oe=67C58DBB&_nc_sid=8b3546", + "username": "leticiagdenigris" + }, + { + "id": "50711769314", + "full_name": "Desarrollo Humano Monterrey", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/475583706_2228012944266965_585650271655539982_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=105&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=2lCOz_sLJjAQ7kNvgFxdBFX&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYDQHApebbqX-fYbcTJxDj2-cZBj9XMZgmP3QOy60LGq8g&oe=67C5AC8F&_nc_sid=8b3546", + "username": "desarrollohumanomty" + }, + { + "id": "704548228", + "full_name": "Cecilia Robledo", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/467344442_1622332008716134_866235395550724018_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Xq7fHxleEj0Q7kNvgE9yzHJ&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYDnmS4PTnTYKTL87CEdGZtaXcDkuBQTfBmvpsQj14vyQQ&oe=67C58C65&_nc_sid=8b3546", + "username": "ceciliarobledonl" + }, + { + "id": "7551237893", + "full_name": "Héctor García García", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/461285594_882544646776265_1807913420511165631_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=105&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=sYmcwBy1yGIQ7kNvgE7Pls_&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCz4GD5NW14B_9u2izOgM83LmpQLeUlvJjsFJu7I50yww&oe=67C591B9&_nc_sid=8b3546", + "username": "hectorgarcianl" + }, + { + "id": "200822313", + "full_name": "Iraís Reyes", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/447071417_436858185754223_8665318167753301826_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=111&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Y1oNK3yvna4Q7kNvgHCfhzA&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYAFyMtrrQZmipye50V5KaB_s5LSbJvRdB_5kfkiO1EYVQ&oe=67C587F4&_nc_sid=8b3546", + "username": "irais_reyes" + }, + { + "id": "1722484749", + "full_name": "José Luis Garza Ochoa", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/447616732_1913291749128819_75832497006784546_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=103&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=hR-yqp8NAFwQ7kNvgH4DPnI&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYC_uz01oLJYzlQLtEpdGrfuoszEhWBtSnah906VXO1izw&oe=67C5946B&_nc_sid=8b3546", + "username": "joseluisgarza8a" + }, + { + "id": "10862993561", + "full_name": "Dr. Ramírez Leal Jesus Arturo", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/322915160_1177768449800227_1082417475788426453_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=107&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=HYfjVrsDLHEQ7kNvgGsnLai&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYDNVZ-B0ewbcPa8eMujo4List22OmpcuCk7oW3FKcDEJw&oe=67C5A261&_nc_sid=8b3546", + "username": "dr.ramirezleal" + }, + { + "id": "4458706629", + "full_name": "Canal 28 Nuevo León", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/334636926_1633810087068886_489422809099095699_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=y23PWfZZ0_IQ7kNvgHTf7Uq&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYDqJEYZ2Yp5ebOVNPZTuiUI81q5Hvi8W9Ec6fN2c4mm9Q&oe=67C5A5F9&_nc_sid=8b3546", + "username": "canal28nl" + }, + { + "id": "3613604366", + "full_name": "Adalberto Madero Quiroga", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/69892331_397902787802089_6005492370148163584_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=fyeK1T8bIhYQ7kNvgFqyMne&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYD0vOcfIZR7Uy0YLur3wGhLWtfgQvA42XgPctPRRHYuqw&oe=67C57E94&_nc_sid=8b3546", + "username": "maderitomty" + }, + { + "id": "1748463634", + "full_name": "anafercardoso", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/436201460_3745328962410209_762725515327553716_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=105&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=1mePITnOaIQQ7kNvgEWHQKy&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYD9TDrsBq7qlndG-V3Au7_OFJCfnq2PXBFNPv1AsbUYZw&oe=67C5A248&_nc_sid=8b3546", + "username": "anafer.cardoso" + }, + { + "id": "3959061675", + "full_name": "Movimiento Ciudadano Nuevo León", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/430656271_407220788565106_6676728465126649132_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=1&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=dXRVatB0DKsQ7kNvgGvG4ln&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYD43H9z93J7VkGaXOHkunEqo2eoQC_hraQFzaF2HpjQYQ&oe=67C58052&_nc_sid=8b3546", + "username": "movciudadano.nl" + }, + { + "id": "8001416859", + "full_name": "M i g u e l L e c h u g a 🥬", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/457435181_1030055858529128_1588239684569342095_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=108&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=2-PM4LhEXd8Q7kNvgGBwISu&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCiY8EA6DwIkOk4o42vrksHZnFBtWTzNdwlUNWKzysaeQ&oe=67C58C5F&_nc_sid=8b3546", + "username": "miguellechugamx" + }, + { + "id": "49974315981", + "full_name": "DIF Monterrey", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/476248343_1687538275535135_1414458470168736962_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=111&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=6tGaGRgBoZcQ7kNvgEU281B&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYB1aADNGgbbTje4AqWUWZ2TkQG63XqAKnZkM2YSHF5aTQ&oe=67C593DB&_nc_sid=8b3546", + "username": "difmonterrey.nl" + }, + { + "id": "9534198431", + "full_name": "Mariana Castellanos", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/290941546_532206931974470_5928306327726304225_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=102&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=5iAITaEb0eYQ7kNvgHI36Jl&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCdO9tkCbBdCTIGiFE4vioJHfkxFWs1B_NytEey-XoTtA&oe=67C59D3C&_nc_sid=8b3546", + "username": "mariana_castellanos_28" + }, + { + "id": "21312574629", + "full_name": "Casino Revolución", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/472108650_579087484742320_1900552301484851760_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=104&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=g1QeBt0uAQoQ7kNvgFkyvuL&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYApir07XzDhwtyQ5yJIcM-H0aPGqqoXBn-v9yYfjXihLg&oe=67C57D72&_nc_sid=8b3546", + "username": "casinorevolucion_oficial" + }, + { + "id": "49618307665", + "full_name": "Secretaría de Igualdad e Inclusión", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/472869844_622342666912715_88870155846635518_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=111&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=RM4ma8swa0IQ7kNvgGANirb&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCRK6Z9GllIc77jNbdOADfbHUBia2-iWD2B542HfkmiyQ&oe=67C5A1CA&_nc_sid=8b3546", + "username": "igualdadnl" + }, + { + "id": "3509751426", + "full_name": "Agua y Drenaje de Monterrey", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/452805483_7602359996560375_3741085026300179938_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=107&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=VfLoGtqCaVEQ7kNvgGo1A8q&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYDFLPzOzhD_vSqARbSvvGSTRFeOnU2gcrP7xNban9AKjw&oe=67C588BD&_nc_sid=8b3546", + "username": "ayd_monterrey" + }, + { + "id": "559603059", + "full_name": "Marco González Valdez", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/447815456_485001563870504_519211616147083103_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=108&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=88V7dOxMzBYQ7kNvgE9PoBa&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYAb5UKMixUW0tNThMvkQg3p4el81XegeYPS0mKAXndFLA&oe=67C5A95A&_nc_sid=8b3546", + "username": "marcogonzaleznl" + }, + { + "id": "24068390", + "full_name": "miguelcharles", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/470944838_945956213604425_4482601475900895506_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=105&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=NurnNpsGKwcQ7kNvgHe8wVD&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCAHArI28CO3s8tSU-HIat1vQaoHlG2yEan-MinwDfgPQ&oe=67C587F5&_nc_sid=8b3546", + "username": "miguelcharles" + }, + { + "id": "24575568", + "full_name": "Dra. Lily García Rodriguez", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/343998589_9479640862047742_202409108514563980_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=-hPuOFbnLNQQ7kNvgHDG-3L&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCHuEtz6U1l5Myk_wUNzrntO9v1vTHQDcvjanQ9DPQv9Q&oe=67C579C3&_nc_sid=8b3546", + "username": "dra.lilygarcianl" + }, + { + "id": "50454477764", + "full_name": "Participación Ciudadana NL", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/472494212_1128373135321801_4119480184438971608_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=TM2irAj1TuwQ7kNvgG1Mv_v&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYAnMEnWQfx-gmljMOI3enwZw9ybgOY_ZfKHcKtGxFOEGw&oe=67C5998A&_nc_sid=8b3546", + "username": "sparticipa_nl" + }, + { + "id": "6819205150", + "full_name": "Lugo LM", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/480654356_2352141288497942_2609241670229913591_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=102&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=ixEKESNVLBQQ7kNvgHDSQLZ&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYAyblmr8B8uLKgFSSQKNGHQ_rbeK19oxrhdWmjHk9THgQ&oe=67C57DFF&_nc_sid=8b3546", + "username": "lugolmof" + }, + { + "id": "982271821", + "full_name": "𝐑𝐨𝐛𝐞𝐫𝐭𝐨 𝐂𝐚𝐯𝐚𝐳𝐨𝐬", + "is_private": false, + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/477042361_653812967082055_35080465225651414_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=107&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=OXa1FJkHUSUQ7kNvgFM0gJH&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCBNLSeRFz8VY3MzU4R0NElhQAq58EUgBGqVQYJBeTNEQ&oe=67C58122&_nc_sid=8b3546", + "username": "robertocavazoseventos" + }, + { + "id": "1546092758", + "full_name": "Samuel Orlando Garcia Villarreal", + "is_private": false, + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/419299848_6909729102407587_4847458129767979978_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=107&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=-LgvBeL8HUYQ7kNvgFugs7-&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCyzge3YitewrFTrK3Kwu629dn8pubcmMHx-hl-_IQ_UQ&oe=67C57F44&_nc_sid=8b3546", + "username": "samuel.garcia.v" + } + ], + "latestIgtvVideos": [ + { + "type": "Video", + "shortCode": "CYLDOqiIKSc", + "title": "", + "caption": "Les deseo un excelente año 2022 lleno de bendiciones, éxito y salud para ustedes y sus seres queridos. \n\nJuntos vamos a superar cualquier reto que se presente. \n\n¡Feliz Año Nuevo!", + "commentsCount": 84, + "commentsDisabled": false, + "dimensionsHeight": 848, + "dimensionsWidth": 480, + "displayUrl": "https://scontent-iad3-2.cdninstagram.com/v/t51.29350-15/270933835_811784263552479_8300961826967310985_n.jpg?stp=dst-jpg_e35_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=103&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=kcRGqVEchoMQ7kNvgE258OZ&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCQ0f0lEbLjDTi0gkibu6OWEOujSpgwAQtXA-JZQ2UiuA&oe=67C59C5C&_nc_sid=8b3546", + "likesCount": 1631, + "videoDuration": 39.833, + "videoViewCount": 15269, + "id": "2741299000067007644", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/CYLDOqiIKSc/", + "firstComment": "", + "latestComments": [], + "images": [], + "videoUrl": "https://scontent-iad3-2.cdninstagram.com/o1/v/t16/f2/m69/AQMT1zZNI96Z8YjVdMM-H72xxKB-IKfxSnuuK-VcXfdUOc9Fi4m9Q5xzkTEthapZZEAMrEaJNUVF99NCe4UKPW0W.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uaWd0di5jMi40ODAuYmFzZWxpbmUifQ&_nc_cat=111&vs=4567863229991657_2313611903&_nc_vs=HBksFQIYOnBhc3N0aHJvdWdoX2V2ZXJzdG9yZS9HRVctS2hDQTRkU3RhcmdGQUZPZHRuRVBTUHhJYnZWQkFBQUYVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dQb1ZLUkNtRy0wTVUwVUxBQ0JkTmcwWXhZZEtidlZCQUFBRhUCAsgBACgAGAAbAYgHdXNlX29pbAExFQAAJpScl%2BODz%2B8%2FFQIoAkMzLBdAQ%2BqfvnbItBgSZGFzaF9iYXNlbGluZV8xX3YxEQB17AcA&ccb=9-4&oh=00_AYA71kTPeHWCjKnb_3N1xdO9VHkmThwvhvZZLfwsOePNOw&oe=67C1AF56&_nc_sid=8b3546", + "alt": null, + "timestamp": "2022-01-01T03:40:29.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "igtv" + }, + { + "type": "Video", + "shortCode": "CX42xnLoYDP", + "title": "", + "caption": "De todo corazón les deseo una muy Feliz Navidad y que pasen una excelente noche en compañía de sus seres queridos.\n\n¡Les mando un fuerte abrazo!", + "commentsCount": 70, + "commentsDisabled": false, + "dimensionsHeight": 750, + "dimensionsWidth": 750, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.29350-15/269936789_2249511708681802_8711926311348109121_n.jpg?stp=dst-jpg_e35_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Qf7-TjIQUugQ7kNvgE5bjAq&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYAJhlxkt4zoHE4PpwPSEUQuswu1fvsdAW0TMHzBqbP2aQ&oe=67C59BC5&_nc_sid=8b3546", + "likesCount": 1304, + "videoDuration": 16.766, + "videoViewCount": 10790, + "id": "2736177677464600783", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/CX42xnLoYDP/", + "firstComment": "", + "latestComments": [], + "images": [], + "videoUrl": "https://scontent-iad3-1.cdninstagram.com/o1/v/t16/f2/m69/AQPBJgj6WpwS1-OcOvW-aGr9Km4EhwTu4lefZbHIAE6X7Lae0PH-7G2ykp6AF4Tw1JQWQ2Li3ogzDtFx21S7s9tj.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uaWd0di5jMi43MjAuYmFzZWxpbmUifQ&_nc_cat=110&vs=642967560175578_3111312889&_nc_vs=HBksFQIYOnBhc3N0aHJvdWdoX2V2ZXJzdG9yZS9HRzl6RlJEc2hQMW90VDBCQUlrSDcxZVVtYjVUYnZWQkFBQUYVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dBQ2RGaERweVFpYUNuZ0FBQVVDYS1KTW0zRWpidlZCQUFBRhUCAsgBACgAGAAbAYgHdXNlX29pbAExFQAAJuqEvO%2FJ1M4%2FFQIoAkMzLBdAMMQYk3S8ahgSZGFzaF9iYXNlbGluZV8xX3YxEQB17AcA&ccb=9-4&oh=00_AYC3x1oME8TZcYlfkrwDrpxqGePwx_S9vS-G8GJFCQ0x3Q&oe=67C1B180&_nc_sid=8b3546", + "alt": null, + "timestamp": "2021-12-25T02:04:21.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "igtv" + }, + { + "type": "Video", + "shortCode": "CXt-zbPAEHq", + "title": "", + "caption": "Les comparto este mensaje, que tengan excelente semana. 👊🏼💪🏼", + "commentsCount": 112, + "commentsDisabled": false, + "dimensionsHeight": 626, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-2.cdninstagram.com/v/t51.29350-15/269649721_360842499135270_6388331839034791248_n.jpg?stp=dst-jpg_e35_s1080x1080_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=106&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Vrcn1_7552YQ7kNvgGkOZnk&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYDAb0S7Ew1Dq68jshkWHkpdaBx7el4wc86M24HDVjIlSQ&oe=67C59E95&_nc_sid=8b3546", + "likesCount": 1475, + "videoDuration": 76.533, + "videoViewCount": 13728, + "id": "2733116761703465450", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/CXt-zbPAEHq/", + "firstComment": "", + "latestComments": [], + "images": [], + "videoUrl": "https://scontent-iad3-1.cdninstagram.com/o1/v/t16/f2/m69/AQO3iDcIMLBRhGFN6sRDLZDiTHPk7LIyXylZOBwOSOCR1i4dazlrXFGOFm7QzPQthkIJsOd71xhnf1zDeTTJLKUT.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uaWd0di5jMi4xMjQwLmJhc2VsaW5lIn0&_nc_cat=107&vs=226773422984455_1316707475&_nc_vs=HBksFQIYOnBhc3N0aHJvdWdoX2V2ZXJzdG9yZS9HRlZJQ2hDLUNrS0d3ZjhHQUEwXzFKRGxzQVZUYnZWQkFBQUYVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dPY1ZGUkE5MEZ2TWNEOEVBSlZPOVgxaEtzczFidlZCQUFBRhUCAsgBACgAGAAbAYgHdXNlX29pbAExFQAAJqijg9Hlu90%2FFQIoAkMzLBdAUyIcrAgxJxgSZGFzaF9iYXNlbGluZV8xX3YxEQB17AcA&ccb=9-4&oh=00_AYCFjQLIbBXnzlSJicfV-h2jMHlFFIm1L1mxrbNAYj1eEw&oe=67C1AF18&_nc_sid=8b3546", + "alt": null, + "timestamp": "2021-12-20T20:43:17.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "igtv" + }, + { + "type": "Video", + "shortCode": "CP1f7-8oB5K", + "title": "🎥 En vivo: Rueda de prensa", + "caption": "", + "commentsCount": 734, + "commentsDisabled": false, + "dimensionsHeight": 853, + "dimensionsWidth": 480, + "displayUrl": "https://scontent-iad3-2.cdninstagram.com/v/t51.29350-15/197344712_994090568007519_9121040446195971347_n.jpg?stp=dst-jpg_e35_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=103&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=dviJcTvSdnUQ7kNvgEgYXJM&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYDwtwN_fbfvT8Rfe2vKgNilQPtqUdgcJQM2WMwNzlcFzA&oe=67C5AD1F&_nc_sid=8b3546", + "likesCount": 5495, + "videoDuration": 185.897, + "videoViewCount": 74874, + "id": "2591117622101679690", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/CP1f7-8oB5K/", + "firstComment": "", + "latestComments": [], + "images": [], + "videoUrl": "https://scontent-iad3-1.cdninstagram.com/o1/v/t16/f2/m69/AQMlT9Wsr5zvE_1mVPHyiMTxHwei6kd6gwmK6e5NOEK7vwbhYaKVk2O8cSEOsqlHsBjTgt3kG80sCm6C4q7C1Whm.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uaWd0di5jMi43MjAuYmFzZWxpbmUifQ&_nc_cat=108&vs=1555232748747316_338818391&_nc_vs=HBksFQIYOnBhc3N0aHJvdWdoX2V2ZXJzdG9yZS9HSzhxYmh2eno1VGFia1FFQU0zdzVYQU91dFV4YnFDQkFBQUYVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dPWlBheHNOUnFQaDJQY0JBSG5GNVR0NENKRlFicUNCQUFBRhUCAsgBACgAGAAbAYgHdXNlX29pbAExFQAAJsiXtOyaysY%2FFQIoAkMzLBdAZzy0OVgQYhgSZGFzaF9iYXNlbGluZV8xX3YxEQB17AcA&ccb=9-4&oh=00_AYAXPFCoibdl0cja1Nzy7f9sWNwKcxOzGux8mVB42pY0mg&oe=67C1AF75&_nc_sid=8b3546", + "alt": null, + "timestamp": "2021-06-07T22:35:20.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "igtv" + }, + { + "type": "Video", + "shortCode": "CPo0Ifkhy1n", + "title": "-", + "caption": "🎥 Pega de calcas en Aramberri y Pino Suárez #TodoVaAEstarBien", + "commentsCount": 50, + "commentsDisabled": false, + "dimensionsHeight": 853, + "dimensionsWidth": 480, + "displayUrl": "https://scontent-iad3-2.cdninstagram.com/v/t51.29350-15/194843009_166795692077993_1085135622415295523_n.jpg?stp=dst-jpg_e35_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=105&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=huWnffkDCC4Q7kNvgFnK7Xa&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYAYsP0bTe6eDmASOhOZkZYfWgCoHTLRa6dCLjwxyYsEwg&oe=67C5845A&_nc_sid=8b3546", + "likesCount": 659, + "videoDuration": 88.433, + "videoViewCount": 8294, + "id": "2587547267997576551", + "hashtags": [ + "TodoVaAEstarBien" + ], + "mentions": [], + "url": "https://www.instagram.com/p/CPo0Ifkhy1n/", + "firstComment": "", + "latestComments": [], + "images": [], + "videoUrl": "https://scontent-iad3-2.cdninstagram.com/o1/v/t16/f2/m84/AQPqi81ZI3PSzWTAnZseSJWB0YvJngqg4yKMzf83hc01wsl-bBc6mFLUv_pQ0UBEbN9BR3bHQA6YojW_32BLPkiFlac6P4ejSvJ6-Dw.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uaWd0di5jMi44MjQuYmFzZWxpbmUifQ&_nc_cat=109&vs=1751434795261814_3432228947&_nc_vs=HBksFQIYTGlnX2JhY2tmaWxsX3RpbWVsaW5lX3ZvZC9BNDQ0Mzg3MzE3QjdGRjgzRjYyQTRDRTU4QTJEOTI4NF92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dIZ3FheHMzNnZ5NVE2UUJBTjRGUlVUQndrWVJicGt3QUFBRhUCAsgBACgAGAAbAYgHdXNlX29pbAExFQAAJrjc2Mz13cI%2FFQIoAkMzLBdAVhu2RaHKwRgSZGFzaF9iYXNlbGluZV8xX3YxEQB17AcA&ccb=9-4&oh=00_AYBcEQWcToUtwdcnu2jmVMxL8ILaT6_djry714P-NjjOIw&oe=67C18872&_nc_sid=8b3546", + "alt": null, + "timestamp": "2021-06-03T00:23:04.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "igtv" + }, + { + "type": "Video", + "shortCode": "CPoUDH5B2eO", + "title": "-", + "caption": "🎥 Pega de calcas en Av. Penitenciaria y Av. Rodrigo Gómez #TodoVaAEstarBien", + "commentsCount": 38, + "commentsDisabled": false, + "dimensionsHeight": 853, + "dimensionsWidth": 480, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.29350-15/196824465_854787655415599_5612996372290982610_n.jpg?stp=dst-jpg_e35_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=107&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=IhtFeMhGGqoQ7kNvgHgNTtZ&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCLLHFwb1Y4_76vjEwrFfABqDn3_J2D1sdnWjbZPnVzig&oe=67C5868D&_nc_sid=8b3546", + "likesCount": 425, + "videoDuration": 111.833, + "videoViewCount": 9121, + "id": "2587406161485981582", + "hashtags": [ + "TodoVaAEstarBien" + ], + "mentions": [], + "url": "https://www.instagram.com/p/CPoUDH5B2eO/", + "firstComment": "", + "latestComments": [], + "images": [], + "videoUrl": "https://scontent-iad3-1.cdninstagram.com/o1/v/t16/f2/m84/AQOfH4uID4yX8LIgsUkkFgUCvY8CI7R50_hnlN-NaCElUFscF9ZKpSF5pgnIPG4G5tAw60jnSd4fHCuymF6d4eyjmYwLnvlK-g_SMdQ.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uaWd0di5jMi44MjQuYmFzZWxpbmUifQ&_nc_cat=108&vs=735692311712877_159423956&_nc_vs=HBksFQIYTGlnX2JhY2tmaWxsX3RpbWVsaW5lX3ZvZC9CMDRDMkU0OEFDNEI3ODBBMEJFRTFFRUM2QjQxQzFBNF92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dNcFFNaGZ4T0FGYUZBVUJBRjhhbG5pT1ZWMF9icGt3QUFBRhUCAsgBACgAGAAbAYgHdXNlX29pbAExFQAAJuzcnrub5sI%2FFQIoAkMzLBdAW%2FVP3ztkWhgSZGFzaF9iYXNlbGluZV8xX3YxEQB17AcA&ccb=9-4&oh=00_AYATS1k12JXuoDahP67r_IS-cLi5PJRu7hFxEOWHl90VPA&oe=67C1AC34&_nc_sid=8b3546", + "alt": null, + "timestamp": "2021-06-02T19:50:42.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "igtv" + }, + { + "type": "Video", + "shortCode": "CPoDvHOBmG8", + "title": "-", + "caption": "🎥 Pega de calcas en Av. Paseo de los Leones y Puerta de Hierro #TodoVaAEstarBien", + "commentsCount": 45, + "commentsDisabled": false, + "dimensionsHeight": 853, + "dimensionsWidth": 480, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.29350-15/194997609_177109447681580_6110006025744611095_n.jpg?stp=dst-jpg_e35_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=108&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=NoMxJGwkTdIQ7kNvgGJ-SHn&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYChM3_XNGcs2PJeTkVn4iXEsr9bra2exGdn9ubdmN4Djw&oe=67C593AF&_nc_sid=8b3546", + "likesCount": 577, + "videoDuration": 97.5, + "videoViewCount": 3136, + "id": "2587334417630781884", + "hashtags": [ + "TodoVaAEstarBien" + ], + "mentions": [], + "url": "https://www.instagram.com/p/CPoDvHOBmG8/", + "firstComment": "", + "latestComments": [], + "images": [], + "videoUrl": "https://scontent-iad3-2.cdninstagram.com/o1/v/t16/f2/m84/AQNCR8JUGvb1qIWmSfGgMM38pX6J-p3HInig3gCuoOMf1OdgE2oo7YtMz6zhPeDB78_AC7RxW8pOMjOs2umDv-LDQtSLAniQynI7dxU.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uaWd0di5jMi44MjQuYmFzZWxpbmUifQ&_nc_cat=106&vs=866212331529007_2584480347&_nc_vs=HBksFQIYTGlnX2JhY2tmaWxsX3RpbWVsaW5lX3ZvZC8zQjQ1MjUzRjUwRDc0RTZDQzMyQjY4N0RGODgzMEFCMF92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dGMTFEeGJJanpxWnN0a0NBTEc2Uk9XcVROWklicGt3QUFBRhUCAsgBACgAGAAbAYgHdXNlX29pbAExFQAAJvSBitC22cs%2FFQIoAkMzLBdAWGAAAAAAABgSZGFzaF9iYXNlbGluZV8xX3YxEQB17AcA&ccb=9-4&oh=00_AYAcLrY3Zg1DwJARVNMlgAqd_lOBB8JNFYSuzCqWjjSZPQ&oe=67C1B456&_nc_sid=8b3546", + "alt": null, + "timestamp": "2021-06-02T17:21:07.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "igtv" + }, + { + "type": "Video", + "shortCode": "CPmWcXphV0-", + "title": "-", + "caption": "Los ciudadanos saben que mi proyecto es el único que puede dar orden y rumbo en el estado, y por eso voy a ser el próximo gobernador de Nuevo León. #TodoVaAEstarBien", + "commentsCount": 124, + "commentsDisabled": false, + "dimensionsHeight": 1138, + "dimensionsWidth": 640, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.29350-15/194246671_191243036283376_2361940023180617788_n.jpg?stp=dst-jpg_e35_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=104&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=y9jgXqOIQtQQ7kNvgHDk7Ke&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYB__lopWj5aXQ_acUiuHph-rVeHum1s5qrdML-PM542vQ&oe=67C58C1C&_nc_sid=8b3546", + "likesCount": 1252, + "videoDuration": 65.233, + "videoViewCount": 7731, + "id": "2586853742532189502", + "hashtags": [ + "TodoVaAEstarBien" + ], + "mentions": [], + "url": "https://www.instagram.com/p/CPmWcXphV0-/", + "firstComment": "", + "latestComments": [], + "images": [], + "videoUrl": "https://scontent-iad3-2.cdninstagram.com/o1/v/t16/f2/m84/AQP8oK3OE7PmLWvXDD_SM6bk4QBjpnD7UKOg1tPbRl8xwTK0uPsvw39CHFYxeGaiya0vbdj5DvxX_sQhogl4wYcuHpcCQnZBZeivBDk.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uaWd0di5jMi4xMjgwLmJhc2VsaW5lIn0&_nc_cat=100&vs=361975249509594_2137611818&_nc_vs=HBksFQIYTGlnX2JhY2tmaWxsX3RpbWVsaW5lX3ZvZC84MDQ0N0YzMDI4Q0JCREY1OTczNDkyREMyMDlBOEQ5N192aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dHaC1qaGZlZzU4ZERPa0VBSl9Hbzc1eUVBZzVicGt3QUFBRhUCAsgBACgAGAAbAYgHdXNlX29pbAExFQAAJpbLk4XLv9Q%2FFQIoAkMzLBdAUE7peNT99BgSZGFzaF9iYXNlbGluZV8xX3YxEQB17AcA&ccb=9-4&oh=00_AYAAeOQ6gu_grhJtrnZlYCAPbps6ecx6edNJ01aA-zDAHA&oe=67C1B6CB&_nc_sid=8b3546", + "alt": null, + "timestamp": "2021-06-02T01:28:15.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "igtv" + }, + { + "type": "Video", + "shortCode": "CPmEURnB0t4", + "title": "-", + "caption": "Con la fuerza de la gente de Guadalupe vamos a construir un Nuevo León más fuerte y más seguro. No tengan duda, este 6 de junio vamos a triunfar… ¡Vamos fuerte! #TodoVaAEstarBien 💪🏼👊🏼", + "commentsCount": 17, + "commentsDisabled": false, + "dimensionsHeight": 853, + "dimensionsWidth": 480, + "displayUrl": "https://scontent-iad3-2.cdninstagram.com/v/t51.29350-15/194488667_533618031439794_7395884977234432225_n.jpg?stp=dst-jpg_e35_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=106&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=dYPDkt27gkgQ7kNvgFVO-up&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYB317Q8axcOdxWxwgROKf07lue9iRCkgHXFBLhwQP4H6g&oe=67C59152&_nc_sid=8b3546", + "likesCount": 434, + "videoDuration": 65.2, + "videoViewCount": 2863, + "id": "2586774021454908280", + "hashtags": [ + "TodoVaAEstarBien" + ], + "mentions": [], + "url": "https://www.instagram.com/p/CPmEURnB0t4/", + "firstComment": "", + "latestComments": [], + "images": [], + "videoUrl": "https://scontent-iad3-2.cdninstagram.com/o1/v/t16/f2/m84/AQMqLMxMonlKDGd213FhDvfEmjd8GVixs2e8JJqyI6xinBQ7qj8-VH0LnrG-Qdp9zIvmTLr06hJEXHDiFwtDgXdPMgel8Pb38E6iq4c.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uaWd0di5jMi4xMjgwLmJhc2VsaW5lIn0&_nc_cat=100&vs=1392212481392742_415462811&_nc_vs=HBksFQIYTGlnX2JhY2tmaWxsX3RpbWVsaW5lX3ZvZC84MDRFRkE0NjY0REFCRjQ1OTNFQzc1REE0QTA0QjM4Rl92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dFTGZIQmRPQUlqLVJMZ0lBR0NjTFJ5MkhjeEpicGt3QUFBRhUCAsgBACgAGAAbAYgHdXNlX29pbAExFQAAJsz5%2B7vP%2BMo%2FFQIoAkMzLBdAUEzMzMzMzRgSZGFzaF9iYXNlbGluZV8xX3YxEQB17AcA&ccb=9-4&oh=00_AYBvFoBjU2R_1SLXj5D7zdefLdXKn1prCqhi8_cWHrCwkw&oe=67C1931F&_nc_sid=8b3546", + "alt": null, + "timestamp": "2021-06-01T22:46:25.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "igtv" + }, + { + "type": "Video", + "shortCode": "CPl5uBkBz4k", + "title": "-", + "caption": "Hay momentos que son decisivos en la historia, y este lo es. Este 6 de junio piensa bien en lo que quieres para tu familia. Confía en mí, todo va a estar bien. 💪🏼👊🏼", + "commentsCount": 15, + "commentsDisabled": false, + "dimensionsHeight": 853, + "dimensionsWidth": 480, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.29350-15/194268178_170858731559560_156243015733238142_n.jpg?stp=dst-jpg_e35_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=104&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=O-snQ2qyzrsQ7kNvgF3Otdl&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYABehdfsiul3Bkxk5pZ1sTgDDkJsrFievYrgyDBBL3QqA&oe=67C5A43B&_nc_sid=8b3546", + "likesCount": 270, + "videoDuration": 116.366, + "videoViewCount": 1391, + "id": "2586727412419477028", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/CPl5uBkBz4k/", + "firstComment": "", + "latestComments": [], + "images": [], + "videoUrl": "https://scontent-iad3-1.cdninstagram.com/o1/v/t16/f2/m84/AQMjRBn4EbEs4erj1eCL0BeMRVkbd9aT1I6R8Ep_XHKrAhTo10DtC6b3C12vZipTEY9_0lM91FMx87uY83-rd_eGCo_u4CYvvdOKh1Q.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uaWd0di5jMi4xMjgwLmJhc2VsaW5lIn0&_nc_cat=104&vs=994668038418588_1455537263&_nc_vs=HBksFQIYTGlnX2JhY2tmaWxsX3RpbWVsaW5lX3ZvZC8xNDRBMDk0NkM4QTA3Mzc4QjZGOTJFODdEMTQ1NUJCNV92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dLWGQ1eGJ0aEhsellTOEJBT0NJX2I1MWdZdzVicGt3QUFBRhUCAsgBACgAGAAbAYgHdXNlX29pbAExFQAAJrCvmcHq%2Fsg%2FFQIoAkMzLBdAXRdsi0OVgRgSZGFzaF9iYXNlbGluZV8xX3YxEQB17AcA&ccb=9-4&oh=00_AYAv_ukSp4f72e-4wiAzI6j9BQFtoaVUvLyhSHM4oOAjHw&oe=67C1988E&_nc_sid=8b3546", + "alt": null, + "timestamp": "2021-06-01T21:16:11.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "igtv" + }, + { + "type": "Video", + "shortCode": "CPkCCHBh-rb", + "title": "¡Gracias @nachito10! #TodoVaAEstarBien 💪🏼👊🏼", + "caption": "", + "commentsCount": 56, + "commentsDisabled": false, + "dimensionsHeight": 853, + "dimensionsWidth": 480, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.29350-15/193913217_497254117994662_7305273916276867846_n.jpg?stp=dst-jpg_e35_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=y8PdEJIOYCoQ7kNvgH-7mYO&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYA-TIWN4upgM0xgaYsDRLd93XqyvmAz58YCjOqKjcH1QA&oe=67C57FFC&_nc_sid=8b3546", + "likesCount": 884, + "videoDuration": 61.366, + "videoViewCount": 5076, + "id": "2586201027091360475", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/CPkCCHBh-rb/", + "firstComment": "", + "latestComments": [], + "images": [], + "videoUrl": "https://scontent-iad3-2.cdninstagram.com/o1/v/t16/f2/m84/AQOR1Aru9jDl_DXI6xL97HXddOPSNChgVF2CjfbNj1TltPWiZTryATURNEOnyknEmI3zhYNkum5frq7Vk3G5yXqdhcRfRzfMRY9TGZc.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uaWd0di5jMi44NDguYmFzZWxpbmUifQ&_nc_cat=111&vs=708012117362823_1028254718&_nc_vs=HBksFQIYTGlnX2JhY2tmaWxsX3RpbWVsaW5lX3ZvZC8yMjQxOTlBRTIzMkI3NkZFMDM0MDlCOEUyMEQ3RDk5OV92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dOUXNIQmY1Rmo3S2ZERUJBSTZXVXp4RW1nVTZicGt3QUFBRhUCAsgBACgAGAAbAYgHdXNlX29pbAExFQAAJuqjj9n3lMw%2FFQIoAkMzLBdATq7ZFocrAhgSZGFzaF9iYXNlbGluZV8xX3YxEQB17AcA&ccb=9-4&oh=00_AYBENYObLy_ba1wVbxFGUa2JxuDW0JG8he454if0BdcJdA&oe=67C1A24E&_nc_sid=8b3546", + "alt": null, + "timestamp": "2021-06-01T03:48:26.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "igtv" + }, + { + "type": "Video", + "shortCode": "CPjM8cKBh9Z", + "title": "🎥 Pega de calcas en Av. Gómez Morín y Alfonso Reyes #TodoVaAEstarBien", + "caption": "", + "commentsCount": 24, + "commentsDisabled": false, + "dimensionsHeight": 853, + "dimensionsWidth": 480, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.29350-15/193903300_977073749766490_1225480291021180655_n.jpg?stp=dst-jpg_e35_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=H6tIni1W0GwQ7kNvgFyoHnc&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYAHjHH_36XClxH0L0joCBbAgk5PlU4FEgfr_mEdqmroFg&oe=67C59365&_nc_sid=8b3546", + "likesCount": 608, + "videoDuration": 63.1, + "videoViewCount": 3130, + "id": "2585967541000478553", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/CPjM8cKBh9Z/", + "firstComment": "", + "latestComments": [], + "images": [], + "videoUrl": "https://scontent-iad3-1.cdninstagram.com/o1/v/t16/f2/m84/AQMVW3HsF4tsPhOGUQB8EVU8khsmCKeY8TF-k-Z5bWHbrXeF0YDTPCmNcvFwB1htr4a4ocsTvqIilddDJsQkV56tKs0Mk6GR4iWr2dk.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uaWd0di5jMi44MjQuYmFzZWxpbmUifQ&_nc_cat=110&vs=611771741159512_2986835488&_nc_vs=HBksFQIYTGlnX2JhY2tmaWxsX3RpbWVsaW5lX3ZvZC9CODQyMUU5MjE3RUYwQjNFOTc2MjIzRTcyMjBBMzI5Ql92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dMeGhaQmRiMEFxaTh5c0RBSzFVS3dwNlVSMXJicGt3QUFBRhUCAsgBACgAGAAbAYgHdXNlX29pbAExFQAAJoaHsfLimcE%2FFQIoAkMzLBdAT4zMzMzMzRgSZGFzaF9iYXNlbGluZV8xX3YxEQB17AcA&ccb=9-4&oh=00_AYBEzyM4GIViUVCv5smhWMmXfGaYCTP44-dWaFIVSXYX3A&oe=67C1B689&_nc_sid=8b3546", + "alt": null, + "timestamp": "2021-05-31T20:03:39.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "igtv" + } + ], + "postsCount": 7723, + "latestPosts": [ + { + "id": "3576752389826611363", + "type": "Sidecar", + "shortCode": "DGjLVkdJQij", + "caption": "Hoy tuve el honor de participar en la inauguración del Foro de Alianzas para el Hábitat capítulo Monterrey, un espacio clave para construir la ciudad del futuro.\n\nJunto a expertos y estudiantes, buscamos soluciones reales para tener una ciudad más sustentable, ordenada y con mejor calidad de vida.\n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGjLVkdJQij/", + "commentsCount": 16, + "dimensionsHeight": 717, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481585399_18485585482052530_5292288465090866871_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=NMNioo7jz30Q7kNvgGULYOQ&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTE1NzA2OA%3D%3D.3-ccb7-5&oh=00_AYAioR1aSMWxpc5zOFAEDd4SA7llmfT2zK2ccx_sDPp9LA&oe=67C5AE7C&_nc_sid=8b3546", + "images": [ + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481585399_18485585482052530_5292288465090866871_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=NMNioo7jz30Q7kNvgGULYOQ&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTE1NzA2OA%3D%3D.3-ccb7-5&oh=00_AYAioR1aSMWxpc5zOFAEDd4SA7llmfT2zK2ccx_sDPp9LA&oe=67C5AE7C&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481896407_18485585485052530_434092325039799309_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=T6lR9-KuVTIQ7kNvgFFNydq&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTAwNjAzMw%3D%3D.3-ccb7-5&oh=00_AYDjHrNDGGtXqrDm00Z5cmikcDDW6r6vCcuOcK6HpKRLCg&oe=67C5810B&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481160795_18485585488052530_1539507575987271556_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=UeGqIKsxTeMQ7kNvgEvSnDc&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTE5OTYwNw%3D%3D.3-ccb7-5&oh=00_AYA_APTXyJ_EQcCqjiZ_MGTRxVeifJNMUz5N-CEkYRqUqw&oe=67C59DE1&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481700662_18485585509052530_2638589110064831842_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=uJZG70AJoEsQ7kNvgFDKkab&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTI0NTE3OQ%3D%3D.3-ccb7-5&oh=00_AYBPk6MhKHVdtym50vJfKxzeiMvpcwkM8k1z7JfjZSFwNg&oe=67C592D9&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481701725_18485585497052530_3587554920053704889_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7u1fTHIwl-kQ7kNvgEtbPyO&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzMDgzMjYxMw%3D%3D.3-ccb7-5&oh=00_AYCgg5aHMYa99aDXCtUXBz_t4kW4O0-uTbU1kmrQyV8kDw&oe=67C595D4&_nc_sid=8b3546" + ], + "alt": "Photo by Adrián de la Garza on February 26, 2025. May be an image of 10 people and text that says 'U-ERRE U-ERRE 55 ក្ Aniver Prepa ۲( Provoca 平 futuro Ed. Prof'.", + "likesCount": 153, + "timestamp": "2025-02-26T20:35:33.000Z", + "childPosts": [ + { + "id": "3576752376539157068", + "type": "Image", + "shortCode": "DGjLVYFJpJM", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGjLVYFJpJM/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 717, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481585399_18485585482052530_5292288465090866871_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=NMNioo7jz30Q7kNvgGULYOQ&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTE1NzA2OA%3D%3D.3-ccb7-5&oh=00_AYAioR1aSMWxpc5zOFAEDd4SA7llmfT2zK2ccx_sDPp9LA&oe=67C5AE7C&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 26, 2025. May be an image of 10 people and text that says 'U-ERRE U-ERRE 55 ក្ Aniver Prepa ۲( Provoca 平 futuro Ed. Prof'.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3576752376539006033", + "type": "Image", + "shortCode": "DGjLVYFJERR", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGjLVYFJERR/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481896407_18485585485052530_434092325039799309_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=T6lR9-KuVTIQ7kNvgFFNydq&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTAwNjAzMw%3D%3D.3-ccb7-5&oh=00_AYDjHrNDGGtXqrDm00Z5cmikcDDW6r6vCcuOcK6HpKRLCg&oe=67C5810B&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 26, 2025. May be a black-and-white image of 4 people, suit, dinner jacket and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3576752376539199607", + "type": "Image", + "shortCode": "DGjLVYFJzh3", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGjLVYFJzh3/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481160795_18485585488052530_1539507575987271556_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=UeGqIKsxTeMQ7kNvgEvSnDc&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTE5OTYwNw%3D%3D.3-ccb7-5&oh=00_AYA_APTXyJ_EQcCqjiZ_MGTRxVeifJNMUz5N-CEkYRqUqw&oe=67C59DE1&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 26, 2025. May be an image of 2 people, dinner jacket, suit and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3576752376539245179", + "type": "Image", + "shortCode": "DGjLVYFJ-p7", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGjLVYFJ-p7/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481700662_18485585509052530_2638589110064831842_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=uJZG70AJoEsQ7kNvgFDKkab&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzOTI0NTE3OQ%3D%3D.3-ccb7-5&oh=00_AYBPk6MhKHVdtym50vJfKxzeiMvpcwkM8k1z7JfjZSFwNg&oe=67C592D9&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 26, 2025.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3576752376530832613", + "type": "Image", + "shortCode": "DGjLVYEp4zl", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGjLVYEp4zl/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481701725_18485585497052530_3587554920053704889_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7u1fTHIwl-kQ7kNvgEtbPyO&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Njc1MjM3NjUzMDgzMjYxMw%3D%3D.3-ccb7-5&oh=00_AYCgg5aHMYa99aDXCtUXBz_t4kW4O0-uTbU1kmrQyV8kDw&oe=67C595D4&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 26, 2025. May be an image of 10 people, people standing, suit, office and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + } + ], + "locationName": "U-ERRE Universidad Regiomontana", + "locationId": "1954214947989485", + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3576105336452698938", + "type": "Sidecar", + "shortCode": "DGg4NtCpFM6", + "caption": "Si en tu calle hay luminarias apagadas, ¡avísanos! Servicios Públicos trabaja todos los días, las 24 horas, para que nuestra ciudad esté iluminada y segura 💡.\n\nLlama al 072 o envíanos tu reporte por redes sociales y resolvemos.\n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGg4NtCpFM6/", + "commentsCount": 60, + "dimensionsHeight": 937, + "dimensionsWidth": 750, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481496181_18485412331052530_741626016313520267_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=LgbMZ-DBxOkQ7kNvgEqtJ5r&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCRahWthB8cOAss722myUNpekmNvpGV0mnuXnLmt_-EXg&oe=67C5AC62&_nc_sid=8b3546", + "images": [ + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481496181_18485412331052530_741626016313520267_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=LgbMZ-DBxOkQ7kNvgEqtJ5r&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCRahWthB8cOAss722myUNpekmNvpGV0mnuXnLmt_-EXg&oe=67C5AC62&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481580721_18485412301052530_8820974216780361429_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7VIqbtahrhcQ7kNvgE06mTv&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NjEwNTMyNzA1NzYxNTU2MQ%3D%3D.3-ccb7-5&oh=00_AYB-jeXVbi2ICbSAsQBZOgMQPYAJ8v219T6xwYrSaJqFBQ&oe=67C5A2B5&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481757909_18485412310052530_6700093297804895280_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=tlbOqZPUZ94Q7kNvgFPCbWU&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NjEwNTMyNjk5MDQyNTM0Ng%3D%3D.3-ccb7-5&oh=00_AYB-dVNijzmgaglbD--Xu3kmP_Ns9qmxNax0N4dgyYoiSw&oe=67C57955&_nc_sid=8b3546" + ], + "alt": null, + "likesCount": 290, + "timestamp": "2025-02-25T23:09:58.000Z", + "childPosts": [ + { + "id": "3576105163865591971", + "type": "Video", + "shortCode": "DGg4LMTphCj", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGg4LMTphCj/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 937, + "dimensionsWidth": 750, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481496181_18485412331052530_741626016313520267_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=LgbMZ-DBxOkQ7kNvgEqtJ5r&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCRahWthB8cOAss722myUNpekmNvpGV0mnuXnLmt_-EXg&oe=67C5AC62&_nc_sid=8b3546", + "images": [], + "videoUrl": "https://scontent-iad3-2.cdninstagram.com/o1/v/t16/f2/m367/AQPQ4r_kCZ59yx27bg4PSChQdME4lNBEzb7UWfNUow5iNrnD55OlFFR4EJQZ7V2IfuItrtt0Nx1i2YynDyFVHgnjx_0Zc9iC0j9jT88.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uY2Fyb3VzZWxfaXRlbS5jMi4xMDgwLmJhc2VsaW5lIn0&_nc_cat=105&vs=1706388450292559_2664043447&_nc_vs=HBksFQIYQGlnX2VwaGVtZXJhbC80MjREQTkwRjRBNjIxQURDNDcxOUI2M0IwQUJDRDlBNV92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dDZ011UndyZ0J2anJ3MEVBQUhPSWhCLTZzOThia1lMQUFBRhUCAsgBACgAGAAbABUAACbO%2FefG2LCVQBUCKAJDMywXQC4AAAAAAAAYFmRhc2hfYmFzZWxpbmVfMTA4MHBfdjERAHXuBwA%3D&ccb=9-4&oh=00_AYC3ryYMKSjwTVFam4C9uqVPlc-K3e7JODbsleRTBVQeEg&oe=67C1855C&_nc_sid=8b3546", + "alt": null, + "likesCount": null, + "videoViewCount": 2123, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3576105327057615561", + "type": "Image", + "shortCode": "DGg4NkSprrJ", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGg4NkSprrJ/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1350, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481580721_18485412301052530_8820974216780361429_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7VIqbtahrhcQ7kNvgE06mTv&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NjEwNTMyNzA1NzYxNTU2MQ%3D%3D.3-ccb7-5&oh=00_AYB-jeXVbi2ICbSAsQBZOgMQPYAJ8v219T6xwYrSaJqFBQ&oe=67C5A2B5&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 25, 2025. May be a Twitter screenshot of text that says 'Haz tu reporte vía redes sociales ο al 072'.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3576105326990425346", + "type": "Image", + "shortCode": "DGg4NkOpX0C", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGg4NkOpX0C/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1350, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481757909_18485412310052530_6700093297804895280_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=tlbOqZPUZ94Q7kNvgFPCbWU&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NjEwNTMyNjk5MDQyNTM0Ng%3D%3D.3-ccb7-5&oh=00_AYB-dVNijzmgaglbD--Xu3kmP_Ns9qmxNax0N4dgyYoiSw&oe=67C57955&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 25, 2025. May be an image of poster and text that says 'La Secretaría aría de Servicios Públicos trabaja de día y de noche para que Monterrey esté iluminado. ADRIÁN DE LA LA GARZA Alcalde de Monterrey'.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + } + ], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575920731370056578", + "type": "Video", + "shortCode": "DGgOPWKRGuC", + "caption": "Esta es la historia de Capi.\n\nFue abandonado, sin nadie que lo cuidara, pero su historia no terminó ahí. Fue rescatado por el Centro de Bienestar Animal, donde recibió cuidados, cariño y una segunda oportunidad.\n\nAsí como Capi, muchos perros y gatos llegan a este refugio en busca de una nueva oportunidad. Aquí son atendidos con amor hasta encontrar la familia que siempre merecieron.\n\nHoy, Capi ya está en su nuevo hogar, con su familia, recibiendo amor que deja huella. 🐾", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGgOPWKRGuC/", + "commentsCount": 52, + "dimensionsHeight": 1917, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-15/481417553_17887288152209277_5059280715899732461_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=109&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=bTX3irZPj_MQ7kNvgFf5M-_&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCCCFr1Llistr8BoAQyVtD59Z7eo2nPDPJqCvshvjAH8Q&oe=67C59138&_nc_sid=8b3546", + "images": [], + "videoUrl": "https://scontent-iad3-1.cdninstagram.com/o1/v/t16/f2/m86/AQP6BVuMrkzBHQuR_k90tG8eEs8npUo-aTTR12sAj0z9y91lyhGla9Rz0GvGJK73UYjuqeXOuWrkasbI49d8HI_cMk6Vgd-8o42Z_Jg.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uY2xpcHMuYzIuNzIwLmJhc2VsaW5lIn0&_nc_cat=104&vs=1771757866889879_818784534&_nc_vs=HBksFQIYUmlnX3hwdl9yZWVsc19wZXJtYW5lbnRfc3JfcHJvZC81RTQ3RkREMURFRDgxMDU0NTFBQTQyNTJCM0ZGN0M5NV92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dHWGFzaHduOVl5MVFqRUVBRVhJQUVibG9JOTlicV9FQUFBRhUCAsgBACgAGAAbABUAACaU1crr3az7QBUCKAJDMywXQFPMzMzMzM0YEmRhc2hfYmFzZWxpbmVfMV92MREAdf4HAA%3D%3D&ccb=9-4&oh=00_AYDdttqkXywlLEhOR6PnhiUPBcY3yjn2LWg-PxEaOVW7NA&oe=67C1B1CE&_nc_sid=8b3546", + "alt": null, + "likesCount": 644, + "videoViewCount": 3528, + "timestamp": "2025-02-25T17:08:13.000Z", + "childPosts": [], + "locationName": "Parque España, Monterrey", + "locationId": "320083772038467", + "ownerUsername": "gabyoyervides_", + "ownerId": "66397953276", + "productType": "clips", + "taggedUsers": [ + { + "full_name": "Adrián de la Garza", + "id": "1483444529", + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-19/476009956_676359164713921_5513413720214908264_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7qU3J4SFzmsQ7kNvgFA1d_N&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBwX2Z48Y05vz3DmEeO9hDWESNvQQdR94D2bHmCXmUeUA&oe=67C5ACA7&_nc_sid=8b3546", + "username": "adriandelagarzas" + } + ] + }, + { + "id": "3575403831769863067", + "type": "Sidecar", + "shortCode": "DGeYtd5NpOb", + "caption": "Esta tarde tuve la grata visita en el Palacio Municipal del Cónsul General de España, Vicente J. Mas Taladriz, con quien platicamos sobre inversiones, innovación y desarrollo en sectores clave como la tecnología, la industria automotriz y las energías renovables.\n\nMonterrey sigue consolidándose como un punto estratégico para la inversión extranjera y este tipo de encuentros nos ayudan a fortalecer los lazos de cooperación. \n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGeYtd5NpOb/", + "commentsCount": 30, + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481658267_18485241997052530_2711310963634757696_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=0j8h1nwFnlwQ7kNvgF9kcS7&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTQwMzgyNTM5NDU3ODE0Ng%3D%3D.3-ccb7-5&oh=00_AYBHVANhLkdTWhn8TImooTBOlxW7x8F7wZw9GXrGMWMZ5g&oe=67C58EFA&_nc_sid=8b3546", + "images": [ + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481658267_18485241997052530_2711310963634757696_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=0j8h1nwFnlwQ7kNvgF9kcS7&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTQwMzgyNTM5NDU3ODE0Ng%3D%3D.3-ccb7-5&oh=00_AYBHVANhLkdTWhn8TImooTBOlxW7x8F7wZw9GXrGMWMZ5g&oe=67C58EFA&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481580382_18485242006052530_1951527192361425823_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=wpJ1QOmQP90Q7kNvgEfKC8O&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTQwMzgyNTM5NDQyODIyNg%3D%3D.3-ccb7-5&oh=00_AYC8jzEMkv942kALk0tYnZtXAskIbct85X7Kj-m5JuM8oQ&oe=67C5909E&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481867426_18485242015052530_8491007479040714172_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=ugO_ewLbxmgQ7kNvgG6Z10g&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTQwMzgyNTM5NDQ3MzY1NA%3D%3D.3-ccb7-5&oh=00_AYDZPb9otbtwqfeF9Nrkec7pjt29sNbeLKRfi76QHH7g7A&oe=67C59B6C&_nc_sid=8b3546" + ], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 2 people, office and text.", + "likesCount": 397, + "timestamp": "2025-02-24T23:56:12.000Z", + "childPosts": [ + { + "id": "3575403825394578146", + "type": "Image", + "shortCode": "DGeYtX9N3Li", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGeYtX9N3Li/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481658267_18485241997052530_2711310963634757696_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=0j8h1nwFnlwQ7kNvgF9kcS7&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTQwMzgyNTM5NDU3ODE0Ng%3D%3D.3-ccb7-5&oh=00_AYBHVANhLkdTWhn8TImooTBOlxW7x8F7wZw9GXrGMWMZ5g&oe=67C58EFA&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 2 people, office and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575403825394428226", + "type": "Image", + "shortCode": "DGeYtX9NSlC", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGeYtX9NSlC/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481580382_18485242006052530_1951527192361425823_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=wpJ1QOmQP90Q7kNvgEfKC8O&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTQwMzgyNTM5NDQyODIyNg%3D%3D.3-ccb7-5&oh=00_AYC8jzEMkv942kALk0tYnZtXAskIbct85X7Kj-m5JuM8oQ&oe=67C5909E&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 5 people and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575403825394473654", + "type": "Image", + "shortCode": "DGeYtX9Ndq2", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGeYtX9Ndq2/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481867426_18485242015052530_8491007479040714172_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=ugO_ewLbxmgQ7kNvgG6Z10g&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTQwMzgyNTM5NDQ3MzY1NA%3D%3D.3-ccb7-5&oh=00_AYDZPb9otbtwqfeF9Nrkec7pjt29sNbeLKRfi76QHH7g7A&oe=67C59B6C&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 2 people, flag and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + } + ], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575305859932618182", + "type": "Sidecar", + "shortCode": "DGeCbygp_3G", + "caption": "Junto a mi esposa @gabyoyervides_ hoy conocimos de cerca el gran trabajo de @destellosdeluzabp, una asociación que cambia vidas ayudando a personas con discapacidad visual.\nVamos a seguir trabajando de la mano para que más regiomontanos tengan acceso a atención oftalmológica y educación inclusiva.", + "hashtags": [], + "mentions": [ + "gabyoyervides_", + "destellosdeluzabp," + ], + "url": "https://www.instagram.com/p/DGeCbygp_3G/", + "commentsCount": 29, + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481243005_18485219815052530_4428621552175351336_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=2bdhxD2yY2kQ7kNvgFnRKcI&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDkyMzAyODI4Nw%3D%3D.3-ccb7-5&oh=00_AYASDO4_8TkmfIc7uV-HFvRnsWfooL2c1DjEABT6p4i-NA&oe=67C59B22&_nc_sid=8b3546", + "images": [ + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481243005_18485219815052530_4428621552175351336_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=2bdhxD2yY2kQ7kNvgFnRKcI&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDkyMzAyODI4Nw%3D%3D.3-ccb7-5&oh=00_AYASDO4_8TkmfIc7uV-HFvRnsWfooL2c1DjEABT6p4i-NA&oe=67C59B22&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481314095_18485219800052530_6023066712220008104_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=rXv61-nE_MAQ7kNvgGOu3Iz&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDc4ODg2MjE4Mg%3D%3D.3-ccb7-5&oh=00_AYC2_zYYVVGBCBy5BtTFebmMHJpjF_fe8dsrMXcdyXi11g&oe=67C5AB32&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481395039_18485219818052530_7408749514796137990_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Z4M1W3pBzI4Q7kNvgH-JTU6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDk3MzUwMTM5Ng%3D%3D.3-ccb7-5&oh=00_AYBZNE7pv3eWj44yG3wbE5iHS2S71mJ3JJC-lJouIxQoQg&oe=67C5ADCA&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481742547_18485219782052530_7960299857388226454_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=EEVmuvzZLzwQ7kNvgH5b9ak&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDc5NzI3MTkwOQ%3D%3D.3-ccb7-5&oh=00_AYAn__a8irWX6aeGireTc5JnqwGmtGeASPy4asFsRtFuFQ&oe=67C57D5A&_nc_sid=8b3546" + ], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 6 people, people studying, people standing, office and text.", + "likesCount": 254, + "timestamp": "2025-02-24T20:41:33.000Z", + "childPosts": [ + { + "id": "3575305850923028287", + "type": "Image", + "shortCode": "DGeCbqHpI8_", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGeCbqHpI8_/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481243005_18485219815052530_4428621552175351336_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=2bdhxD2yY2kQ7kNvgFnRKcI&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDkyMzAyODI4Nw%3D%3D.3-ccb7-5&oh=00_AYASDO4_8TkmfIc7uV-HFvRnsWfooL2c1DjEABT6p4i-NA&oe=67C59B22&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 6 people, people studying, people standing, office and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575305850788862182", + "type": "Image", + "shortCode": "DGeCbp_pVjm", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGeCbp_pVjm/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481314095_18485219800052530_6023066712220008104_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=rXv61-nE_MAQ7kNvgGOu3Iz&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDc4ODg2MjE4Mg%3D%3D.3-ccb7-5&oh=00_AYC2_zYYVVGBCBy5BtTFebmMHJpjF_fe8dsrMXcdyXi11g&oe=67C5AB32&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 1 person, childrens toy, computer keyboard and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575305850973501396", + "type": "Image", + "shortCode": "DGeCbqKprfU", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGeCbqKprfU/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481395039_18485219818052530_7408749514796137990_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Z4M1W3pBzI4Q7kNvgH-JTU6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDk3MzUwMTM5Ng%3D%3D.3-ccb7-5&oh=00_AYBZNE7pv3eWj44yG3wbE5iHS2S71mJ3JJC-lJouIxQoQg&oe=67C5ADCA&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of ‎4 people, eyewear, printer, cash machine, screen, microscope, hospital, office and ‎text that says '‎ORADO JERPO MEDICO CON DESTELLOS DE LUZ Partes del εςό ojo 2okoDgc0om Esclenótica Carmas Pupila Iris- Vitreo Retina Criatatino Mue cliiares Man ilum Nervio optico Pestañas Reali form חמישיש 내영 ស្នាំ។ Prote delos delos gafas Conducto lagrimal Descan dur durant‎'‎‎.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575305850797271909", + "type": "Image", + "shortCode": "DGeCbqAJatl", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGeCbqAJatl/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481742547_18485219782052530_7960299857388226454_n.jpg?stp=dst-jpg_e35_s1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=EEVmuvzZLzwQ7kNvgH5b9ak&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NTMwNTg1MDc5NzI3MTkwOQ%3D%3D.3-ccb7-5&oh=00_AYAn__a8irWX6aeGireTc5JnqwGmtGeASPy4asFsRtFuFQ&oe=67C57D5A&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 24, 2025. May be an image of 5 people, people studying, people standing, newsroom, office and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + } + ], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3575157746601071249", + "type": "Video", + "shortCode": "DGdgwdOJJKR", + "caption": "¡En Monterrey se Recicla y se Resuelve!\n\nMe dio mucho gusto ver y saludar a familias completas poniendo su granito de arena para nuestra ciudad. Los invitamos a que sigamos reciclando en los puntos fijos que tenemos: Parque España y Parque Canoas. \n\n¡Aquí se resuelve!", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGdgwdOJJKR/", + "commentsCount": 40, + "dimensionsHeight": 850, + "dimensionsWidth": 480, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481759656_18485179516052530_2758943356094999050_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=kElsX71VZZIQ7kNvgGrjEp_&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYAfBSMOwkf9HXqPW9UHm1Nod1PK31ewIm2lI4AdU9hLwA&oe=67C5917C&_nc_sid=8b3546", + "images": [], + "videoUrl": "https://scontent-iad3-1.cdninstagram.com/o1/v/t16/f2/m86/AQPtZ7qkIR11NCpplySxkIsU3smIfsgiFTFuRJQwUWJia9CkIqBc-KUn6cys1wNVDbZGo3cuQYrD5pKJOb93EoU2Ap6W_2hwYcpTuek.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uY2xpcHMuYzIuNzIwLmJhc2VsaW5lIn0&_nc_cat=101&vs=599855026257119_2400007891&_nc_vs=HBksFQIYUmlnX3hwdl9yZWVsc19wZXJtYW5lbnRfc3JfcHJvZC8wQzQwQzJFMzBCNkJGRDQ2MkI5ODgzMzQ3OUZFNDdCNF92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dQLUZzUnhSTGFzdXN5VUNBTXZrN2Z5QUtXTWhicV9FQUFBRhUCAsgBACgAGAAbABUAACb837jaqsC%2BPxUCKAJDMywXQFDAAAAAAAAYEmRhc2hfYmFzZWxpbmVfMV92MREAdf4HAA%3D%3D&ccb=9-4&oh=00_AYABpwn40sLbXCOH1l0Q7FxcWACtRsyqKF6Ooxn-J2fuuQ&oe=67C19055&_nc_sid=8b3546", + "alt": null, + "likesCount": 226, + "videoViewCount": 2337, + "timestamp": "2025-02-24T15:49:19.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "clips" + }, + { + "id": "3574711209371873982", + "type": "Video", + "shortCode": "DGb7OfBN7K-", + "caption": "Gracias a quienes abren su corazón y les dan una segunda oportunidad a estas mascotas.\nAdoptar es un acto de amor que deja huella❤️🐾\n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGb7OfBN7K-/", + "commentsCount": 45, + "dimensionsHeight": 1136, + "dimensionsWidth": 640, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481803383_1266972577732201_333100246445106975_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=101&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=2N4LBBs4xkoQ7kNvgHv_6s0&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYDHQO8Ovdvxs33PT5a-bUe0IRv0btsixrYBJMSdEM5l6w&oe=67C5A637&_nc_sid=8b3546", + "images": [], + "videoUrl": "https://scontent-iad3-2.cdninstagram.com/o1/v/t16/f2/m86/AQOMb3e5yBL-Whasd1HzjTOHUqCJ6H4TQbj6zZd2Obr8T3u61WJOE6WNFg5wh52B6GEoXWk0KE49fo4gbChjUPvE-HsfkdIoGC_FkDs.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uY2xpcHMuYzIuNzIwLmJhc2VsaW5lIn0&_nc_cat=103&vs=1650765472991661_3043598127&_nc_vs=HBksFQIYUmlnX3hwdl9yZWVsc19wZXJtYW5lbnRfc3JfcHJvZC8zRTQ2OTFCNTBFQUNFNjFDQzFBMjI1MDBGQ0U3N0I5RV92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dMR05vQndWTGN3X1lMY0VBR2lMdW1QcHJNY2RicV9FQUFBRhUCAsgBACgAGAAbABUAACaC5eiXiMS2PxUCKAJDMywXQEhzMzMzMzMYEmRhc2hfYmFzZWxpbmVfMV92MREAdf4HAA%3D%3D&ccb=9-4&oh=00_AYC2eNEX1RIwRu5fwau_ko6DOkMpQUFg8zdPR8EzO2Uf0g&oe=67C1BB72&_nc_sid=8b3546", + "alt": null, + "likesCount": 337, + "videoViewCount": 2578, + "timestamp": "2025-02-24T01:00:48.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "clips" + }, + { + "id": "3574625916672107235", + "type": "Sidecar", + "shortCode": "DGbn1UAJWLj", + "caption": "Este domingo mi esposa @gabyoyervides_ y yo vivimos una mañana llena de alegría en la Feria de Adopciones “Amor que deja huella”. \n\nGracias a las familias que adoptaron a 22 perritos y 5 gatos que hoy dormirán en un hogar llenos de cariño.\n\nAdoptar es cambiar una vida y, a la vez, llenar la nuestra de amor incondicional.\n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [ + "gabyoyervides_" + ], + "url": "https://www.instagram.com/p/DGbn1UAJWLj/", + "commentsCount": 86, + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481598628_18485050462052530_6943890238376007619_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=I9jwb-13R0QQ7kNvgH0moIC&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQzMTcwNjc5NA%3D%3D.3-ccb7-5&oh=00_AYDD57d0xNUY2NUF0dlpu4cB93TZJKM6mscUK5T9RI5t2g&oe=67C59830&_nc_sid=8b3546", + "images": [ + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481598628_18485050462052530_6943890238376007619_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=I9jwb-13R0QQ7kNvgH0moIC&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQzMTcwNjc5NA%3D%3D.3-ccb7-5&oh=00_AYDD57d0xNUY2NUF0dlpu4cB93TZJKM6mscUK5T9RI5t2g&oe=67C59830&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481179980_18485050471052530_4789787889477849374_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=rTIExQ2ujxkQ7kNvgHdArof&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQzMTcwNzIyMg%3D%3D.3-ccb7-5&oh=00_AYAfxJne4j0-OSjgAhb0INbt7MWy-j6QpzOMY6ilBOAgZA&oe=67C57B54&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481036850_18485050480052530_4263919696116033175_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=d5ESLIJu46IQ7kNvgE7lcPN&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM4OTk1NTY2NQ%3D%3D.3-ccb7-5&oh=00_AYCEr1TKW1lTq2Oaqso2YHSWD7RCXg9J9n22kkXjAuPexw&oe=67C585C2&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480868090_18485050489052530_9177445398933850852_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=YX0aCwViBQMQ7kNvgHnvLnj&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODM2NDU3MQ%3D%3D.3-ccb7-5&oh=00_AYBKOCIeazRGN1y_OrIztNy6ecypSr0mzhTFdYI2-HY-lA&oe=67C5AB89&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481386245_18485050498052530_8146167078313545440_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=bO3YIfa3518Q7kNvgGOPNBu&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQ0ODQ2NjIwNg%3D%3D.3-ccb7-5&oh=00_AYDIFhOojcVTsTbg4D_QHj1ITEdWG00DGnDsWWH3Vc4pOg&oe=67C5AE42&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481232883_18485050507052530_4811447304630849245_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=OIoSmYr0bRwQ7kNvgHmBDHW&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODM3MDgzNQ%3D%3D.3-ccb7-5&oh=00_AYDjxQeSjHSO--TOud6b_5M444GyxuN_tSzIxA3ZT9OGEw&oe=67C5A248&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481338978_18485050516052530_2521269563541274121_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=f1O-YNhao_EQ7kNvgGM20mu&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODI3NzU3OA%3D%3D.3-ccb7-5&oh=00_AYDU8rmqeeY0ebSel7iPW4_yd62Df2xyrnJgSqbcokuP_w&oe=67C57B1C&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481038573_18485050525052530_3975497264038752483_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=kSDh72mg6QIQ7kNvgHy9mVR&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQ0ODY1MjIyMg%3D%3D.3-ccb7-5&oh=00_AYDyEkzFX997_3LB4tWty6pT-IVXUU6B4Wk78zcbPdFKIA&oe=67C59A99&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481052680_18485050534052530_4887264455262851473_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=4YEAiPVCVxoQ7kNvgFjU9GM&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODE4ODE0MQ%3D%3D.3-ccb7-5&oh=00_AYAtkmHLCat-INhkpgMC7gaoRJO3L_NXKQhepWAjBT019Q&oe=67C582EA&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481015818_18485050543052530_6626002521994330049_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7dUocOjrI5oQ7kNvgE-AG68&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODMyODU1OQ%3D%3D.3-ccb7-5&oh=00_AYAgjRRmadmUWogDGD2EH_DSTl4R1nA1-WX8rmIdJWk2vg&oe=67C596E0&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481010469_18485050552052530_1032545387524868505_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=vKPDHTOGhH4Q7kNvgGVULJH&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM4MTQ0NzI0Mg%3D%3D.3-ccb7-5&oh=00_AYDmsAkCyAB4JiMx1fUyHH07YuUsclXP0QtoV_YNhw1i7Q&oe=67C592EF&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481237231_18485050561052530_5902898244339317184_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=8AqKlSnpLpUQ7kNvgE8TltX&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODM3NDgyMg%3D%3D.3-ccb7-5&oh=00_AYD-4SmXh9q1bZb2Z-BTjXUgMgG23ONerD75WLK2dNQaxQ&oe=67C593BD&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481311990_18485050570052530_4621891136551024473_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=8Y6W8crQxfAQ7kNvgHhkji6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQzMTkyODM3NQ%3D%3D.3-ccb7-5&oh=00_AYBXuyWntA4Jm7Nlvt_mNXVF4h665s7Yx7ljl-L9IcCqhQ&oe=67C589DE&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481056039_18485050579052530_5718361635390257069_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=ZuJ1nEirc9gQ7kNvgEgtn_T&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQ0ODQ1NDIyOQ%3D%3D.3-ccb7-5&oh=00_AYB2N9Lj2TR1gJ6Rx-s4l5k9L2gXZ1I5kmg3hb1gAkaiLA&oe=67C598CA&_nc_sid=8b3546" + ], + "alt": "Photo shared by Adrián de la Garza on February 23, 2025 tagging @gabyoyervides_. May be an image of 2 people and text.", + "likesCount": 1097, + "timestamp": "2025-02-23T22:10:37.000Z", + "childPosts": [ + { + "id": "3574625900431706794", + "type": "Image", + "shortCode": "DGbn1E4JIqq", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E4JIqq/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481598628_18485050462052530_6943890238376007619_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=I9jwb-13R0QQ7kNvgH0moIC&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQzMTcwNjc5NA%3D%3D.3-ccb7-5&oh=00_AYDD57d0xNUY2NUF0dlpu4cB93TZJKM6mscUK5T9RI5t2g&oe=67C59830&_nc_sid=8b3546", + "images": [], + "alt": "Photo shared by Adrián de la Garza on February 23, 2025 tagging @gabyoyervides_. May be an image of 2 people and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "taggedUsers": [ + { + "full_name": "Gaby Oyervides", + "id": "66397953276", + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/462483519_1201551891079625_5500174295318152454_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=105&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=44rsWGmzxpoQ7kNvgENMYq6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBFfhFavBUqpzo4ghlMpuB5SMPEBlcVoHS7981IKPxXTg&oe=67C5A89C&_nc_sid=8b3546", + "username": "gabyoyervides_" + } + ] + }, + { + "id": "3574625900431707222", + "type": "Image", + "shortCode": "DGbn1E4JIxW", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E4JIxW/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481179980_18485050471052530_4789787889477849374_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=rTIExQ2ujxkQ7kNvgHdArof&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQzMTcwNzIyMg%3D%3D.3-ccb7-5&oh=00_AYAfxJne4j0-OSjgAhb0INbt7MWy-j6QpzOMY6ilBOAgZA&oe=67C57B54&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of dog and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900389955665", + "type": "Image", + "shortCode": "DGbn1E1p3hR", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E1p3hR/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1350, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481036850_18485050480052530_4263919696116033175_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=d5ESLIJu46IQ7kNvgE7lcPN&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM4OTk1NTY2NQ%3D%3D.3-ccb7-5&oh=00_AYCEr1TKW1lTq2Oaqso2YHSWD7RCXg9J9n22kkXjAuPexw&oe=67C585C2&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 1 person, collie and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900398364571", + "type": "Image", + "shortCode": "DGbn1E2J8eb", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E2J8eb/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480868090_18485050489052530_9177445398933850852_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=YX0aCwViBQMQ7kNvgHnvLnj&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODM2NDU3MQ%3D%3D.3-ccb7-5&oh=00_AYBKOCIeazRGN1y_OrIztNy6ecypSr0mzhTFdYI2-HY-lA&oe=67C5AB89&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 1 person, chihuahua, collie and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900448466206", + "type": "Image", + "shortCode": "DGbn1E5JEUe", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E5JEUe/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481386245_18485050498052530_8146167078313545440_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=bO3YIfa3518Q7kNvgGOPNBu&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQ0ODQ2NjIwNg%3D%3D.3-ccb7-5&oh=00_AYDIFhOojcVTsTbg4D_QHj1ITEdWG00DGnDsWWH3Vc4pOg&oe=67C5AE42&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of corgi, collie, bandanna and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900398370835", + "type": "Image", + "shortCode": "DGbn1E2J-AT", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E2J-AT/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481232883_18485050507052530_4811447304630849245_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=OIoSmYr0bRwQ7kNvgHmBDHW&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODM3MDgzNQ%3D%3D.3-ccb7-5&oh=00_AYDjxQeSjHSO--TOud6b_5M444GyxuN_tSzIxA3ZT9OGEw&oe=67C5A248&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 2 people and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900398277578", + "type": "Image", + "shortCode": "DGbn1E2JnPK", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E2JnPK/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481338978_18485050516052530_2521269563541274121_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=f1O-YNhao_EQ7kNvgGM20mu&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODI3NzU3OA%3D%3D.3-ccb7-5&oh=00_AYDU8rmqeeY0ebSel7iPW4_yd62Df2xyrnJgSqbcokuP_w&oe=67C57B1C&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of chihuahua, bandanna and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900448652222", + "type": "Image", + "shortCode": "DGbn1E5Jxu-", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E5Jxu-/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481038573_18485050525052530_3975497264038752483_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=kSDh72mg6QIQ7kNvgHy9mVR&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQ0ODY1MjIyMg%3D%3D.3-ccb7-5&oh=00_AYDyEkzFX997_3LB4tWty6pT-IVXUU6B4Wk78zcbPdFKIA&oe=67C59A99&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of mastiff, collie, bandanna and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900398188141", + "type": "Image", + "shortCode": "DGbn1E2JRZt", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E2JRZt/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481052680_18485050534052530_4887264455262851473_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=4YEAiPVCVxoQ7kNvgFjU9GM&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODE4ODE0MQ%3D%3D.3-ccb7-5&oh=00_AYAtkmHLCat-INhkpgMC7gaoRJO3L_NXKQhepWAjBT019Q&oe=67C582EA&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 2 people, dog and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900398328559", + "type": "Image", + "shortCode": "DGbn1E2Jzrv", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E2Jzrv/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481015818_18485050543052530_6626002521994330049_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7dUocOjrI5oQ7kNvgE-AG68&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODMyODU1OQ%3D%3D.3-ccb7-5&oh=00_AYAgjRRmadmUWogDGD2EH_DSTl4R1nA1-WX8rmIdJWk2vg&oe=67C596E0&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 1 person, dog and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900381447242", + "type": "Image", + "shortCode": "DGbn1E1JaRK", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E1JaRK/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481010469_18485050552052530_1032545387524868505_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=vKPDHTOGhH4Q7kNvgGVULJH&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM4MTQ0NzI0Mg%3D%3D.3-ccb7-5&oh=00_AYDmsAkCyAB4JiMx1fUyHH07YuUsclXP0QtoV_YNhw1i7Q&oe=67C592EF&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 7 people, dog and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900398374822", + "type": "Image", + "shortCode": "DGbn1E2J--m", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E2J--m/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481237231_18485050561052530_5902898244339317184_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=8AqKlSnpLpUQ7kNvgE8TltX&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDM5ODM3NDgyMg%3D%3D.3-ccb7-5&oh=00_AYD-4SmXh9q1bZb2Z-BTjXUgMgG23ONerD75WLK2dNQaxQ&oe=67C593BD&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 1 person, collie, petfood and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900431928375", + "type": "Image", + "shortCode": "DGbn1E4J-w3", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E4J-w3/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481311990_18485050570052530_4621891136551024473_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=8Y6W8crQxfAQ7kNvgHhkji6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQzMTkyODM3NQ%3D%3D.3-ccb7-5&oh=00_AYBXuyWntA4Jm7Nlvt_mNXVF4h665s7Yx7ljl-L9IcCqhQ&oe=67C589DE&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 1 person, collie, corgi and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3574625900448454229", + "type": "Image", + "shortCode": "DGbn1E5JBZV", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGbn1E5JBZV/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 1349, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481056039_18485050579052530_5718361635390257069_n.jpg?stp=dst-jpg_e35_p1080x1080_sh0.08_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=ZuJ1nEirc9gQ7kNvgEgtn_T&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3NDYyNTkwMDQ0ODQ1NDIyOQ%3D%3D.3-ccb7-5&oh=00_AYB2N9Lj2TR1gJ6Rx-s4l5k9L2gXZ1I5kmg3hb1gAkaiLA&oe=67C598CA&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 23, 2025. May be an image of 3 people, crowd and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + } + ], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "taggedUsers": [ + { + "full_name": "Gaby Oyervides", + "id": "66397953276", + "is_verified": false, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/462483519_1201551891079625_5500174295318152454_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=105&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=44rsWGmzxpoQ7kNvgENMYq6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBFfhFavBUqpzo4ghlMpuB5SMPEBlcVoHS7981IKPxXTg&oe=67C5A89C&_nc_sid=8b3546", + "username": "gabyoyervides_" + } + ] + }, + { + "id": "3573893024685937859", + "type": "Sidecar", + "shortCode": "DGZBMVJptTD", + "caption": "¡En Monterrey se recicla y se resuelve!♻️\n\nEste sábado sumamos esfuerzos con empresas locales y vecinos de la zona poniente, recolectando toneladas de materiales reciclables.\n\nSeguimos trabajando por un Monterrey más limpio y sustentable. \nVisita nuestros puntos fijos de reciclaje.\n📍 Parque Tucán \n📍 Parque España\n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMVJptTD/", + "commentsCount": 35, + "dimensionsHeight": 717, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481209676_18484859230052530_1587082404481791868_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=nDc0Y_h05y4Q7kNvgEs1HUi&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzQ1MjQ4Mg%3D%3D.3-ccb7-5&oh=00_AYBauhiag9AlOM3RLA1vsgWus5QEXgIiDjx8s26WbTyP8g&oe=67C5B0B6&_nc_sid=8b3546", + "images": [ + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481209676_18484859230052530_1587082404481791868_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=nDc0Y_h05y4Q7kNvgEs1HUi&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzQ1MjQ4Mg%3D%3D.3-ccb7-5&oh=00_AYBauhiag9AlOM3RLA1vsgWus5QEXgIiDjx8s26WbTyP8g&oe=67C5B0B6&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481011442_18484859158052530_7997298743970503748_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Av-C9YwPtMoQ7kNvgHjiWER&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTE5NjkyNjA0Ng%3D%3D.3-ccb7-5&oh=00_AYA5_ktS3oNl4oVOzkkPHS_ggJroyk5WBymfcAHRq6vv1Q&oe=67C5A26D&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480993782_18484859173052530_5548552597408632076_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=AHWrlkHK47UQ7kNvgHxnKwo&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIwNTQzMDA2OA%3D%3D.3-ccb7-5&oh=00_AYCwiib26y-h0_F9iJZlXYglyOaPyFxUPqCYSICQVzr_EQ&oe=67C58DAC&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481224022_18484859176052530_613921378657991458_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=gFFSxb1ta3AQ7kNvgFqxIQO&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzM2NTI3OA%3D%3D.3-ccb7-5&oh=00_AYBxXt8NomCXOc8nBInEkw2_gP03-EPTn5JkfjfvnPJYfQ&oe=67C59FAA&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481058596_18484859185052530_6222593389900532574_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=bPg6WSmb9JQQ7kNvgFNCM54&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI1NTcyNTcwMQ%3D%3D.3-ccb7-5&oh=00_AYDD8-I7_5V4qs3GkZ0e9A3CEeI9ShUn2xx0Wku8Fz9r2Q&oe=67C59518&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481005038_18484859221052530_7277507886567056117_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=HKUaR9ZcQwcQ7kNvgHzZOuu&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIwNTUwNzg4Ng%3D%3D.3-ccb7-5&oh=00_AYAkJ6P47rtsIJoyPjNtDhMcLdnDmZBjAGuGQLa-IDNPlw&oe=67C5AE32&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481025208_18484859206052530_8229519256527118304_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=5oug2TofT4IQ7kNvgGGIM52&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI1NTYyNzg0MQ%3D%3D.3-ccb7-5&oh=00_AYBAeHZScpnq4kJBzakH3RwlOQhlV0W3O_qP18hstLHwng&oe=67C57B4A&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481160160_18484859224052530_6677757374498163832_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Kx1tixMj6JUQ7kNvgFN6FYp&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIxMzgwNDMwMQ%3D%3D.3-ccb7-5&oh=00_AYD6jTZwVP-j4L2G0IfeakYtP6i0_ozaFl93LgH0QXBQfA&oe=67C5A8CD&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481757068_18484859239052530_7694872054091715576_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=s3u_AMY96CYQ7kNvgFCYhcR&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzI4NDIyMA%3D%3D.3-ccb7-5&oh=00_AYCstSJv8W1h6teJQkwjq2g4yjgRYzcuYZ-9379OibsdpA&oe=67C596A5&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481394608_18484859236052530_3558149599907401765_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=3MtJKn4LT-UQ7kNvgEYg2t6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIwNTQyOTMwNQ%3D%3D.3-ccb7-5&oh=00_AYCS-1n5PjOFCsCt3Jnng6Pk_RYNPrn1i1k5LmaCiAy4wQ&oe=67C57919&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481010738_18484859248052530_1612583066444476718_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=gMjd4I5eUkkQ7kNvgH8P1f0&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIxMzg4ODgxMQ%3D%3D.3-ccb7-5&oh=00_AYBy1noZZ4dGKUNYcLrihTyP_eGg14FjZNj5z76aZS4yqA&oe=67C5A176&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480879707_18484859257052530_8380999794156791663_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=nLLILkvDLwQQ7kNvgHwo2IB&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzQwNDQxMg%3D%3D.3-ccb7-5&oh=00_AYDJMK6B_-oegdfdkCv9ZDX_IXWTmAjNTdIvExFak0R81w&oe=67C59939&_nc_sid=8b3546" + ], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 4 people, people racing vehicles, plastic bag, garbage, road and text that says 'CIC BCC C MTY BO REI 279円 79'.", + "likesCount": 297, + "timestamp": "2025-02-22T21:54:30.000Z", + "childPosts": [ + { + "id": "3573893011247452482", + "type": "Image", + "shortCode": "DGZBMIop9FC", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMIop9FC/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 717, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481209676_18484859230052530_1587082404481791868_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=nDc0Y_h05y4Q7kNvgEs1HUi&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzQ1MjQ4Mg%3D%3D.3-ccb7-5&oh=00_AYBauhiag9AlOM3RLA1vsgWus5QEXgIiDjx8s26WbTyP8g&oe=67C5B0B6&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 4 people, people racing vehicles, plastic bag, garbage, road and text that says 'CIC BCC C MTY BO REI 279円 79'.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011196926046", + "type": "Image", + "shortCode": "DGZBMIlpNhe", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMIlpNhe/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481011442_18484859158052530_7997298743970503748_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Av-C9YwPtMoQ7kNvgHjiWER&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTE5NjkyNjA0Ng%3D%3D.3-ccb7-5&oh=00_AYA5_ktS3oNl4oVOzkkPHS_ggJroyk5WBymfcAHRq6vv1Q&oe=67C5A26D&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 5 people, people racing vehicles and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011205430068", + "type": "Image", + "shortCode": "DGZBMImJps0", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMImJps0/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480993782_18484859173052530_5548552597408632076_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=AHWrlkHK47UQ7kNvgHxnKwo&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIwNTQzMDA2OA%3D%3D.3-ccb7-5&oh=00_AYCwiib26y-h0_F9iJZlXYglyOaPyFxUPqCYSICQVzr_EQ&oe=67C58DAC&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 7 people, people standing and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011247365278", + "type": "Image", + "shortCode": "DGZBMIopnye", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMIopnye/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481224022_18484859176052530_613921378657991458_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=gFFSxb1ta3AQ7kNvgFqxIQO&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzM2NTI3OA%3D%3D.3-ccb7-5&oh=00_AYBxXt8NomCXOc8nBInEkw2_gP03-EPTn5JkfjfvnPJYfQ&oe=67C59FAA&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 4 people, people standing and text that says 'ALS Dark VE'.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011255725701", + "type": "Image", + "shortCode": "DGZBMIpJg6F", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMIpJg6F/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481058596_18484859185052530_6222593389900532574_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=bPg6WSmb9JQQ7kNvgFNCM54&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI1NTcyNTcwMQ%3D%3D.3-ccb7-5&oh=00_AYDD8-I7_5V4qs3GkZ0e9A3CEeI9ShUn2xx0Wku8Fz9r2Q&oe=67C59518&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 1 person, plastic bag, garbage and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011205507886", + "type": "Image", + "shortCode": "DGZBMImJ8su", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMImJ8su/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481005038_18484859221052530_7277507886567056117_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=HKUaR9ZcQwcQ7kNvgHzZOuu&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIwNTUwNzg4Ng%3D%3D.3-ccb7-5&oh=00_AYAkJ6P47rtsIJoyPjNtDhMcLdnDmZBjAGuGQLa-IDNPlw&oe=67C5AE32&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 5 people, people racing vehicles and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011255627841", + "type": "Image", + "shortCode": "DGZBMIpJJBB", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMIpJJBB/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481025208_18484859206052530_8229519256527118304_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=5oug2TofT4IQ7kNvgGGIM52&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI1NTYyNzg0MQ%3D%3D.3-ccb7-5&oh=00_AYBAeHZScpnq4kJBzakH3RwlOQhlV0W3O_qP18hstLHwng&oe=67C57B4A&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 7 people, minivan, windshield, wheel, sedan and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011213804301", + "type": "Image", + "shortCode": "DGZBMImpmMN", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMImpmMN/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481160160_18484859224052530_6677757374498163832_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Kx1tixMj6JUQ7kNvgFN6FYp&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIxMzgwNDMwMQ%3D%3D.3-ccb7-5&oh=00_AYD6jTZwVP-j4L2G0IfeakYtP6i0_ozaFl93LgH0QXBQfA&oe=67C5A8CD&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 3 people, people standing, newspaper, carton, road and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011247284220", + "type": "Image", + "shortCode": "DGZBMIopT_8", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMIopT_8/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481757068_18484859239052530_7694872054091715576_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=s3u_AMY96CYQ7kNvgFCYhcR&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzI4NDIyMA%3D%3D.3-ccb7-5&oh=00_AYCstSJv8W1h6teJQkwjq2g4yjgRYzcuYZ-9379OibsdpA&oe=67C596A5&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 4 people, speaker, camera, generator, telescope and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011205429305", + "type": "Image", + "shortCode": "DGZBMImJpg5", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMImJpg5/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481394608_18484859236052530_3558149599907401765_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=3MtJKn4LT-UQ7kNvgEYg2t6&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIwNTQyOTMwNQ%3D%3D.3-ccb7-5&oh=00_AYCS-1n5PjOFCsCt3Jnng6Pk_RYNPrn1i1k5LmaCiAy4wQ&oe=67C57919&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of ‎1 person, garbage, carton, plastic bag and ‎text that says '‎خسو Este RECICLA RESUELVE | AQUI SE RESUELVE MTY un programa de MTY SOSTENIBLE‎'‎‎.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011213888811", + "type": "Image", + "shortCode": "DGZBMImp60r", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMImp60r/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481010738_18484859248052530_1612583066444476718_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=gMjd4I5eUkkQ7kNvgH8P1f0&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTIxMzg4ODgxMQ%3D%3D.3-ccb7-5&oh=00_AYBy1noZZ4dGKUNYcLrihTyP_eGg14FjZNj5z76aZS4yqA&oe=67C5A176&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of ‎1 person, house plant, pitcher plant and ‎text that says '‎JELAVE RECICLA MANDE RECICLAJE RECICLA ده REGISTRO V ፈሃል SE‎'‎‎.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573893011247404412", + "type": "Image", + "shortCode": "DGZBMIopxV8", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGZBMIopxV8/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 718, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480879707_18484859257052530_8380999794156791663_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=nLLILkvDLwQQ7kNvgHwo2IB&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3Mzg5MzAxMTI0NzQwNDQxMg%3D%3D.3-ccb7-5&oh=00_AYDJMK6B_-oegdfdkCv9ZDX_IXWTmAjNTdIvExFak0R81w&oe=67C59939&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 22, 2025. May be an image of 3 people, garbage, petfood, trailer and text that says 'PAPEL ARTON RECICLA UTENS UTENSLIOS LIOS DECOCINA DE co SINA RECICLA PET 米 នល - NTY T リー RECICLA MTY ι RESUELVE \"\"\" n*ИT YNOSTENIR SOSTENIBLE'.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + } + ], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573206712391658984", + "type": "Sidecar", + "shortCode": "DGWlJLBJW3o", + "caption": "Hoy en Sesión Ordinaria de Cabildo aprobamos a las ganadoras del reconocimiento público “Mujer que Inspira 2025”, una medalla a ocho extraordinarias regiomontanas por su impacto en la ciencia, el arte, el emprendimiento y el compromiso social.\n\nLa entrega será en marzo en una Sesión Solemne. \n\nTambién aprobamos la convocatoria para la Medalla al Mérito Deportivo 2025, así que si conoces a alguien que haya dejado huella en el deporte, ¡postúlalo antes del 17 de abril! \n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGWlJLBJW3o/", + "commentsCount": 27, + "dimensionsHeight": 719, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481023355_18484679389052530_6140187690128957417_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=a1f72bmQ4uUQ7kNvgF77bu8&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI3MTU2MzM1Ng%3D%3D.3-ccb7-5&oh=00_AYC2-QtZtVs6m11dO1i8btXMsWbGr1N7jEspX_2qV9sYEg&oe=67C581F9&_nc_sid=8b3546", + "images": [ + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481023355_18484679389052530_6140187690128957417_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=a1f72bmQ4uUQ7kNvgF77bu8&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI3MTU2MzM1Ng%3D%3D.3-ccb7-5&oh=00_AYC2-QtZtVs6m11dO1i8btXMsWbGr1N7jEspX_2qV9sYEg&oe=67C581F9&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481021674_18484679401052530_4672475523107727628_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7IhtZupbkCUQ7kNvgF8_Os_&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI4ODI2Mjc4Mg%3D%3D.3-ccb7-5&oh=00_AYAnsx8dFbpFMPXmjdjHjLsak8tqlUs_4s65Qpb9iKTNcA&oe=67C5AAE9&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481609757_18484679434052530_1002587209572338076_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=hhpLXg4JEZQQ7kNvgGxmRlx&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDMxMzQwMzIwMA%3D%3D.3-ccb7-5&oh=00_AYAT3dqo8B8kW2WBIGNNV-3DBLRGypMnQ65dji1WrzTNCQ&oe=67C59443&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480769572_18484679410052530_2367166304843383149_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Uq8uYFpT1t8Q7kNvgGjjZ8e&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI4MDAxNTg5MA%3D%3D.3-ccb7-5&oh=00_AYDE43y_h6Q5dbW9EYI7_dk0uP5ioKKL94IvcYAGmEDnIg&oe=67C5AB6F&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481090656_18484679419052530_7908906074502398054_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Iup4puNVgE0Q7kNvgGeBkmP&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI5Njc0MjUxNA%3D%3D.3-ccb7-5&oh=00_AYAUVyYjvzZh-KW2ZZQgy3hTS60_v-NAKLetLb6oTyowXQ&oe=67C59AED&_nc_sid=8b3546", + "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481089515_18484679437052530_6214755354493708923_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Sa30U2quhqoQ7kNvgGUyP0m&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI0NjM4NzUwMA%3D%3D.3-ccb7-5&oh=00_AYD3lMJiiqS8uebFJxTO0c5JGsawcK2qKqc6bCyl-0Qcmw&oe=67C5821F&_nc_sid=8b3546" + ], + "alt": "Photo by Adrián de la Garza on February 21, 2025. May be an image of 3 people, people standing, suit, blazer, dinner jacket and text.", + "likesCount": 663, + "timestamp": "2025-02-21T23:10:55.000Z", + "childPosts": [ + { + "id": "3573206704271563356", + "type": "Image", + "shortCode": "DGWlJDdJppc", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGWlJDdJppc/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 719, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481023355_18484679389052530_6140187690128957417_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=a1f72bmQ4uUQ7kNvgF77bu8&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI3MTU2MzM1Ng%3D%3D.3-ccb7-5&oh=00_AYC2-QtZtVs6m11dO1i8btXMsWbGr1N7jEspX_2qV9sYEg&oe=67C581F9&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 21, 2025. May be an image of 3 people, people standing, suit, blazer, dinner jacket and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573206704288262782", + "type": "Image", + "shortCode": "DGWlJDeJWp-", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGWlJDeJWp-/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481021674_18484679401052530_4672475523107727628_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=7IhtZupbkCUQ7kNvgF8_Os_&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI4ODI2Mjc4Mg%3D%3D.3-ccb7-5&oh=00_AYAnsx8dFbpFMPXmjdjHjLsak8tqlUs_4s65Qpb9iKTNcA&oe=67C5AAE9&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 21, 2025. May be an image of 3 people, office and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573206704313403200", + "type": "Image", + "shortCode": "DGWlJDfpQdA", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGWlJDfpQdA/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481609757_18484679434052530_1002587209572338076_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=hhpLXg4JEZQQ7kNvgGxmRlx&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDMxMzQwMzIwMA%3D%3D.3-ccb7-5&oh=00_AYAT3dqo8B8kW2WBIGNNV-3DBLRGypMnQ65dji1WrzTNCQ&oe=67C59443&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 21, 2025. May be an image of 12 people and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573206704280015890", + "type": "Image", + "shortCode": "DGWlJDdp5QS", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGWlJDdp5QS/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480769572_18484679410052530_2367166304843383149_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Uq8uYFpT1t8Q7kNvgGjjZ8e&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI4MDAxNTg5MA%3D%3D.3-ccb7-5&oh=00_AYDE43y_h6Q5dbW9EYI7_dk0uP5ioKKL94IvcYAGmEDnIg&oe=67C5AB6F&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 21, 2025. May be an image of 8 people and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573206704296742514", + "type": "Image", + "shortCode": "DGWlJDeps5y", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGWlJDeps5y/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481090656_18484679419052530_7908906074502398054_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Iup4puNVgE0Q7kNvgGeBkmP&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI5Njc0MjUxNA%3D%3D.3-ccb7-5&oh=00_AYAUVyYjvzZh-KW2ZZQgy3hTS60_v-NAKLetLb6oTyowXQ&oe=67C59AED&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 21, 2025. May be an image of 6 people, people standing, office and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3573206704246387500", + "type": "Image", + "shortCode": "DGWlJDbpnMs", + "caption": "", + "hashtags": [], + "mentions": [], + "url": "https://www.instagram.com/p/DGWlJDbpnMs/", + "commentsCount": 0, + "firstComment": "", + "latestComments": [], + "dimensionsHeight": 720, + "dimensionsWidth": 1080, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481089515_18484679437052530_6214755354493708923_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=Sa30U2quhqoQ7kNvgGUyP0m&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&ig_cache_key=MzU3MzIwNjcwNDI0NjM4NzUwMA%3D%3D.3-ccb7-5&oh=00_AYD3lMJiiqS8uebFJxTO0c5JGsawcK2qKqc6bCyl-0Qcmw&oe=67C5821F&_nc_sid=8b3546", + "images": [], + "alt": "Photo by Adrián de la Garza on February 21, 2025. May be an image of 2 people, crowd and text.", + "likesCount": null, + "timestamp": null, + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + } + ], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529" + }, + { + "id": "3572663527829586595", + "type": "Video", + "shortCode": "DGUpoy-RsKj", + "caption": "Ciudad Deportiva se transforma para el bien de todos los regios.\n\nEl día de hoy supervisé las obras de remodelación para que Ciudad Deportiva cuente con espacios dignos y seguros para todos los atletas. \n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGUpoy-RsKj/", + "commentsCount": 50, + "dimensionsHeight": 850, + "dimensionsWidth": 480, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/481398042_18484542001052530_8988623957150353881_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=ZHIP4YZZkhcQ7kNvgHQ6EM8&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYBr5wTRTS9e9jNd-T-BfkPNsKO9zLqk7HUs6JnrYUuFBQ&oe=67C57C19&_nc_sid=8b3546", + "images": [], + "videoUrl": "https://scontent-iad3-2.cdninstagram.com/o1/v/t16/f2/m86/AQOhnxHcSxYyObhGvCIrJRyJowTaneahs5NRFeoW8po5s2pe4YYw1zZB_YlMA7OoX68dys7IyWnF1AK5uWR0o7m9G0jkBGkB7HnJ2TA.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uY2xpcHMuYzIuNzIwLmJhc2VsaW5lIn0&_nc_cat=111&vs=2745584712284435_625181183&_nc_vs=HBksFQIYUmlnX3hwdl9yZWVsc19wZXJtYW5lbnRfc3JfcHJvZC81NTQ2QzZBMjA3OTFGRTY2NkFCQThENkQ2MjhEQTZCNV92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dENjhwaHhQUC1CY3R5c0hBTTRkOEdncXBPeGRicV9FQUFBRhUCAsgBACgAGAAbABUAACa2t%2B6q%2Bs2pQRUCKAJDMywXQFmEOVgQYk4YEmRhc2hfYmFzZWxpbmVfMV92MREAdf4HAA%3D%3D&ccb=9-4&oh=00_AYDU6GrZOdJLvzhna_OwK8XaBqwdNNTqq3i7KHv9tKgoQg&oe=67C1A5AC&_nc_sid=8b3546", + "alt": null, + "likesCount": 792, + "videoViewCount": 10364, + "timestamp": "2025-02-21T05:13:06.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "clips", + "taggedUsers": [ + { + "full_name": "aldodenigris", + "id": "55883830", + "is_verified": true, + "profile_pic_url": "https://scontent-iad3-2.cdninstagram.com/v/t51.2885-19/371750700_852089726562390_1994334907073070657_n.jpg?stp=dst-jpg_e0_s150x150_tt6&_nc_ht=scontent-iad3-2.cdninstagram.com&_nc_cat=106&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=WvhBgIuJwh4Q7kNvgHuaXzV&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYDo-Un_uUmPUf2aXtvkelUoSZmy-a7RGhq-muttU2o-Mg&oe=67C5851D&_nc_sid=8b3546", + "username": "aldodenigris" + } + ] + }, + { + "id": "3572453492912528761", + "type": "Video", + "shortCode": "DGT54Ytp515", + "caption": "Hoy recibí al Comisionado Omar Amador Escobar en la Academia de Policía y el C4 para seguir fortaleciendo nuestra estrategia de seguridad.\n\nCon esto seguimos trabajando en la coordinación con gobierno Federal, donde trabajaremos para combatir el crimen y proteger a las familias de Monterrey.\n\n#AquíSeResuelve", + "hashtags": [ + "AquíSeResuelve" + ], + "mentions": [], + "url": "https://www.instagram.com/p/DGT54Ytp515/", + "commentsCount": 79, + "dimensionsHeight": 1333, + "dimensionsWidth": 750, + "displayUrl": "https://scontent-iad3-1.cdninstagram.com/v/t51.2885-15/480768883_18484496086052530_282359645328069450_n.jpg?stp=dst-jpg_e15_tt6&_nc_ht=scontent-iad3-1.cdninstagram.com&_nc_cat=110&_nc_oc=Q6cZ2AHTCOaXaa4bEoXv0D9g71V70sAhSjkFIbY5nVJH4IoOROFPs-ZZeKCtLYAfmehSwm2-4V46Zl_uAZfViXeQnfAp&_nc_ohc=zhpGLfTmBckQ7kNvgFalOLX&_nc_gid=6d9266d3ac954e73877152693c6c42f8&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AYCv4A3cx6s-dnB4du_dJT88wT8ERO79LSd6PotjwvLNNg&oe=67C58ACE&_nc_sid=8b3546", + "images": [], + "videoUrl": "https://scontent-iad3-1.cdninstagram.com/o1/v/t16/f2/m86/AQPuxNrdQ76Ieh46OUihSArNIWlcgWkpnhtgsh2L_aCM6dSCFdhcr6EDgagPMomRGXLESaMKI2AZyM2UG11rThXbbBQhB_SsBS-PHIU.mp4?stp=dst-mp4&efg=eyJxZV9ncm91cHMiOiJbXCJpZ193ZWJfZGVsaXZlcnlfdnRzX290ZlwiXSIsInZlbmNvZGVfdGFnIjoidnRzX3ZvZF91cmxnZW4uY2xpcHMuYzIuNzIwLmJhc2VsaW5lIn0&_nc_cat=101&vs=1419145882406626_2561696239&_nc_vs=HBksFQIYUmlnX3hwdl9yZWVsc19wZXJtYW5lbnRfc3JfcHJvZC8zNzRFMjM4Njg5Qjc0QkQ3NDkwN0I0MzM5OTZBNDM4OF92aWRlb19kYXNoaW5pdC5tcDQVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dJUUFzUnhMTktab2Zoa0dBTjR1UVp5a3VZOGFicV9FQUFBRhUCAsgBACgAGAAbABUAACbIoI2%2B8MKyPxUCKAJDMywXQE7u2RaHKwIYEmRhc2hfYmFzZWxpbmVfMV92MREAdf4HAA%3D%3D&ccb=9-4&oh=00_AYAbwCQxavV7tj89ePnRPJqSq7N2DoVqqY_V0rccfBBUAw&oe=67C19427&_nc_sid=8b3546", + "alt": null, + "likesCount": 1164, + "videoViewCount": 9282, + "timestamp": "2025-02-20T22:16:11.000Z", + "childPosts": [], + "ownerUsername": "adriandelagarzas", + "ownerId": "1483444529", + "productType": "clips" + } + ] + } +] \ No newline at end of file diff --git a/backend/app/testing/data/tiktok/actors.md b/backend/app/testing/data/tiktok/actors.md new file mode 100644 index 0000000000..a6f1337c3d --- /dev/null +++ b/backend/app/testing/data/tiktok/actors.md @@ -0,0 +1,8 @@ +# tiktok profile +clockworks/tiktok-profile-scraper + +# tiktok posts +clockworks/tiktok-profile-scraper + +# tiktok comments +clockworks/tiktok-comments-scraper \ No newline at end of file diff --git a/backend/app/testing/data/tiktok/comment_samples.json b/backend/app/testing/data/tiktok/comment_samples.json new file mode 100644 index 0000000000..86dfae7918 --- /dev/null +++ b/backend/app/testing/data/tiktok/comment_samples.json @@ -0,0 +1,70 @@ +[ + { + "videoWebUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7485219208662502661", + "submittedVideoUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7485219208662502661", + "input": "https://www.tiktok.com/@adriandelagarzasantos/video/7485219208662502661", + "cid": "7486280702196204306", + "createTime": 1743035581, + "createTimeISO": "2025-03-27T00:33:01.000Z", + "text": "Para cuando declaraciones de lo de rudy ?", + "diggCount": 0, + "likedByAuthor": false, + "pinnedByAuthor": false, + "repliesToId": null, + "replyCommentTotal": 0, + "uid": "7358264065921483782", + "uniqueId": "geral_bf", + "avatarThumbnail": "https://p16-common-sign-sg.tiktokcdn-us.com/tos-alisg-avt-0068/6ba43721d69cb356546780d7c6153a9d~tplv-tiktokx-cropcenter:100:100.jpg?dr=9640&refresh_token=fe54eb94&x-expires=1743127200&x-signature=M%2FK7ltEgqqgi0dxik7Nhu8XMTCU%3D&t=4d5b0474&ps=13740610&shp=30310797&shcp=ff37627b&idc=useast5" + }, + { + "videoWebUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7485219208662502661", + "submittedVideoUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7485219208662502661", + "input": "https://www.tiktok.com/@adriandelagarzasantos/video/7485219208662502661", + "cid": "7485225079555752709", + "createTime": 1742789808, + "createTimeISO": "2025-03-24T04:16:48.000Z", + "text": "Excelente gracias por su excelente labor señor alcalde siga haciendo feliz a las familias regiomontanas", + "diggCount": 0, + "likedByAuthor": false, + "pinnedByAuthor": false, + "repliesToId": null, + "replyCommentTotal": 0, + "uid": "7302839634067833861", + "uniqueId": "boly571", + "avatarThumbnail": "https://p16-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/895b10c4351fccdf2a82c1ea36954e23~tplv-tiktokx-cropcenter:100:100.jpg?dr=9640&refresh_token=36a68805&x-expires=1743127200&x-signature=eVKWTm3NIhHUAbvgdfgF1g%2BYMJk%3D&t=4d5b0474&ps=13740610&shp=30310797&shcp=ff37627b&idc=useast5" + }, + { + "videoWebUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7485219208662502661", + "submittedVideoUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7485219208662502661", + "input": "https://www.tiktok.com/@adriandelagarzasantos/video/7485219208662502661", + "cid": "7485227140184244997", + "createTime": 1742790301, + "createTimeISO": "2025-03-24T04:25:01.000Z", + "text": "👍", + "diggCount": 0, + "likedByAuthor": false, + "pinnedByAuthor": false, + "repliesToId": null, + "replyCommentTotal": 0, + "uid": "7179483932185936901", + "uniqueId": "user5222684959745", + "avatarThumbnail": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/22df9b2016d5b89bc3aebb111e28b709~tplv-tiktokx-cropcenter:100:100.jpg?dr=9640&refresh_token=ff28ed48&x-expires=1743127200&x-signature=BQnur2Rm16ypTTtSDQpC3EkgAwg%3D&t=4d5b0474&ps=13740610&shp=30310797&shcp=ff37627b&idc=useast5" + }, + { + "videoWebUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7485219208662502661", + "submittedVideoUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7485219208662502661", + "input": "https://www.tiktok.com/@adriandelagarzasantos/video/7485219208662502661", + "cid": "7485221762797765382", + "createTime": 1742789068, + "createTimeISO": "2025-03-24T04:04:28.000Z", + "text": "Ojala hubiera ganado de gobernador 😞", + "diggCount": 1, + "likedByAuthor": false, + "pinnedByAuthor": false, + "repliesToId": null, + "replyCommentTotal": 0, + "uid": "6972187056781607942", + "uniqueId": "kenndy12211", + "avatarThumbnail": "https://p16-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/3d4277a461cebc4377b3bb60754feb81~tplv-tiktokx-cropcenter:100:100.jpg?dr=9640&refresh_token=10efaffc&x-expires=1743127200&x-signature=XavaDRxGhUKEfx4HNLABPmCs7cc%3D&t=4d5b0474&ps=13740610&shp=30310797&shcp=ff37627b&idc=useast5" + } +] \ No newline at end of file diff --git a/backend/app/testing/data/tiktok/post_samples.json b/backend/app/testing/data/tiktok/post_samples.json new file mode 100644 index 0000000000..f3bd061564 --- /dev/null +++ b/backend/app/testing/data/tiktok/post_samples.json @@ -0,0 +1,434 @@ +[ + { + "id": "7485219208662502661", + "text": "¡La Temporada Acuática ya comenzó! 💦☀️\n\nLos esperamos en nuestros parques Aztlán, España, Tucán y Monterrey 400 para que disfruten en familia con total seguridad.\n\nRecuerda que tenemos salvavidas y equipo capacitado en cada parque para tu tranquilidad.\n\n¡Vengan a refrescarse y a pasar un gran día de diversión! 💧🏊‍♂️\n\n#AquíSeResuelve", + "textLanguage": "es", + "createTime": 1742788413, + "createTimeISO": "2025-03-24T03:53:33.000Z", + "isAd": false, + "authorMeta": { + "id": "7345639043763536901", + "name": "adriandelagarzasantos", + "profileUrl": "https://www.tiktok.com/@adriandelagarzasantos", + "nickName": "Adrián de la Garza", + "verified": false, + "signature": "Alcalde de Monterrey 2024 - 2027. Orgullosamente regio 🌄", + "bioLink": null, + "originalAvatarUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "avatar": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "commerceUserInfo": { + "commerceUser": false + }, + "privateAccount": false, + "region": "MX", + "roomId": "", + "ttSeller": false, + "following": 2, + "friends": 2, + "fans": 17400, + "heart": 205100, + "video": 212, + "digg": 0 + }, + "musicMeta": { + "musicName": "sonido original", + "musicAuthor": "Adrián de la Garza", + "musicOriginal": true, + "playUrl": "https://v16m.tiktokcdn-us.com/c2b7bd77973257be474ae86482b5a10e/67e50e41/video/tos/useast5/tos-useast5-ve-27dcd7c799-tx/owM3QC0fJnE9XJBRBLDg3dFN40r4QCfIUb1DMU/?a=1233&bti=ODszNWYuMDE6&ch=0&cr=0&dr=0&er=0&lr=default&cd=0%7C0%7C0%7C0&br=250&bt=125&ft=GSDrKInz7ThaGNyOXq8Zmo&mime_type=audio_mpeg&qs=6&rc=ZGZpNTQ0OTczZWVmO2VkaEBpM3FpN3k5cmpneTMzNzU8M0A0Yy1iMy8vNjAxMTJjNC40YSNuYy5eMmRrYmRgLS1kMTZzcw%3D%3D&vvpl=1&l=20250327023631A6A10587A7695D05BC87&btag=e00088000", + "coverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "originalCoverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "musicId": "7485219234662992646" + }, + "locationMeta": { + "address": "Nuevo León, Mexico", + "city": "", + "cityCode": "3995465", + "countryCode": "3996063", + "locationName": "Monterrey", + "locationId": "22535865211434478" + }, + "webVideoUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7485219208662502661", + "mediaUrls": [], + "videoMeta": { + "height": 1024, + "width": 576, + "duration": 49, + "coverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/oszIkI1DoAuQgg6CIifbGzBAEV8oWiCt01PREg?lk3s=81f88b70&x-expires=1743213600&x-signature=6M1w08IqUtyxuoYrWXOBxyravPg%3D&shp=81f88b70&shcp=-", + "originalCoverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/oszIkI1DoAuQgg6CIifbGzBAEV8oWiCt01PREg?lk3s=81f88b70&x-expires=1743213600&x-signature=6M1w08IqUtyxuoYrWXOBxyravPg%3D&shp=81f88b70&shcp=-", + "definition": "540p", + "format": "mp4", + "subtitleLinks": [ + { + "language": "eng-US", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/f5d86d85495b9cb2e7a39d88064ffd3f/67e75ce1/video/tos/useast5/tos-useast5-v-0068c799-tx/58a7553469f14eab9e53ba68ae3a13c5/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=32528&bt=16264&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=MzV4OXQ5cjtneTMzNzczM0BpMzV4OXQ5cjtneTMzNzczM0Bfbm1kMmRzYmRgLS1kMTZzYSNfbm1kMmRzYmRgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00048000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/f5d86d85495b9cb2e7a39d88064ffd3f/67e75ce1/video/tos/useast5/tos-useast5-v-0068c799-tx/58a7553469f14eab9e53ba68ae3a13c5/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=32528&bt=16264&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=MzV4OXQ5cjtneTMzNzczM0BpMzV4OXQ5cjtneTMzNzczM0Bfbm1kMmRzYmRgLS1kMTZzYSNfbm1kMmRzYmRgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00048000" + }, + { + "language": "spa-ES", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/3a2d32b80edef663eb25e22a43adcab1/67e75ce1/video/tos/useast5/tos-useast5-v-0068c799-tx/aa11e80366b04c59b7ceb4ebb88a2b14/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=32528&bt=16264&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=MzV4OXQ5cjtneTMzNzczM0BpMzV4OXQ5cjtneTMzNzczM0Bfbm1kMmRzYmRgLS1kMTZzYSNfbm1kMmRzYmRgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00048000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/3a2d32b80edef663eb25e22a43adcab1/67e75ce1/video/tos/useast5/tos-useast5-v-0068c799-tx/aa11e80366b04c59b7ceb4ebb88a2b14/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=32528&bt=16264&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=MzV4OXQ5cjtneTMzNzczM0BpMzV4OXQ5cjtneTMzNzczM0Bfbm1kMmRzYmRgLS1kMTZzYSNfbm1kMmRzYmRgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00048000" + } + ] + }, + "diggCount": 123, + "shareCount": 2, + "playCount": 1350, + "collectCount": 3, + "commentCount": 4, + "mentions": [], + "detailedMentions": [], + "hashtags": [ + { + "name": "aquíseresuelve" + } + ], + "effectStickers": [], + "isSlideshow": false, + "isPinned": false, + "isSponsored": false, + "input": "adriandelagarzasantos", + "fromProfileSection": "videos" + }, + { + "id": "7484015343938161925", + "text": "Visitando INC Monterrey", + "textLanguage": "es", + "createTime": 1742508117, + "createTimeISO": "2025-03-20T22:01:57.000Z", + "isAd": false, + "authorMeta": { + "id": "7345639043763536901", + "name": "adriandelagarzasantos", + "profileUrl": "https://www.tiktok.com/@adriandelagarzasantos", + "nickName": "Adrián de la Garza", + "verified": false, + "signature": "Alcalde de Monterrey 2024 - 2027. Orgullosamente regio 🌄", + "bioLink": null, + "originalAvatarUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "avatar": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "commerceUserInfo": { + "commerceUser": false + }, + "privateAccount": false, + "region": "MX", + "roomId": "", + "ttSeller": false, + "following": 2, + "friends": 2, + "fans": 17400, + "heart": 205100, + "video": 212, + "digg": 0 + }, + "musicMeta": { + "musicName": "sonido original", + "musicAuthor": "Adrián de la Garza", + "musicOriginal": true, + "playUrl": "https://v16m.tiktokcdn-us.com/7014e40035a4f7d26c01d7ac6cb11ed5/67e50e2b/video/tos/useast5/tos-useast5-v-27dcd7c799-tx/owmPZIDFAEyESQi9LQCixEAgcQTAWAyZBlUk4/?a=1233&bti=ODszNWYuMDE6&ch=0&cr=0&dr=0&er=0&lr=default&cd=0%7C0%7C0%7C0&br=250&bt=125&ds=5&ft=GSDrKInz7ThaGNyOXq8Zmo&mime_type=audio_mpeg&qs=13&rc=ajdpZmw5cnA8eTMzNzU8M0BpajdpZmw5cnA8eTMzNzU8M0BgZDYxMmQ0LWJgLS1kMTZzYSNgZDYxMmQ0LWJgLS1kMTZzcw%3D%3D&vvpl=1&l=20250327023631A6A10587A7695D05BC87&btag=e00078000", + "coverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "originalCoverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "musicId": "7484015361151552311" + }, + "locationMeta": { + "address": "Nuevo León, Mexico", + "city": "", + "cityCode": "3995465", + "countryCode": "3996063", + "locationName": "Monterrey", + "locationId": "22535865211434478" + }, + "webVideoUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7484015343938161925", + "mediaUrls": [], + "videoMeta": { + "height": 1024, + "width": 576, + "duration": 27, + "coverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/ogfG4TFfJnu7QQEdkOmFNEDDBKSY3QRcBlJAHB?lk3s=81f88b70&x-expires=1743213600&x-signature=KUHNuTQSvJpjszsgaoFI0tBCmtg%3D&shp=81f88b70&shcp=-", + "originalCoverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/ogfG4TFfJnu7QQEdkOmFNEDDBKSY3QRcBlJAHB?lk3s=81f88b70&x-expires=1743213600&x-signature=KUHNuTQSvJpjszsgaoFI0tBCmtg%3D&shp=81f88b70&shcp=-", + "definition": "540p", + "format": "mp4", + "subtitleLinks": [ + { + "language": "eng-US", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/50d7f7ef89dac36964f0c9455123aba3/67e75ccb/video/tos/useast8/tos-useast8-v-0068c799-tx2/1ed750dd38324d48a0f9b23c9701159f/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=24870&bt=12435&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=Mzx3Z2w5cms8eTMzNzczM0BpMzx3Z2w5cms8eTMzNzczM0BqM2xnMmRrLWJgLS1kMTZzYSNqM2xnMmRrLWJgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00078000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/50d7f7ef89dac36964f0c9455123aba3/67e75ccb/video/tos/useast8/tos-useast8-v-0068c799-tx2/1ed750dd38324d48a0f9b23c9701159f/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=24870&bt=12435&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=Mzx3Z2w5cms8eTMzNzczM0BpMzx3Z2w5cms8eTMzNzczM0BqM2xnMmRrLWJgLS1kMTZzYSNqM2xnMmRrLWJgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00078000" + }, + { + "language": "spa-ES", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/20d31c68b34e7378f3c23f9f27f31d3b/67e75ccb/video/tos/useast8/tos-useast8-v-0068c799-tx2/cea9d58da3a24f49a5ef5a6bc6650b96/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=24870&bt=12435&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=Mzx3Z2w5cms8eTMzNzczM0BpMzx3Z2w5cms8eTMzNzczM0BqM2xnMmRrLWJgLS1kMTZzYSNqM2xnMmRrLWJgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00078000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/20d31c68b34e7378f3c23f9f27f31d3b/67e75ccb/video/tos/useast8/tos-useast8-v-0068c799-tx2/cea9d58da3a24f49a5ef5a6bc6650b96/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=24870&bt=12435&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=Mzx3Z2w5cms8eTMzNzczM0BpMzx3Z2w5cms8eTMzNzczM0BqM2xnMmRrLWJgLS1kMTZzYSNqM2xnMmRrLWJgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00078000" + } + ] + }, + "diggCount": 213, + "shareCount": 4, + "playCount": 3950, + "collectCount": 9, + "commentCount": 20, + "mentions": [], + "detailedMentions": [], + "hashtags": [], + "effectStickers": [], + "isSlideshow": false, + "isPinned": false, + "isSponsored": false, + "input": "adriandelagarzasantos", + "fromProfileSection": "videos" + }, + { + "id": "7484014959018593542", + "text": "Un gusto saludarlos a todos en el INC Monterrey", + "textLanguage": "es", + "createTime": 1742508025, + "createTimeISO": "2025-03-20T22:00:25.000Z", + "isAd": false, + "authorMeta": { + "id": "7345639043763536901", + "name": "adriandelagarzasantos", + "profileUrl": "https://www.tiktok.com/@adriandelagarzasantos", + "nickName": "Adrián de la Garza", + "verified": false, + "signature": "Alcalde de Monterrey 2024 - 2027. Orgullosamente regio 🌄", + "bioLink": null, + "originalAvatarUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "avatar": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "commerceUserInfo": { + "commerceUser": false + }, + "privateAccount": false, + "region": "MX", + "roomId": "", + "ttSeller": false, + "following": 2, + "friends": 2, + "fans": 17400, + "heart": 205100, + "video": 212, + "digg": 0 + }, + "musicMeta": { + "musicName": "The Deep House15161", + "musicAuthor": "AudioPapa", + "musicOriginal": false, + "musicAlbum": "The Deep House02", + "playUrl": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/okhfnbDFaDkGUIqyGQGAeCf8IRzKF64hBB1BFT", + "coverMediumUrl": "https://p16-sg.tiktokcdn.com/aweme/200x200/tos-alisg-v-2774/ooCAAG5yEWkEbBpAHDnhxEh7D8HZDxegAVetLG.jpeg", + "originalCoverMediumUrl": "https://p16-sg.tiktokcdn.com/aweme/200x200/tos-alisg-v-2774/ooCAAG5yEWkEbBpAHDnhxEh7D8HZDxegAVetLG.jpeg", + "musicId": "7377897167895447588" + }, + "locationMeta": { + "address": "Nuevo León, Mexico", + "city": "", + "cityCode": "3995465", + "countryCode": "3996063", + "locationName": "Monterrey", + "locationId": "22535865211434478" + }, + "webVideoUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7484014959018593542", + "mediaUrls": [], + "videoMeta": { + "height": 1024, + "width": 576, + "duration": 59, + "coverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/o4hQgEjIjfaQPAHf5SquGRfaBCGDICFYS2sCW1?lk3s=81f88b70&x-expires=1743213600&x-signature=IcBz0OI8pYlG3Cx2P8ieRl0D3D4%3D&shp=81f88b70&shcp=-", + "originalCoverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/o4hQgEjIjfaQPAHf5SquGRfaBCGDICFYS2sCW1?lk3s=81f88b70&x-expires=1743213600&x-signature=IcBz0OI8pYlG3Cx2P8ieRl0D3D4%3D&shp=81f88b70&shcp=-", + "definition": "540p", + "format": "mp4", + "subtitleLinks": [ + { + "language": "eng-US", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/862b71281e414fca259315518b38a0ad/67e75ceb/video/tos/useast5/tos-useast5-v-0068c799-tx/cc423677698f4f9b91aba02db21fa6d1/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=25042&bt=12521&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajRvams5cnA7eTMzNzczM0BpajRvams5cnA7eTMzNzczM0BeaTJxMmQ0c2JgLS1kMTZzYSNeaTJxMmQ0c2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/862b71281e414fca259315518b38a0ad/67e75ceb/video/tos/useast5/tos-useast5-v-0068c799-tx/cc423677698f4f9b91aba02db21fa6d1/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=25042&bt=12521&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajRvams5cnA7eTMzNzczM0BpajRvams5cnA7eTMzNzczM0BeaTJxMmQ0c2JgLS1kMTZzYSNeaTJxMmQ0c2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000" + }, + { + "language": "spa-ES", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/ed6635b877e35751e53313b651b737b8/67e75ceb/video/tos/useast5/tos-useast5-v-0068c799-tx/e1383e5754cf485d92d39270e73b78bf/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=25042&bt=12521&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajRvams5cnA7eTMzNzczM0BpajRvams5cnA7eTMzNzczM0BeaTJxMmQ0c2JgLS1kMTZzYSNeaTJxMmQ0c2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/ed6635b877e35751e53313b651b737b8/67e75ceb/video/tos/useast5/tos-useast5-v-0068c799-tx/e1383e5754cf485d92d39270e73b78bf/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=25042&bt=12521&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajRvams5cnA7eTMzNzczM0BpajRvams5cnA7eTMzNzczM0BeaTJxMmQ0c2JgLS1kMTZzYSNeaTJxMmQ0c2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000" + } + ] + }, + "diggCount": 460, + "shareCount": 189, + "playCount": 11700, + "collectCount": 23, + "commentCount": 28, + "mentions": [], + "detailedMentions": [], + "hashtags": [], + "effectStickers": [], + "isSlideshow": false, + "isPinned": false, + "isSponsored": false, + "input": "adriandelagarzasantos", + "fromProfileSection": "videos" + }, + { + "id": "7483964138323004678", + "text": "Les comparto el video de la persecución y abatimiento del cobarde que asesinó al elemento de nuestra Policía de Monterrey. \n\nLes reitero que en Monterrey quien la hace la paga, todo nuestro apoyo a las y los policías de nuestra corporación. Siempre vamos a estar del lado de la protección de las familias regias.\n\n#AquíSeResuelve", + "textLanguage": "es", + "createTime": 1742496193, + "createTimeISO": "2025-03-20T18:43:13.000Z", + "isAd": false, + "isMuted": true, + "authorMeta": { + "id": "7345639043763536901", + "name": "adriandelagarzasantos", + "profileUrl": "https://www.tiktok.com/@adriandelagarzasantos", + "nickName": "Adrián de la Garza", + "verified": false, + "signature": "Alcalde de Monterrey 2024 - 2027. Orgullosamente regio 🌄", + "bioLink": null, + "originalAvatarUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "avatar": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "commerceUserInfo": { + "commerceUser": false + }, + "privateAccount": false, + "region": "MX", + "roomId": "", + "ttSeller": false, + "following": 2, + "friends": 2, + "fans": 17400, + "heart": 205100, + "video": 212, + "digg": 0 + }, + "musicMeta": { + "musicName": "sonido original", + "musicAuthor": "Adrián de la Garza", + "musicOriginal": true, + "playUrl": "https://v16m.tiktokcdn-us.com/01cf0c7b096446f459204a159c1f9f9e/67e50e4f/video/tos/useast5/tos-useast5-ve-27dcd7c799-tx/owfFwmcjGnRyIQEkYe7FKUDDBgZR3MJcH0JCPB/?a=1233&bti=ODszNWYuMDE6&ch=0&cr=0&dr=0&er=0&lr=default&cd=0%7C0%7C0%7C0&br=250&bt=125&ft=GSDrKInz7ThaGNyOXq8Zmo&mime_type=audio_mpeg&qs=6&rc=aWRlPDNnPGY7OTZnOmQ2NkBpajl1bHQ5cms5eTMzNzU8M0AtXmEwXjUxNjYxMi0zLzVjYSM1azQvMmRjMGJgLS1kMTZzcw%3D%3D&vvpl=1&l=20250327023631A6A10587A7695D05BC87&btag=e00090000", + "coverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "originalCoverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "musicId": "7483964151887448837" + }, + "locationMeta": { + "address": "Nuevo León, Mexico", + "city": "", + "cityCode": "3995465", + "countryCode": "3996063", + "locationName": "Monterrey", + "locationId": "22535865211434478" + }, + "webVideoUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7483964138323004678", + "mediaUrls": [], + "videoMeta": { + "height": 1024, + "width": 576, + "duration": 63, + "coverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/o8ekcWjACFCI4Lg99FGgSPgcugeLpYjAMLEIf2?lk3s=81f88b70&x-expires=1743213600&x-signature=%2FmykHev1Z%2FDub7Zg9LJpy5e%2FOTg%3D&shp=81f88b70&shcp=-", + "originalCoverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/o8ekcWjACFCI4Lg99FGgSPgcugeLpYjAMLEIf2?lk3s=81f88b70&x-expires=1743213600&x-signature=%2FmykHev1Z%2FDub7Zg9LJpy5e%2FOTg%3D&shp=81f88b70&shcp=-", + "definition": "540p", + "format": "mp4" + }, + "diggCount": 21, + "shareCount": 4, + "playCount": 442, + "collectCount": 1, + "commentCount": 3, + "mentions": [], + "detailedMentions": [], + "hashtags": [ + { + "name": "aquíseresuelve" + } + ], + "effectStickers": [], + "isSlideshow": false, + "isPinned": false, + "isSponsored": false, + "input": "adriandelagarzasantos", + "fromProfileSection": "videos" + }, + { + "id": "7483962985107541254", + "text": "Después de los hechos ocurridos, quiero dejar algo muy claro: en Monterrey no vamos a tolerar agresiones contra los policías que nos protegen.\n\nA quienes amenazan la paz de nuestra ciudad: el que la hace, la paga.\n\nHoy más que nunca trabajamos por fortalecer la seguridad de Monterrey.\n\n#AquíSeResuelve", + "textLanguage": "es", + "createTime": 1742495930, + "createTimeISO": "2025-03-20T18:38:50.000Z", + "isAd": false, + "authorMeta": { + "id": "7345639043763536901", + "name": "adriandelagarzasantos", + "profileUrl": "https://www.tiktok.com/@adriandelagarzasantos", + "nickName": "Adrián de la Garza", + "verified": false, + "signature": "Alcalde de Monterrey 2024 - 2027. Orgullosamente regio 🌄", + "bioLink": null, + "originalAvatarUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "avatar": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "commerceUserInfo": { + "commerceUser": false + }, + "privateAccount": false, + "region": "MX", + "roomId": "", + "ttSeller": false, + "following": 2, + "friends": 2, + "fans": 17400, + "heart": 205100, + "video": 212, + "digg": 0 + }, + "musicMeta": { + "musicName": "sonido original", + "musicAuthor": "Adrián de la Garza", + "musicOriginal": true, + "playUrl": "https://v16m.tiktokcdn-us.com/4bdff0b23683a720a78a645ee17a7b4f/67e50e57/video/tos/useast5/tos-useast5-ve-27dcd7c799-tx/okGdJFDfJUB0kYLBR4EQKDBCwg1QY4C53nfMku/?a=1233&bti=ODszNWYuMDE6&ch=0&cr=0&dr=0&er=0&lr=default&cd=0%7C0%7C0%7C0&br=250&bt=125&ft=GSDrKInz7ThaGNyOXq8Zmo&mime_type=audio_mpeg&qs=6&rc=Z2c0MzZmaWg8ZjM2OzZlZ0BpM2xtPHY5cmo5eTMzNzU8M0AvNV8zYy0uX14xYi9fLi8tYSMwaV80MmRjLmJgLS1kMTZzcw%3D%3D&vvpl=1&l=20250327023631A6A10587A7695D05BC87&btag=e00090000", + "coverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "originalCoverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "musicId": "7483963050649340677" + }, + "locationMeta": { + "address": "Nuevo León, Mexico", + "city": "", + "cityCode": "3995465", + "countryCode": "3996063", + "locationName": "Monterrey", + "locationId": "22535865211434478" + }, + "webVideoUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7483962985107541254", + "mediaUrls": [], + "videoMeta": { + "height": 1024, + "width": 576, + "duration": 71, + "coverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/oAjcT99IDejJCICI4CLPAYvGgzSSdC3MeLuIeU?lk3s=81f88b70&x-expires=1743213600&x-signature=Xa2YuQ%2BXfk3EUqX409qcbVx8V0Y%3D&shp=81f88b70&shcp=-", + "originalCoverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/oAjcT99IDejJCICI4CLPAYvGgzSSdC3MeLuIeU?lk3s=81f88b70&x-expires=1743213600&x-signature=Xa2YuQ%2BXfk3EUqX409qcbVx8V0Y%3D&shp=81f88b70&shcp=-", + "definition": "540p", + "format": "mp4", + "subtitleLinks": [ + { + "language": "eng-US", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/a0ba9e8a781caa8a826dc85b672b7fc3/67e75cf7/video/tos/useast5/tos-useast5-v-0068c799-tx/3f2080e838cf449abc42de5c9b9bfb83/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=35410&bt=17705&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajZtcW85cmQ4eTMzNzczM0BpajZtcW85cmQ4eTMzNzczM0BtcWw0MmRjc2JgLS1kMTZzYSNtcWw0MmRjc2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/a0ba9e8a781caa8a826dc85b672b7fc3/67e75cf7/video/tos/useast5/tos-useast5-v-0068c799-tx/3f2080e838cf449abc42de5c9b9bfb83/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=35410&bt=17705&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajZtcW85cmQ4eTMzNzczM0BpajZtcW85cmQ4eTMzNzczM0BtcWw0MmRjc2JgLS1kMTZzYSNtcWw0MmRjc2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000" + }, + { + "language": "spa-ES", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/3e8eaf591f6e013446d327e5abb4bd44/67e75cf7/video/tos/useast8/tos-useast8-v-0068c799-tx2/8877bbfde8d545b1ada7dc465a671aaa/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=35410&bt=17705&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajZtcW85cmQ4eTMzNzczM0BpajZtcW85cmQ4eTMzNzczM0BtcWw0MmRjc2JgLS1kMTZzYSNtcWw0MmRjc2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/3e8eaf591f6e013446d327e5abb4bd44/67e75cf7/video/tos/useast8/tos-useast8-v-0068c799-tx2/8877bbfde8d545b1ada7dc465a671aaa/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=35410&bt=17705&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajZtcW85cmQ4eTMzNzczM0BpajZtcW85cmQ4eTMzNzczM0BtcWw0MmRjc2JgLS1kMTZzYSNtcWw0MmRjc2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000" + } + ] + }, + "diggCount": 1443, + "shareCount": 7, + "playCount": 117900, + "collectCount": 15, + "commentCount": 58, + "mentions": [], + "detailedMentions": [], + "hashtags": [ + { + "name": "aquíseresuelve" + } + ], + "effectStickers": [], + "isSlideshow": false, + "isPinned": false, + "isSponsored": false, + "input": "adriandelagarzasantos", + "fromProfileSection": "videos" + } +] \ No newline at end of file diff --git a/backend/app/testing/data/tiktok/profile_samples.json b/backend/app/testing/data/tiktok/profile_samples.json new file mode 100644 index 0000000000..f3bd061564 --- /dev/null +++ b/backend/app/testing/data/tiktok/profile_samples.json @@ -0,0 +1,434 @@ +[ + { + "id": "7485219208662502661", + "text": "¡La Temporada Acuática ya comenzó! 💦☀️\n\nLos esperamos en nuestros parques Aztlán, España, Tucán y Monterrey 400 para que disfruten en familia con total seguridad.\n\nRecuerda que tenemos salvavidas y equipo capacitado en cada parque para tu tranquilidad.\n\n¡Vengan a refrescarse y a pasar un gran día de diversión! 💧🏊‍♂️\n\n#AquíSeResuelve", + "textLanguage": "es", + "createTime": 1742788413, + "createTimeISO": "2025-03-24T03:53:33.000Z", + "isAd": false, + "authorMeta": { + "id": "7345639043763536901", + "name": "adriandelagarzasantos", + "profileUrl": "https://www.tiktok.com/@adriandelagarzasantos", + "nickName": "Adrián de la Garza", + "verified": false, + "signature": "Alcalde de Monterrey 2024 - 2027. Orgullosamente regio 🌄", + "bioLink": null, + "originalAvatarUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "avatar": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "commerceUserInfo": { + "commerceUser": false + }, + "privateAccount": false, + "region": "MX", + "roomId": "", + "ttSeller": false, + "following": 2, + "friends": 2, + "fans": 17400, + "heart": 205100, + "video": 212, + "digg": 0 + }, + "musicMeta": { + "musicName": "sonido original", + "musicAuthor": "Adrián de la Garza", + "musicOriginal": true, + "playUrl": "https://v16m.tiktokcdn-us.com/c2b7bd77973257be474ae86482b5a10e/67e50e41/video/tos/useast5/tos-useast5-ve-27dcd7c799-tx/owM3QC0fJnE9XJBRBLDg3dFN40r4QCfIUb1DMU/?a=1233&bti=ODszNWYuMDE6&ch=0&cr=0&dr=0&er=0&lr=default&cd=0%7C0%7C0%7C0&br=250&bt=125&ft=GSDrKInz7ThaGNyOXq8Zmo&mime_type=audio_mpeg&qs=6&rc=ZGZpNTQ0OTczZWVmO2VkaEBpM3FpN3k5cmpneTMzNzU8M0A0Yy1iMy8vNjAxMTJjNC40YSNuYy5eMmRrYmRgLS1kMTZzcw%3D%3D&vvpl=1&l=20250327023631A6A10587A7695D05BC87&btag=e00088000", + "coverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "originalCoverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "musicId": "7485219234662992646" + }, + "locationMeta": { + "address": "Nuevo León, Mexico", + "city": "", + "cityCode": "3995465", + "countryCode": "3996063", + "locationName": "Monterrey", + "locationId": "22535865211434478" + }, + "webVideoUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7485219208662502661", + "mediaUrls": [], + "videoMeta": { + "height": 1024, + "width": 576, + "duration": 49, + "coverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/oszIkI1DoAuQgg6CIifbGzBAEV8oWiCt01PREg?lk3s=81f88b70&x-expires=1743213600&x-signature=6M1w08IqUtyxuoYrWXOBxyravPg%3D&shp=81f88b70&shcp=-", + "originalCoverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/oszIkI1DoAuQgg6CIifbGzBAEV8oWiCt01PREg?lk3s=81f88b70&x-expires=1743213600&x-signature=6M1w08IqUtyxuoYrWXOBxyravPg%3D&shp=81f88b70&shcp=-", + "definition": "540p", + "format": "mp4", + "subtitleLinks": [ + { + "language": "eng-US", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/f5d86d85495b9cb2e7a39d88064ffd3f/67e75ce1/video/tos/useast5/tos-useast5-v-0068c799-tx/58a7553469f14eab9e53ba68ae3a13c5/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=32528&bt=16264&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=MzV4OXQ5cjtneTMzNzczM0BpMzV4OXQ5cjtneTMzNzczM0Bfbm1kMmRzYmRgLS1kMTZzYSNfbm1kMmRzYmRgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00048000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/f5d86d85495b9cb2e7a39d88064ffd3f/67e75ce1/video/tos/useast5/tos-useast5-v-0068c799-tx/58a7553469f14eab9e53ba68ae3a13c5/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=32528&bt=16264&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=MzV4OXQ5cjtneTMzNzczM0BpMzV4OXQ5cjtneTMzNzczM0Bfbm1kMmRzYmRgLS1kMTZzYSNfbm1kMmRzYmRgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00048000" + }, + { + "language": "spa-ES", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/3a2d32b80edef663eb25e22a43adcab1/67e75ce1/video/tos/useast5/tos-useast5-v-0068c799-tx/aa11e80366b04c59b7ceb4ebb88a2b14/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=32528&bt=16264&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=MzV4OXQ5cjtneTMzNzczM0BpMzV4OXQ5cjtneTMzNzczM0Bfbm1kMmRzYmRgLS1kMTZzYSNfbm1kMmRzYmRgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00048000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/3a2d32b80edef663eb25e22a43adcab1/67e75ce1/video/tos/useast5/tos-useast5-v-0068c799-tx/aa11e80366b04c59b7ceb4ebb88a2b14/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=32528&bt=16264&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=MzV4OXQ5cjtneTMzNzczM0BpMzV4OXQ5cjtneTMzNzczM0Bfbm1kMmRzYmRgLS1kMTZzYSNfbm1kMmRzYmRgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00048000" + } + ] + }, + "diggCount": 123, + "shareCount": 2, + "playCount": 1350, + "collectCount": 3, + "commentCount": 4, + "mentions": [], + "detailedMentions": [], + "hashtags": [ + { + "name": "aquíseresuelve" + } + ], + "effectStickers": [], + "isSlideshow": false, + "isPinned": false, + "isSponsored": false, + "input": "adriandelagarzasantos", + "fromProfileSection": "videos" + }, + { + "id": "7484015343938161925", + "text": "Visitando INC Monterrey", + "textLanguage": "es", + "createTime": 1742508117, + "createTimeISO": "2025-03-20T22:01:57.000Z", + "isAd": false, + "authorMeta": { + "id": "7345639043763536901", + "name": "adriandelagarzasantos", + "profileUrl": "https://www.tiktok.com/@adriandelagarzasantos", + "nickName": "Adrián de la Garza", + "verified": false, + "signature": "Alcalde de Monterrey 2024 - 2027. Orgullosamente regio 🌄", + "bioLink": null, + "originalAvatarUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "avatar": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "commerceUserInfo": { + "commerceUser": false + }, + "privateAccount": false, + "region": "MX", + "roomId": "", + "ttSeller": false, + "following": 2, + "friends": 2, + "fans": 17400, + "heart": 205100, + "video": 212, + "digg": 0 + }, + "musicMeta": { + "musicName": "sonido original", + "musicAuthor": "Adrián de la Garza", + "musicOriginal": true, + "playUrl": "https://v16m.tiktokcdn-us.com/7014e40035a4f7d26c01d7ac6cb11ed5/67e50e2b/video/tos/useast5/tos-useast5-v-27dcd7c799-tx/owmPZIDFAEyESQi9LQCixEAgcQTAWAyZBlUk4/?a=1233&bti=ODszNWYuMDE6&ch=0&cr=0&dr=0&er=0&lr=default&cd=0%7C0%7C0%7C0&br=250&bt=125&ds=5&ft=GSDrKInz7ThaGNyOXq8Zmo&mime_type=audio_mpeg&qs=13&rc=ajdpZmw5cnA8eTMzNzU8M0BpajdpZmw5cnA8eTMzNzU8M0BgZDYxMmQ0LWJgLS1kMTZzYSNgZDYxMmQ0LWJgLS1kMTZzcw%3D%3D&vvpl=1&l=20250327023631A6A10587A7695D05BC87&btag=e00078000", + "coverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "originalCoverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "musicId": "7484015361151552311" + }, + "locationMeta": { + "address": "Nuevo León, Mexico", + "city": "", + "cityCode": "3995465", + "countryCode": "3996063", + "locationName": "Monterrey", + "locationId": "22535865211434478" + }, + "webVideoUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7484015343938161925", + "mediaUrls": [], + "videoMeta": { + "height": 1024, + "width": 576, + "duration": 27, + "coverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/ogfG4TFfJnu7QQEdkOmFNEDDBKSY3QRcBlJAHB?lk3s=81f88b70&x-expires=1743213600&x-signature=KUHNuTQSvJpjszsgaoFI0tBCmtg%3D&shp=81f88b70&shcp=-", + "originalCoverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/ogfG4TFfJnu7QQEdkOmFNEDDBKSY3QRcBlJAHB?lk3s=81f88b70&x-expires=1743213600&x-signature=KUHNuTQSvJpjszsgaoFI0tBCmtg%3D&shp=81f88b70&shcp=-", + "definition": "540p", + "format": "mp4", + "subtitleLinks": [ + { + "language": "eng-US", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/50d7f7ef89dac36964f0c9455123aba3/67e75ccb/video/tos/useast8/tos-useast8-v-0068c799-tx2/1ed750dd38324d48a0f9b23c9701159f/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=24870&bt=12435&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=Mzx3Z2w5cms8eTMzNzczM0BpMzx3Z2w5cms8eTMzNzczM0BqM2xnMmRrLWJgLS1kMTZzYSNqM2xnMmRrLWJgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00078000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/50d7f7ef89dac36964f0c9455123aba3/67e75ccb/video/tos/useast8/tos-useast8-v-0068c799-tx2/1ed750dd38324d48a0f9b23c9701159f/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=24870&bt=12435&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=Mzx3Z2w5cms8eTMzNzczM0BpMzx3Z2w5cms8eTMzNzczM0BqM2xnMmRrLWJgLS1kMTZzYSNqM2xnMmRrLWJgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00078000" + }, + { + "language": "spa-ES", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/20d31c68b34e7378f3c23f9f27f31d3b/67e75ccb/video/tos/useast8/tos-useast8-v-0068c799-tx2/cea9d58da3a24f49a5ef5a6bc6650b96/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=24870&bt=12435&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=Mzx3Z2w5cms8eTMzNzczM0BpMzx3Z2w5cms8eTMzNzczM0BqM2xnMmRrLWJgLS1kMTZzYSNqM2xnMmRrLWJgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00078000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/20d31c68b34e7378f3c23f9f27f31d3b/67e75ccb/video/tos/useast8/tos-useast8-v-0068c799-tx2/cea9d58da3a24f49a5ef5a6bc6650b96/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=24870&bt=12435&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=Mzx3Z2w5cms8eTMzNzczM0BpMzx3Z2w5cms8eTMzNzczM0BqM2xnMmRrLWJgLS1kMTZzYSNqM2xnMmRrLWJgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00078000" + } + ] + }, + "diggCount": 213, + "shareCount": 4, + "playCount": 3950, + "collectCount": 9, + "commentCount": 20, + "mentions": [], + "detailedMentions": [], + "hashtags": [], + "effectStickers": [], + "isSlideshow": false, + "isPinned": false, + "isSponsored": false, + "input": "adriandelagarzasantos", + "fromProfileSection": "videos" + }, + { + "id": "7484014959018593542", + "text": "Un gusto saludarlos a todos en el INC Monterrey", + "textLanguage": "es", + "createTime": 1742508025, + "createTimeISO": "2025-03-20T22:00:25.000Z", + "isAd": false, + "authorMeta": { + "id": "7345639043763536901", + "name": "adriandelagarzasantos", + "profileUrl": "https://www.tiktok.com/@adriandelagarzasantos", + "nickName": "Adrián de la Garza", + "verified": false, + "signature": "Alcalde de Monterrey 2024 - 2027. Orgullosamente regio 🌄", + "bioLink": null, + "originalAvatarUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "avatar": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "commerceUserInfo": { + "commerceUser": false + }, + "privateAccount": false, + "region": "MX", + "roomId": "", + "ttSeller": false, + "following": 2, + "friends": 2, + "fans": 17400, + "heart": 205100, + "video": 212, + "digg": 0 + }, + "musicMeta": { + "musicName": "The Deep House15161", + "musicAuthor": "AudioPapa", + "musicOriginal": false, + "musicAlbum": "The Deep House02", + "playUrl": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/okhfnbDFaDkGUIqyGQGAeCf8IRzKF64hBB1BFT", + "coverMediumUrl": "https://p16-sg.tiktokcdn.com/aweme/200x200/tos-alisg-v-2774/ooCAAG5yEWkEbBpAHDnhxEh7D8HZDxegAVetLG.jpeg", + "originalCoverMediumUrl": "https://p16-sg.tiktokcdn.com/aweme/200x200/tos-alisg-v-2774/ooCAAG5yEWkEbBpAHDnhxEh7D8HZDxegAVetLG.jpeg", + "musicId": "7377897167895447588" + }, + "locationMeta": { + "address": "Nuevo León, Mexico", + "city": "", + "cityCode": "3995465", + "countryCode": "3996063", + "locationName": "Monterrey", + "locationId": "22535865211434478" + }, + "webVideoUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7484014959018593542", + "mediaUrls": [], + "videoMeta": { + "height": 1024, + "width": 576, + "duration": 59, + "coverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/o4hQgEjIjfaQPAHf5SquGRfaBCGDICFYS2sCW1?lk3s=81f88b70&x-expires=1743213600&x-signature=IcBz0OI8pYlG3Cx2P8ieRl0D3D4%3D&shp=81f88b70&shcp=-", + "originalCoverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/o4hQgEjIjfaQPAHf5SquGRfaBCGDICFYS2sCW1?lk3s=81f88b70&x-expires=1743213600&x-signature=IcBz0OI8pYlG3Cx2P8ieRl0D3D4%3D&shp=81f88b70&shcp=-", + "definition": "540p", + "format": "mp4", + "subtitleLinks": [ + { + "language": "eng-US", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/862b71281e414fca259315518b38a0ad/67e75ceb/video/tos/useast5/tos-useast5-v-0068c799-tx/cc423677698f4f9b91aba02db21fa6d1/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=25042&bt=12521&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajRvams5cnA7eTMzNzczM0BpajRvams5cnA7eTMzNzczM0BeaTJxMmQ0c2JgLS1kMTZzYSNeaTJxMmQ0c2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/862b71281e414fca259315518b38a0ad/67e75ceb/video/tos/useast5/tos-useast5-v-0068c799-tx/cc423677698f4f9b91aba02db21fa6d1/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=25042&bt=12521&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajRvams5cnA7eTMzNzczM0BpajRvams5cnA7eTMzNzczM0BeaTJxMmQ0c2JgLS1kMTZzYSNeaTJxMmQ0c2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000" + }, + { + "language": "spa-ES", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/ed6635b877e35751e53313b651b737b8/67e75ceb/video/tos/useast5/tos-useast5-v-0068c799-tx/e1383e5754cf485d92d39270e73b78bf/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=25042&bt=12521&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajRvams5cnA7eTMzNzczM0BpajRvams5cnA7eTMzNzczM0BeaTJxMmQ0c2JgLS1kMTZzYSNeaTJxMmQ0c2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/ed6635b877e35751e53313b651b737b8/67e75ceb/video/tos/useast5/tos-useast5-v-0068c799-tx/e1383e5754cf485d92d39270e73b78bf/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=25042&bt=12521&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajRvams5cnA7eTMzNzczM0BpajRvams5cnA7eTMzNzczM0BeaTJxMmQ0c2JgLS1kMTZzYSNeaTJxMmQ0c2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000" + } + ] + }, + "diggCount": 460, + "shareCount": 189, + "playCount": 11700, + "collectCount": 23, + "commentCount": 28, + "mentions": [], + "detailedMentions": [], + "hashtags": [], + "effectStickers": [], + "isSlideshow": false, + "isPinned": false, + "isSponsored": false, + "input": "adriandelagarzasantos", + "fromProfileSection": "videos" + }, + { + "id": "7483964138323004678", + "text": "Les comparto el video de la persecución y abatimiento del cobarde que asesinó al elemento de nuestra Policía de Monterrey. \n\nLes reitero que en Monterrey quien la hace la paga, todo nuestro apoyo a las y los policías de nuestra corporación. Siempre vamos a estar del lado de la protección de las familias regias.\n\n#AquíSeResuelve", + "textLanguage": "es", + "createTime": 1742496193, + "createTimeISO": "2025-03-20T18:43:13.000Z", + "isAd": false, + "isMuted": true, + "authorMeta": { + "id": "7345639043763536901", + "name": "adriandelagarzasantos", + "profileUrl": "https://www.tiktok.com/@adriandelagarzasantos", + "nickName": "Adrián de la Garza", + "verified": false, + "signature": "Alcalde de Monterrey 2024 - 2027. Orgullosamente regio 🌄", + "bioLink": null, + "originalAvatarUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "avatar": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "commerceUserInfo": { + "commerceUser": false + }, + "privateAccount": false, + "region": "MX", + "roomId": "", + "ttSeller": false, + "following": 2, + "friends": 2, + "fans": 17400, + "heart": 205100, + "video": 212, + "digg": 0 + }, + "musicMeta": { + "musicName": "sonido original", + "musicAuthor": "Adrián de la Garza", + "musicOriginal": true, + "playUrl": "https://v16m.tiktokcdn-us.com/01cf0c7b096446f459204a159c1f9f9e/67e50e4f/video/tos/useast5/tos-useast5-ve-27dcd7c799-tx/owfFwmcjGnRyIQEkYe7FKUDDBgZR3MJcH0JCPB/?a=1233&bti=ODszNWYuMDE6&ch=0&cr=0&dr=0&er=0&lr=default&cd=0%7C0%7C0%7C0&br=250&bt=125&ft=GSDrKInz7ThaGNyOXq8Zmo&mime_type=audio_mpeg&qs=6&rc=aWRlPDNnPGY7OTZnOmQ2NkBpajl1bHQ5cms5eTMzNzU8M0AtXmEwXjUxNjYxMi0zLzVjYSM1azQvMmRjMGJgLS1kMTZzcw%3D%3D&vvpl=1&l=20250327023631A6A10587A7695D05BC87&btag=e00090000", + "coverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "originalCoverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "musicId": "7483964151887448837" + }, + "locationMeta": { + "address": "Nuevo León, Mexico", + "city": "", + "cityCode": "3995465", + "countryCode": "3996063", + "locationName": "Monterrey", + "locationId": "22535865211434478" + }, + "webVideoUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7483964138323004678", + "mediaUrls": [], + "videoMeta": { + "height": 1024, + "width": 576, + "duration": 63, + "coverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/o8ekcWjACFCI4Lg99FGgSPgcugeLpYjAMLEIf2?lk3s=81f88b70&x-expires=1743213600&x-signature=%2FmykHev1Z%2FDub7Zg9LJpy5e%2FOTg%3D&shp=81f88b70&shcp=-", + "originalCoverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/o8ekcWjACFCI4Lg99FGgSPgcugeLpYjAMLEIf2?lk3s=81f88b70&x-expires=1743213600&x-signature=%2FmykHev1Z%2FDub7Zg9LJpy5e%2FOTg%3D&shp=81f88b70&shcp=-", + "definition": "540p", + "format": "mp4" + }, + "diggCount": 21, + "shareCount": 4, + "playCount": 442, + "collectCount": 1, + "commentCount": 3, + "mentions": [], + "detailedMentions": [], + "hashtags": [ + { + "name": "aquíseresuelve" + } + ], + "effectStickers": [], + "isSlideshow": false, + "isPinned": false, + "isSponsored": false, + "input": "adriandelagarzasantos", + "fromProfileSection": "videos" + }, + { + "id": "7483962985107541254", + "text": "Después de los hechos ocurridos, quiero dejar algo muy claro: en Monterrey no vamos a tolerar agresiones contra los policías que nos protegen.\n\nA quienes amenazan la paz de nuestra ciudad: el que la hace, la paga.\n\nHoy más que nunca trabajamos por fortalecer la seguridad de Monterrey.\n\n#AquíSeResuelve", + "textLanguage": "es", + "createTime": 1742495930, + "createTimeISO": "2025-03-20T18:38:50.000Z", + "isAd": false, + "authorMeta": { + "id": "7345639043763536901", + "name": "adriandelagarzasantos", + "profileUrl": "https://www.tiktok.com/@adriandelagarzasantos", + "nickName": "Adrián de la Garza", + "verified": false, + "signature": "Alcalde de Monterrey 2024 - 2027. Orgullosamente regio 🌄", + "bioLink": null, + "originalAvatarUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "avatar": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5", + "commerceUserInfo": { + "commerceUser": false + }, + "privateAccount": false, + "region": "MX", + "roomId": "", + "ttSeller": false, + "following": 2, + "friends": 2, + "fans": 17400, + "heart": 205100, + "video": 212, + "digg": 0 + }, + "musicMeta": { + "musicName": "sonido original", + "musicAuthor": "Adrián de la Garza", + "musicOriginal": true, + "playUrl": "https://v16m.tiktokcdn-us.com/4bdff0b23683a720a78a645ee17a7b4f/67e50e57/video/tos/useast5/tos-useast5-ve-27dcd7c799-tx/okGdJFDfJUB0kYLBR4EQKDBCwg1QY4C53nfMku/?a=1233&bti=ODszNWYuMDE6&ch=0&cr=0&dr=0&er=0&lr=default&cd=0%7C0%7C0%7C0&br=250&bt=125&ft=GSDrKInz7ThaGNyOXq8Zmo&mime_type=audio_mpeg&qs=6&rc=Z2c0MzZmaWg8ZjM2OzZlZ0BpM2xtPHY5cmo5eTMzNzU8M0AvNV8zYy0uX14xYi9fLi8tYSMwaV80MmRjLmJgLS1kMTZzcw%3D%3D&vvpl=1&l=20250327023631A6A10587A7695D05BC87&btag=e00090000", + "coverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "originalCoverMediumUrl": "https://p19-common-sign-va.tiktokcdn-us.com/tos-maliva-avt-0068/8dd74ab47f19b93fe53304f091910191~tplv-tiktokx-cropcenter:720:720.jpeg?dr=9640&refresh_token=40cf0f3a&x-expires=1743213600&x-signature=9ZNqdHCaoRigyWPq8vIj2du1qA0%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast8", + "musicId": "7483963050649340677" + }, + "locationMeta": { + "address": "Nuevo León, Mexico", + "city": "", + "cityCode": "3995465", + "countryCode": "3996063", + "locationName": "Monterrey", + "locationId": "22535865211434478" + }, + "webVideoUrl": "https://www.tiktok.com/@adriandelagarzasantos/video/7483962985107541254", + "mediaUrls": [], + "videoMeta": { + "height": 1024, + "width": 576, + "duration": 71, + "coverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/oAjcT99IDejJCICI4CLPAYvGgzSSdC3MeLuIeU?lk3s=81f88b70&x-expires=1743213600&x-signature=Xa2YuQ%2BXfk3EUqX409qcbVx8V0Y%3D&shp=81f88b70&shcp=-", + "originalCoverUrl": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/oAjcT99IDejJCICI4CLPAYvGgzSSdC3MeLuIeU?lk3s=81f88b70&x-expires=1743213600&x-signature=Xa2YuQ%2BXfk3EUqX409qcbVx8V0Y%3D&shp=81f88b70&shcp=-", + "definition": "540p", + "format": "mp4", + "subtitleLinks": [ + { + "language": "eng-US", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/a0ba9e8a781caa8a826dc85b672b7fc3/67e75cf7/video/tos/useast5/tos-useast5-v-0068c799-tx/3f2080e838cf449abc42de5c9b9bfb83/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=35410&bt=17705&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajZtcW85cmQ4eTMzNzczM0BpajZtcW85cmQ4eTMzNzczM0BtcWw0MmRjc2JgLS1kMTZzYSNtcWw0MmRjc2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/a0ba9e8a781caa8a826dc85b672b7fc3/67e75cf7/video/tos/useast5/tos-useast5-v-0068c799-tx/3f2080e838cf449abc42de5c9b9bfb83/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=35410&bt=17705&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajZtcW85cmQ4eTMzNzczM0BpajZtcW85cmQ4eTMzNzczM0BtcWw0MmRjc2JgLS1kMTZzYSNtcWw0MmRjc2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000" + }, + { + "language": "spa-ES", + "downloadLink": "https://v16m-webapp.tiktokcdn-us.com/3e8eaf591f6e013446d327e5abb4bd44/67e75cf7/video/tos/useast8/tos-useast8-v-0068c799-tx2/8877bbfde8d545b1ada7dc465a671aaa/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=35410&bt=17705&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajZtcW85cmQ4eTMzNzczM0BpajZtcW85cmQ4eTMzNzczM0BtcWw0MmRjc2JgLS1kMTZzYSNtcWw0MmRjc2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000", + "tiktokLink": "https://v16m-webapp.tiktokcdn-us.com/3e8eaf591f6e013446d327e5abb4bd44/67e75cf7/video/tos/useast8/tos-useast8-v-0068c799-tx2/8877bbfde8d545b1ada7dc465a671aaa/?a=1988&bti=ODszNWYuMDE6&ch=0&cr=3&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=35410&bt=17705&cs=0&ds=4&ft=4KLMeMzm8Zmo06asvb4jVvAuQpWrKsd.&mime_type=video_mp4&qs=13&rc=ajZtcW85cmQ4eTMzNzczM0BpajZtcW85cmQ4eTMzNzczM0BtcWw0MmRjc2JgLS1kMTZzYSNtcWw0MmRjc2JgLS1kMTZzcw%3D%3D&l=20250327023631A6A10587A7695D05BC87&btag=e00050000" + } + ] + }, + "diggCount": 1443, + "shareCount": 7, + "playCount": 117900, + "collectCount": 15, + "commentCount": 58, + "mentions": [], + "detailedMentions": [], + "hashtags": [ + { + "name": "aquíseresuelve" + } + ], + "effectStickers": [], + "isSlideshow": false, + "isPinned": false, + "isSponsored": false, + "input": "adriandelagarzasantos", + "fromProfileSection": "videos" + } +] \ No newline at end of file diff --git a/backend/app/testing/data/x/actors.md b/backend/app/testing/data/x/actors.md new file mode 100644 index 0000000000..122e8a7086 --- /dev/null +++ b/backend/app/testing/data/x/actors.md @@ -0,0 +1,8 @@ +# x profile +apidojo/twitter-scraper-lite + +# x posts +apidojo/twitter-scraper-lite + +# x comments +kaitoeasyapi/twitter-reply \ No newline at end of file diff --git a/backend/app/testing/data/x/comment_samples.json b/backend/app/testing/data/x/comment_samples.json new file mode 100644 index 0000000000..634ded1ace --- /dev/null +++ b/backend/app/testing/data/x/comment_samples.json @@ -0,0 +1,1118 @@ +[ + { + "type": "tweet", + "id": "1904885698240389472", + "url": "https://x.com/AdrianDeLaGarza/status/1904885698240389472", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1904885698240389472", + "text": "¡Mucha precaución al salir de casa! ⚠️\n\nLa lluvia estará constante en nuestra ciudad durante el día, es importante tomar medidas de precauciones: \n🚗 Maneja despacio. \n⏰ Toma tu tiempo. \n🚧 Verifica rutas de traslado.\n⛅️ Revisa constantemente el pronóstico del tiempo.\n📞 Ante una emergencia marca al 911.", + "source": "Twitter for iPhone", + "retweetCount": 5, + "replyCount": 13, + "likeCount": 41, + "quoteCount": 0, + "viewCount": "1787", + "createdAt": "Wed Mar 26 13:18:36 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": false, + "inReplyToId": null, + "conversationId": "1904885698240389472", + "inReplyToUserId": "", + "inReplyToUsername": "", + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72177, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [], + "profile_bio": {} + }, + "extendedEntities": {}, + "card": {}, + "place": {}, + "entities": { + "hashtags": [], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904962012481753405", + "url": "https://x.com/karlaelias387/status/1904962012481753405", + "twitterUrl": "https://twitter.com/karlaelias387/status/1904962012481753405", + "text": "@AdrianDeLaGarza Hay que manejar con cuidado, luego hay accidentes", + "source": "", + "retweetCount": 0, + "replyCount": 0, + "likeCount": 0, + "quoteCount": 0, + "viewCount": 5, + "createdAt": "Wed Mar 26 18:21:50 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": true, + "inReplyToId": "1904885698240389472", + "conversationId": "1904885698240389472", + "inReplyToUserId": "2357040230", + "inReplyToUsername": "AdrianDeLaGarza", + "isPinned": false, + "author": { + "type": "user", + "userName": "karlaelias387", + "url": "https://x.com/karlaelias387", + "twitterUrl": "https://twitter.com/karlaelias387", + "id": "1893164360580964352", + "name": "Karla Elias", + "isVerified": false, + "isBlueVerified": false, + "profilePicture": "https://pbs.twimg.com/profile_images/1893164492436934656/0XRBHP5C_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/1893164360580964352/1740200664", + "description": "", + "location": "", + "followers": 0, + "following": 15, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 05:02:21 +0000 2025", + "entities": { + "description": { + "urls": [] + }, + "url": {} + }, + "fastFollowersCount": 0, + "favouritesCount": 57, + "hasCustomTimelines": true, + "isTranslator": false, + "mediaCount": 0, + "statusesCount": 51, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [], + "profile_bio": { + "description": "", + "entities": { + "description": {} + } + } + }, + "extendedEntities": {}, + "card": null, + "place": {}, + "entities": { + "user_mentions": [ + { + "id_str": "2357040230", + "indices": [ + 0, + 16 + ], + "name": "Adrián de la Garza", + "screen_name": "AdrianDeLaGarza" + } + ] + }, + "reply_to_user_results": { + "rest_id": "2357040230", + "result": { + "__typename": "User", + "rest_id": "2357040230", + "core": { + "screen_name": "AdrianDeLaGarza" + } + } + }, + "quoted_tweet_results": null, + "quoted_tweet": null, + "retweeted_tweet": null, + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904960877163397329", + "url": "https://x.com/edsonrojas1999/status/1904960877163397329", + "twitterUrl": "https://twitter.com/edsonrojas1999/status/1904960877163397329", + "text": "@AdrianDeLaGarza Hay que manejar con cuidado", + "source": "", + "retweetCount": 0, + "replyCount": 0, + "likeCount": 0, + "quoteCount": 0, + "viewCount": 4, + "createdAt": "Wed Mar 26 18:17:20 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": true, + "inReplyToId": "1904885698240389472", + "conversationId": "1904885698240389472", + "inReplyToUserId": "2357040230", + "inReplyToUsername": "AdrianDeLaGarza", + "isPinned": false, + "author": { + "type": "user", + "userName": "edsonrojas1999", + "url": "https://x.com/edsonrojas1999", + "twitterUrl": "https://twitter.com/edsonrojas1999", + "id": "1893169920999129088", + "name": "Edson Rojas", + "isVerified": false, + "isBlueVerified": false, + "profilePicture": "https://pbs.twimg.com/profile_images/1893170016465362944/iBVpYZET_normal.jpg", + "coverPicture": "", + "description": "", + "location": "", + "followers": 0, + "following": 4, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 05:24:23 +0000 2025", + "entities": { + "description": { + "urls": [] + }, + "url": {} + }, + "fastFollowersCount": 0, + "favouritesCount": 55, + "hasCustomTimelines": true, + "isTranslator": false, + "mediaCount": 0, + "statusesCount": 47, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [], + "profile_bio": { + "description": "", + "entities": { + "description": {} + } + } + }, + "extendedEntities": {}, + "card": null, + "place": {}, + "entities": { + "user_mentions": [ + { + "id_str": "2357040230", + "indices": [ + 0, + 16 + ], + "name": "Adrián de la Garza", + "screen_name": "AdrianDeLaGarza" + } + ] + }, + "reply_to_user_results": { + "rest_id": "2357040230", + "result": { + "__typename": "User", + "rest_id": "2357040230", + "core": { + "screen_name": "AdrianDeLaGarza" + } + } + }, + "quoted_tweet_results": null, + "quoted_tweet": null, + "retweeted_tweet": null, + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904960250437939618", + "url": "https://x.com/antoniosan970/status/1904960250437939618", + "twitterUrl": "https://twitter.com/antoniosan970/status/1904960250437939618", + "text": "@AdrianDeLaGarza Hace rato estaba muy fuerte allá para la zona del topo chico", + "source": "", + "retweetCount": 0, + "replyCount": 0, + "likeCount": 0, + "quoteCount": 0, + "viewCount": 12, + "createdAt": "Wed Mar 26 18:14:50 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": true, + "inReplyToId": "1904885698240389472", + "conversationId": "1904885698240389472", + "inReplyToUserId": "2357040230", + "inReplyToUsername": "AdrianDeLaGarza", + "isPinned": false, + "author": { + "type": "user", + "userName": "antoniosan970", + "url": "https://x.com/antoniosan970", + "twitterUrl": "https://twitter.com/antoniosan970", + "id": "1893171956536750080", + "name": "Antonio Sandoval", + "isVerified": false, + "isBlueVerified": false, + "profilePicture": "https://pbs.twimg.com/profile_images/1893172077722501120/DejQwfug_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/1893171956536750080/1740202561", + "description": "", + "location": "", + "followers": 2, + "following": 23, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 05:32:33 +0000 2025", + "entities": { + "description": { + "urls": [] + }, + "url": {} + }, + "fastFollowersCount": 0, + "favouritesCount": 57, + "hasCustomTimelines": true, + "isTranslator": false, + "mediaCount": 0, + "statusesCount": 50, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [], + "profile_bio": { + "description": "", + "entities": { + "description": {} + } + } + }, + "extendedEntities": {}, + "card": null, + "place": {}, + "entities": { + "user_mentions": [ + { + "id_str": "2357040230", + "indices": [ + 0, + 16 + ], + "name": "Adrián de la Garza", + "screen_name": "AdrianDeLaGarza" + } + ] + }, + "reply_to_user_results": { + "rest_id": "2357040230", + "result": { + "__typename": "User", + "rest_id": "2357040230", + "core": { + "screen_name": "AdrianDeLaGarza" + } + } + }, + "quoted_tweet_results": null, + "quoted_tweet": null, + "retweeted_tweet": null, + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904958735732125951", + "url": "https://x.com/AbigailTor9682/status/1904958735732125951", + "twitterUrl": "https://twitter.com/AbigailTor9682/status/1904958735732125951", + "text": "@AdrianDeLaGarza Gracias Alcalde", + "source": "", + "retweetCount": 0, + "replyCount": 0, + "likeCount": 0, + "quoteCount": 0, + "viewCount": 4, + "createdAt": "Wed Mar 26 18:08:49 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": true, + "inReplyToId": "1904885698240389472", + "conversationId": "1904885698240389472", + "inReplyToUserId": "2357040230", + "inReplyToUsername": "AdrianDeLaGarza", + "isPinned": false, + "author": { + "type": "user", + "userName": "AbigailTor9682", + "url": "https://x.com/AbigailTor9682", + "twitterUrl": "https://twitter.com/AbigailTor9682", + "id": "1893166710364004352", + "name": "Abigail Torres", + "isVerified": false, + "isBlueVerified": false, + "profilePicture": "https://pbs.twimg.com/profile_images/1893166819248160768/O9geVNDa_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/1893166710364004352/1740201233", + "description": "", + "location": "", + "followers": 1, + "following": 9, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 05:11:44 +0000 2025", + "entities": { + "description": { + "urls": [] + }, + "url": {} + }, + "fastFollowersCount": 0, + "favouritesCount": 54, + "hasCustomTimelines": true, + "isTranslator": false, + "mediaCount": 0, + "statusesCount": 53, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [], + "profile_bio": { + "description": "", + "entities": { + "description": {} + } + } + }, + "extendedEntities": {}, + "card": null, + "place": {}, + "entities": { + "user_mentions": [ + { + "id_str": "2357040230", + "indices": [ + 0, + 16 + ], + "name": "Adrián de la Garza", + "screen_name": "AdrianDeLaGarza" + } + ] + }, + "reply_to_user_results": { + "rest_id": "2357040230", + "result": { + "__typename": "User", + "rest_id": "2357040230", + "core": { + "screen_name": "AdrianDeLaGarza" + } + } + }, + "quoted_tweet_results": null, + "quoted_tweet": null, + "retweeted_tweet": null, + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904958170356686980", + "url": "https://x.com/RobertoGar76469/status/1904958170356686980", + "twitterUrl": "https://twitter.com/RobertoGar76469/status/1904958170356686980", + "text": "@AdrianDeLaGarza Gracias Alcalde por la información", + "source": "", + "retweetCount": 0, + "replyCount": 0, + "likeCount": 0, + "quoteCount": 0, + "viewCount": 7, + "createdAt": "Wed Mar 26 18:06:34 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": true, + "inReplyToId": "1904885698240389472", + "conversationId": "1904885698240389472", + "inReplyToUserId": "2357040230", + "inReplyToUsername": "AdrianDeLaGarza", + "isPinned": false, + "author": { + "type": "user", + "userName": "RobertoGar76469", + "url": "https://x.com/RobertoGar76469", + "twitterUrl": "https://twitter.com/RobertoGar76469", + "id": "1893180995224117250", + "name": "Roberto Garza", + "isVerified": false, + "isBlueVerified": false, + "profilePicture": "https://pbs.twimg.com/profile_images/1893181081383211008/SV4Bg1Px_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/1893180995224117250/1740204577", + "description": "", + "location": "", + "followers": 0, + "following": 14, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 06:08:25 +0000 2025", + "entities": { + "description": { + "urls": [] + }, + "url": {} + }, + "fastFollowersCount": 0, + "favouritesCount": 54, + "hasCustomTimelines": true, + "isTranslator": false, + "mediaCount": 0, + "statusesCount": 47, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [], + "profile_bio": { + "description": "", + "entities": { + "description": {} + } + } + }, + "extendedEntities": {}, + "card": null, + "place": {}, + "entities": { + "user_mentions": [ + { + "id_str": "2357040230", + "indices": [ + 0, + 16 + ], + "name": "Adrián de la Garza", + "screen_name": "AdrianDeLaGarza" + } + ] + }, + "reply_to_user_results": { + "rest_id": "2357040230", + "result": { + "__typename": "User", + "rest_id": "2357040230", + "core": { + "screen_name": "AdrianDeLaGarza" + } + } + }, + "quoted_tweet_results": null, + "quoted_tweet": null, + "retweeted_tweet": null, + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904955280070258823", + "url": "https://x.com/everardopej/status/1904955280070258823", + "twitterUrl": "https://twitter.com/everardopej/status/1904955280070258823", + "text": "@AdrianDeLaGarza Gracias Alcalde", + "source": "", + "retweetCount": 0, + "replyCount": 0, + "likeCount": 0, + "quoteCount": 0, + "viewCount": 11, + "createdAt": "Wed Mar 26 17:55:05 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": true, + "inReplyToId": "1904885698240389472", + "conversationId": "1904885698240389472", + "inReplyToUserId": "2357040230", + "inReplyToUsername": "AdrianDeLaGarza", + "isPinned": false, + "author": { + "type": "user", + "userName": "everardopej", + "url": "https://x.com/everardopej", + "twitterUrl": "https://twitter.com/everardopej", + "id": "1893177936464728064", + "name": "Everardo Peña", + "isVerified": false, + "isBlueVerified": false, + "profilePicture": "https://pbs.twimg.com/profile_images/1893178018211762176/Erz4rqlz_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/1893177936464728064/1740203874", + "description": "", + "location": "", + "followers": 1, + "following": 16, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 05:56:14 +0000 2025", + "entities": { + "description": { + "urls": [] + }, + "url": {} + }, + "fastFollowersCount": 0, + "favouritesCount": 55, + "hasCustomTimelines": true, + "isTranslator": false, + "mediaCount": 0, + "statusesCount": 44, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [], + "profile_bio": { + "description": "", + "entities": { + "description": {} + } + } + }, + "extendedEntities": {}, + "card": null, + "place": {}, + "entities": { + "user_mentions": [ + { + "id_str": "2357040230", + "indices": [ + 0, + 16 + ], + "name": "Adrián de la Garza", + "screen_name": "AdrianDeLaGarza" + } + ] + }, + "reply_to_user_results": { + "rest_id": "2357040230", + "result": { + "__typename": "User", + "rest_id": "2357040230", + "core": { + "screen_name": "AdrianDeLaGarza" + } + } + }, + "quoted_tweet_results": null, + "quoted_tweet": null, + "retweeted_tweet": null, + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904913287080259663", + "url": "https://x.com/BeltEmilio/status/1904913287080259663", + "twitterUrl": "https://twitter.com/BeltEmilio/status/1904913287080259663", + "text": "@AdrianDeLaGarza miles de personas transitan en carretera nacional una gran parte pertenece a Monterrey y el libramiento un oficial d tránsito lo quitó antes de hora programada urge tus elementos hagan su trabajo correctamente no saben dirigir un caos ahora complica el tráfico @samuel_garcias https://t.co/7p6qLea1PS", + "source": "", + "retweetCount": 0, + "replyCount": 0, + "likeCount": 0, + "quoteCount": 0, + "viewCount": 43, + "createdAt": "Wed Mar 26 15:08:13 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": true, + "inReplyToId": "1904885698240389472", + "conversationId": "1904885698240389472", + "inReplyToUserId": "2357040230", + "inReplyToUsername": "AdrianDeLaGarza", + "isPinned": false, + "author": { + "type": "user", + "userName": "BeltEmilio", + "url": "https://x.com/BeltEmilio", + "twitterUrl": "https://twitter.com/BeltEmilio", + "id": "1423397816131637251", + "name": "Emilio Nieto Belt", + "isVerified": false, + "isBlueVerified": false, + "profilePicture": "https://pbs.twimg.com/profile_images/1423397994804699145/VJLph9dq_normal.jpg", + "coverPicture": "", + "description": "", + "location": "", + "followers": 10, + "following": 58, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Thu Aug 05 21:38:21 +0000 2021", + "entities": { + "description": { + "urls": [] + }, + "url": {} + }, + "fastFollowersCount": 0, + "favouritesCount": 50, + "hasCustomTimelines": true, + "isTranslator": false, + "mediaCount": 37, + "statusesCount": 121, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [], + "profile_bio": { + "description": "Soy ferviente seguidor del rock en sus diferentes géneros, no tolero la injusticia y el abuso de cualquier tipo, me interesa estar informado para ayudar .", + "entities": { + "description": {} + } + } + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.twitter.com/7p6qLea1PS", + "expanded_url": "https://twitter.com/BeltEmilio/status/1904913287080259663/photo/1", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "h": 240, + "w": 240, + "x": 224, + "y": 318 + } + ] + }, + "orig": { + "faces": [ + { + "h": 240, + "w": 240, + "x": 224, + "y": 318 + } + ] + } + }, + "id_str": "1904913279333302272", + "indices": [ + 295, + 318 + ], + "media_key": "3_1904913279333302272", + "media_results": { + "id": "QXBpTWVkaWFSZXN1bHRzOgwAAQoAARpvnIjulgAACgACGm+cirxXME8AAA==", + "result": { + "__typename": "ApiMedia", + "id": "QXBpTWVkaWE6DAABCgABGm+ciO6WAAAKAAIab5yKvFcwTwAA", + "media_key": "3_1904913279333302272" + } + }, + "media_url_https": "https://pbs.twimg.com/media/Gm-ciO6WAAATOnL.jpg", + "original_info": { + "focus_rects": [ + { + "h": 645, + "w": 1152, + "x": 0, + "y": 958 + }, + { + "h": 1152, + "w": 1152, + "x": 0, + "y": 704 + }, + { + "h": 1313, + "w": 1152, + "x": 0, + "y": 624 + }, + { + "h": 2048, + "w": 1024, + "x": 0, + "y": 0 + }, + { + "h": 2048, + "w": 1152, + "x": 0, + "y": 0 + } + ], + "height": 2048, + "width": 1152 + }, + "sizes": { + "large": { + "h": 2048, + "w": 1152 + } + }, + "type": "photo", + "url": "https://t.co/7p6qLea1PS" + } + ] + }, + "card": null, + "place": {}, + "entities": { + "user_mentions": [ + { + "id_str": "2357040230", + "indices": [ + 0, + 16 + ], + "name": "Adrián de la Garza", + "screen_name": "AdrianDeLaGarza" + }, + { + "id_str": "258973851", + "indices": [ + 279, + 294 + ], + "name": "Samuel García", + "screen_name": "samuel_garcias" + } + ] + }, + "reply_to_user_results": { + "rest_id": "2357040230", + "result": { + "__typename": "User", + "rest_id": "2357040230", + "core": { + "screen_name": "AdrianDeLaGarza" + } + } + }, + "quoted_tweet_results": null, + "quoted_tweet": null, + "retweeted_tweet": null, + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904912459091042436", + "url": "https://x.com/ja_flores52/status/1904912459091042436", + "twitterUrl": "https://twitter.com/ja_flores52/status/1904912459091042436", + "text": "@AdrianDeLaGarza Un buen alcalde diría:\n\nEstá lloviendo, pero el pavimento es antiderrapante, los semáforos están perfectamente sincronizados, no hay baches y puedes llegar a tiempo a tu trabajo. Pero si excedes la velocidad, te voy a quitar mucho dinero.", + "source": "", + "retweetCount": 0, + "replyCount": 0, + "likeCount": 0, + "quoteCount": 0, + "viewCount": 20, + "createdAt": "Wed Mar 26 15:04:56 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": true, + "inReplyToId": "1904885698240389472", + "conversationId": "1904885698240389472", + "inReplyToUserId": "2357040230", + "inReplyToUsername": "AdrianDeLaGarza", + "isPinned": false, + "author": { + "type": "user", + "userName": "ja_flores52", + "url": "https://x.com/ja_flores52", + "twitterUrl": "https://twitter.com/ja_flores52", + "id": "482614022", + "name": "Boanerge⚡Hijo del Trueno", + "isVerified": false, + "isBlueVerified": false, + "profilePicture": "https://pbs.twimg.com/profile_images/1589256254588948480/MM4YTcC8_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/482614022/1702924630", + "description": "", + "location": "Monterrey", + "followers": 454, + "following": 449, + "status": "", + "canDm": true, + "canMediaTag": true, + "createdAt": "Sat Feb 04 03:41:23 +0000 2012", + "entities": { + "description": { + "urls": [] + }, + "url": {} + }, + "fastFollowersCount": 0, + "favouritesCount": 16522, + "hasCustomTimelines": true, + "isTranslator": true, + "mediaCount": 3573, + "statusesCount": 44159, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [], + "profile_bio": { + "description": "Andrés Manuel López Obrador, el más inepto, corrupto, espurio y mitómano presidente en toda la historia de México!", + "entities": { + "description": {} + } + } + }, + "extendedEntities": {}, + "card": null, + "place": { + "bounding_box_polygon": { + "coordinates": [ + [ + [ + -100.421037, + 25.4805381 + ], + [ + -100.421037, + 25.802899 + ], + [ + -100.166146, + 25.802899 + ], + [ + -100.166146, + 25.4805381 + ], + [ + -100.421037, + 25.4805381 + ] + ] + ], + "type": "Polygon" + }, + "country": "Mexico", + "country_code": "MX", + "full_name": "Monterrey, Nuevo León", + "id": "b19e24ce42ccd6aa", + "name": "Monterrey", + "place_type": "city" + }, + "entities": { + "user_mentions": [ + { + "id_str": "2357040230", + "indices": [ + 0, + 16 + ], + "name": "Adrián de la Garza", + "screen_name": "AdrianDeLaGarza" + } + ] + }, + "reply_to_user_results": { + "rest_id": "2357040230", + "result": { + "__typename": "User", + "rest_id": "2357040230", + "core": { + "screen_name": "AdrianDeLaGarza" + } + } + }, + "quoted_tweet_results": null, + "quoted_tweet": null, + "retweeted_tweet": null, + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904904237324136494", + "url": "https://x.com/manriquez_95454/status/1904904237324136494", + "twitterUrl": "https://twitter.com/manriquez_95454/status/1904904237324136494", + "text": "@AdrianDeLaGarza https://t.co/ocnBnSdoFc", + "source": "", + "retweetCount": 0, + "replyCount": 0, + "likeCount": 0, + "quoteCount": 0, + "viewCount": 5, + "createdAt": "Wed Mar 26 14:32:16 +0000 2025", + "lang": "qme", + "bookmarkCount": 0, + "isReply": true, + "inReplyToId": "1904900290119139722", + "conversationId": "1904885698240389472", + "inReplyToUserId": "1712660960366755840", + "inReplyToUsername": "manriquez_95454", + "isPinned": false, + "author": { + "type": "user", + "userName": "manriquez_95454", + "url": "https://x.com/manriquez_95454", + "twitterUrl": "https://twitter.com/manriquez_95454", + "id": "1712660960366755840", + "name": "José sixto Verdasco", + "isVerified": false, + "isBlueVerified": false, + "profilePicture": "https://pbs.twimg.com/profile_images/1848026271311069185/yIFM00-6_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/1712660960366755840/1726674351", + "description": "", + "location": "", + "followers": 12, + "following": 153, + "status": "", + "canDm": false, + "canMediaTag": false, + "createdAt": "Fri Oct 13 02:47:03 +0000 2023", + "entities": { + "description": { + "urls": [] + }, + "url": {} + }, + "fastFollowersCount": 0, + "favouritesCount": 2348, + "hasCustomTimelines": true, + "isTranslator": false, + "mediaCount": 86, + "statusesCount": 1020, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [], + "profile_bio": { + "description": "", + "entities": { + "description": {} + } + } + }, + "extendedEntities": { + "media": [ + { + "additional_media_info": { + "monetizable": false + }, + "display_url": "pic.twitter.com/ocnBnSdoFc", + "expanded_url": "https://twitter.com/manriquez_95454/status/1904904237324136494/video/1", + "ext_media_availability": { + "status": "Available" + }, + "id_str": "1904904205179015168", + "indices": [ + 17, + 40 + ], + "media_key": "7_1904904205179015168", + "media_results": { + "id": "QXBpTWVkaWFSZXN1bHRzOgwAAwoAARpvlEgw19AACgACGm+UT6zXcC4AAA==", + "result": { + "__typename": "ApiMedia", + "id": "QXBpTWVkaWE6DAADCgABGm+USDDX0AAKAAIab5RPrNdwLgAA", + "media_key": "7_1904904205179015168" + } + }, + "media_url_https": "https://pbs.twimg.com/ext_tw_video_thumb/1904904205179015168/pu/img/8WKB5c2w_dkpjchc.jpg", + "original_info": { + "focus_rects": [], + "height": 864, + "width": 480 + }, + "sizes": { + "large": { + "h": 864, + "w": 480 + } + }, + "type": "video", + "url": "https://t.co/ocnBnSdoFc", + "video_info": { + "aspect_ratio": [ + 5, + 9 + ], + "duration_millis": 46954, + "variants": [ + { + "content_type": "application/x-mpegURL", + "url": "https://video.twimg.com/ext_tw_video/1904904205179015168/pu/pl/LICZf7mEKr1ZepeA.m3u8?tag=12" + }, + { + "bitrate": 632000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/ext_tw_video/1904904205179015168/pu/vid/avc1/320x576/XN70PNXs4rJmXtL-.mp4?tag=12" + }, + { + "bitrate": 950000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/ext_tw_video/1904904205179015168/pu/vid/avc1/480x864/IwNS1luTuU-MjuaG.mp4?tag=12" + } + ] + } + } + ] + }, + "card": null, + "place": {}, + "entities": { + "user_mentions": [ + { + "id_str": "2357040230", + "indices": [ + 0, + 16 + ], + "name": "Adrián de la Garza", + "screen_name": "AdrianDeLaGarza" + } + ] + }, + "reply_to_user_results": { + "rest_id": "1712660960366755840", + "result": { + "__typename": "User", + "rest_id": "1712660960366755840", + "core": { + "screen_name": "manriquez_95454" + } + } + }, + "quoted_tweet_results": null, + "quoted_tweet": null, + "retweeted_tweet": null, + "isConversationControlled": false + } +] \ No newline at end of file diff --git a/backend/app/testing/data/x/post_samples.json b/backend/app/testing/data/x/post_samples.json new file mode 100644 index 0000000000..b32f2efa2f --- /dev/null +++ b/backend/app/testing/data/x/post_samples.json @@ -0,0 +1,4557 @@ +[ + { + "type": "tweet", + "id": "1905018863219364194", + "url": "https://x.com/AdrianDeLaGarza/status/1905018863219364194", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1905018863219364194", + "text": "Mejorar la seguridad es una de nuestras prioridades; hoy damos un paso más para tener una ciudad con orden y paz. \n\nPresenté la nueva imagen de nuestra Policía y relanzamos la campaña de reclutamiento para seguir fortaleciendo a nuestra corporación. \n\nEn Monterrey, reconocemos el esfuerzo de quienes nos protegen, por eso mejoramos sus condiciones laborales, con un salario competitivo, prestaciones de calidad y un plan de vida y carrera que garantiza su bienestar y el de sus familias.\n\n¡Únete a la Policía de Monterrey! Más que un trabajo, un proyecto de vida. \n\n📞 Teléfono: 81 5102 6750\n📲 WhatsApp: 81 1533 1942\n\n#AquíSeResuelve", + "fullText": "Mejorar la seguridad es una de nuestras prioridades; hoy damos un paso más para tener una ciudad con orden y paz. \n\nPresenté la nueva imagen de nuestra Policía y relanzamos la campaña de reclutamiento para seguir fortaleciendo a nuestra corporación. \n\nEn Monterrey, reconocemos el https://t.co/QftMT1FQ0e", + "source": "Twitter for iPhone", + "retweetCount": 4, + "replyCount": 4, + "likeCount": 12, + "quoteCount": 0, + "viewCount": 627, + "createdAt": "Wed Mar 26 22:07:45 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": false, + "conversationId": "1905018863219364194", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/QftMT1FQ0e", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1905018863219364194/photo/1", + "id_str": "1905018858219487232", + "indices": [ + 281, + 304 + ], + "media_key": "3_1905018858219487232", + "media_url_https": "https://pbs.twimg.com/media/Gm_8ju6XwAAstFg.jpg", + "type": "photo", + "url": "https://t.co/QftMT1FQ0e", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 656, + "y": 495, + "h": 48, + "w": 48 + } + ] + }, + "medium": { + "faces": [ + { + "x": 583, + "y": 440, + "h": 42, + "w": 42 + } + ] + }, + "small": { + "faces": [ + { + "x": 330, + "y": 249, + "h": 24, + "w": 24 + } + ] + }, + "orig": { + "faces": [ + { + "x": 656, + "y": 495, + "h": 48, + "w": 48 + } + ] + } + }, + "sizes": { + "large": { + "h": 1350, + "w": 1080, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 960, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 544, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1350, + "width": 1080, + "focus_rects": [ + { + "x": 0, + "y": 744, + "w": 1080, + "h": 605 + }, + { + "x": 0, + "y": 270, + "w": 1080, + "h": 1080 + }, + { + "x": 0, + "y": 119, + "w": 1080, + "h": 1231 + }, + { + "x": 405, + "y": 0, + "w": 675, + "h": 1350 + }, + { + "x": 0, + "y": 0, + "w": 1080, + "h": 1350 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1905018858219487232" + } + } + }, + { + "display_url": "pic.x.com/QftMT1FQ0e", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1905018863219364194/photo/1", + "id_str": "1905018858223665152", + "indices": [ + 281, + 304 + ], + "media_key": "3_1905018858223665152", + "media_url_https": "https://pbs.twimg.com/media/Gm_8ju7XgAAVS6I.jpg", + "type": "photo", + "url": "https://t.co/QftMT1FQ0e", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 842, + "y": 191, + "h": 48, + "w": 48 + }, + { + "x": 721, + "y": 930, + "h": 60, + "w": 60 + } + ] + }, + "medium": { + "faces": [ + { + "x": 748, + "y": 169, + "h": 42, + "w": 42 + }, + { + "x": 640, + "y": 826, + "h": 53, + "w": 53 + } + ] + }, + "small": { + "faces": [ + { + "x": 424, + "y": 96, + "h": 24, + "w": 24 + }, + { + "x": 363, + "y": 468, + "h": 30, + "w": 30 + } + ] + }, + "orig": { + "faces": [ + { + "x": 842, + "y": 191, + "h": 48, + "w": 48 + }, + { + "x": 721, + "y": 930, + "h": 60, + "w": 60 + } + ] + } + }, + "sizes": { + "large": { + "h": 1350, + "w": 1080, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 960, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 544, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1350, + "width": 1080, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1080, + "h": 605 + }, + { + "x": 0, + "y": 0, + "w": 1080, + "h": 1080 + }, + { + "x": 0, + "y": 0, + "w": 1080, + "h": 1231 + }, + { + "x": 304, + "y": 0, + "w": 675, + "h": 1350 + }, + { + "x": 0, + "y": 0, + "w": 1080, + "h": 1350 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1905018858223665152" + } + } + }, + { + "display_url": "pic.x.com/QftMT1FQ0e", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1905018863219364194/photo/1", + "id_str": "1905018858219384832", + "indices": [ + 281, + 304 + ], + "media_key": "3_1905018858219384832", + "media_url_https": "https://pbs.twimg.com/media/Gm_8ju6WMAAX98A.jpg", + "type": "photo", + "url": "https://t.co/QftMT1FQ0e", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 458, + "y": 444, + "h": 77, + "w": 77 + }, + { + "x": 742, + "y": 341, + "h": 105, + "w": 105 + }, + { + "x": 411, + "y": 185, + "h": 100, + "w": 100 + } + ] + }, + "medium": { + "faces": [ + { + "x": 407, + "y": 394, + "h": 68, + "w": 68 + }, + { + "x": 659, + "y": 303, + "h": 93, + "w": 93 + }, + { + "x": 365, + "y": 164, + "h": 88, + "w": 88 + } + ] + }, + "small": { + "faces": [ + { + "x": 230, + "y": 223, + "h": 38, + "w": 38 + }, + { + "x": 373, + "y": 171, + "h": 52, + "w": 52 + }, + { + "x": 207, + "y": 93, + "h": 50, + "w": 50 + } + ] + }, + "orig": { + "faces": [ + { + "x": 458, + "y": 444, + "h": 77, + "w": 77 + }, + { + "x": 742, + "y": 341, + "h": 105, + "w": 105 + }, + { + "x": 411, + "y": 185, + "h": 100, + "w": 100 + } + ] + } + }, + "sizes": { + "large": { + "h": 1350, + "w": 1080, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 960, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 544, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1350, + "width": 1080, + "focus_rects": [ + { + "x": 0, + "y": 541, + "w": 1080, + "h": 605 + }, + { + "x": 0, + "y": 270, + "w": 1080, + "h": 1080 + }, + { + "x": 0, + "y": 119, + "w": 1080, + "h": 1231 + }, + { + "x": 0, + "y": 0, + "w": 675, + "h": 1350 + }, + { + "x": 0, + "y": 0, + "w": 1080, + "h": 1350 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1905018858219384832" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [ + { + "indices": [ + 618, + 633 + ], + "text": "AquíSeResuelve" + } + ], + "symbols": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/media/Gm_8ju6XwAAstFg.jpg", + "https://pbs.twimg.com/media/Gm_8ju7XgAAVS6I.jpg", + "https://pbs.twimg.com/media/Gm_8ju6WMAAX98A.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904944171422474407", + "url": "https://x.com/AdrianDeLaGarza/status/1904944171422474407", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1904944171422474407", + "text": "Hoy más que nunca, buscamos personas valientes, con vocación de servicio y el deseo de sumar por la seguridad en Monterrey. \n\nSi tienes el compromiso, la disciplina y el orgullo de proteger a tu comunidad, este es tu momento para unirte a la Policía de Monterrey. No es sólo un trabajo, es un proyecto de vida. \n\nÚnete a la mejor policía de México y sé parte del equipo que mantiene segura nuestra ciudad.\n\n¡Inscríbete hoy y transforma tu futuro! 🚓\n\n📞 Teléfono: 81 5102 6750\n📲 WhatsApp: 81 1533 1942\n\n#AquíSeResuelve", + "fullText": "Hoy más que nunca, buscamos personas valientes, con vocación de servicio y el deseo de sumar por la seguridad en Monterrey. \n\nSi tienes el compromiso, la disciplina y el orgullo de proteger a tu comunidad, este es tu momento para unirte a la Policía de Monterrey. No es sólo un https://t.co/aFbCzB8qQa", + "source": "Twitter for iPhone", + "retweetCount": 50, + "replyCount": 25, + "likeCount": 91, + "quoteCount": 13, + "viewCount": 9294, + "createdAt": "Wed Mar 26 17:10:57 +0000 2025", + "lang": "es", + "bookmarkCount": 2, + "isReply": false, + "conversationId": "1904944171422474407", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/aFbCzB8qQa", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904944171422474407/video/1", + "id_str": "1904944027696320512", + "indices": [ + 278, + 301 + ], + "media_key": "13_1904944027696320512", + "media_url_https": "https://pbs.twimg.com/amplify_video_thumb/1904944027696320512/img/XcoguvI4BV2Sap1r.jpg", + "type": "video", + "url": "https://t.co/aFbCzB8qQa", + "additional_media_info": { + "monetizable": false + }, + "ext_media_availability": { + "status": "Available" + }, + "sizes": { + "large": { + "h": 720, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 675, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 383, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 720, + "width": 1280, + "focus_rects": [] + }, + "allow_download_status": { + "allow_download": true + }, + "video_info": { + "aspect_ratio": [ + 16, + 9 + ], + "duration_millis": 60000, + "variants": [ + { + "content_type": "application/x-mpegURL", + "url": "https://video.twimg.com/amplify_video/1904944027696320512/pl/iRvK1LAeMXJgyAc7.m3u8?tag=16" + }, + { + "bitrate": 288000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1904944027696320512/vid/avc1/480x270/e6FRIqoCgq30SuEl.mp4?tag=16" + }, + { + "bitrate": 832000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1904944027696320512/vid/avc1/640x360/ll5fw51aIg7sVA-l.mp4?tag=16" + }, + { + "bitrate": 2176000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1904944027696320512/vid/avc1/1280x720/vPu5x7snMsEd6TTO.mp4?tag=16" + } + ] + }, + "media_results": { + "result": { + "media_key": "13_1904944027696320512" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [ + { + "indices": [ + 501, + 516 + ], + "text": "AquíSeResuelve" + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/amplify_video_thumb/1904944027696320512/img/XcoguvI4BV2Sap1r.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904885698240389472", + "url": "https://x.com/AdrianDeLaGarza/status/1904885698240389472", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1904885698240389472", + "text": "¡Mucha precaución al salir de casa! ⚠️\n\nLa lluvia estará constante en nuestra ciudad durante el día, es importante tomar medidas de precauciones: \n🚗 Maneja despacio. \n⏰ Toma tu tiempo. \n🚧 Verifica rutas de traslado.\n⛅️ Revisa constantemente el pronóstico del tiempo.\n📞 Ante una emergencia marca al 911.", + "fullText": "¡Mucha precaución al salir de casa! ⚠️\n\nLa lluvia estará constante en nuestra ciudad durante el día, es importante tomar medidas de precauciones: \n🚗 Maneja despacio. \n⏰ Toma tu tiempo. \n🚧 Verifica rutas de traslado.\n⛅️ Revisa constantemente el pronóstico del tiempo.\n📞 Ante", + "source": "Twitter for iPhone", + "retweetCount": 5, + "replyCount": 13, + "likeCount": 41, + "quoteCount": 0, + "viewCount": 1783, + "createdAt": "Wed Mar 26 13:18:36 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": false, + "conversationId": "1904885698240389472", + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": {}, + "card": {}, + "place": {}, + "entities": { + "hashtags": [], + "symbols": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904675146415112204", + "url": "https://x.com/AdrianDeLaGarza/status/1904675146415112204", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1904675146415112204", + "text": "Hoy me reuní con el Embajador de Taiwán, Iván Yueh-Jung Lee, para fortalecer lazos y explorar oportunidades de inversión e innovación para Monterrey. Compartimos la visión de trabajar y resolver para seguir impulsando el desarrollo de nuestra ciudad. \n\n#AquíSeResuelve https://t.co/ve0DjXICyw", + "fullText": "Hoy me reuní con el Embajador de Taiwán, Iván Yueh-Jung Lee, para fortalecer lazos y explorar oportunidades de inversión e innovación para Monterrey. Compartimos la visión de trabajar y resolver para seguir impulsando el desarrollo de nuestra ciudad. \n\n#AquíSeResuelve https://t.co/ve0DjXICyw", + "source": "Twitter for iPhone", + "retweetCount": 6, + "replyCount": 20, + "likeCount": 42, + "quoteCount": 0, + "viewCount": 1320, + "createdAt": "Tue Mar 25 23:21:56 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": false, + "conversationId": "1904675146415112204", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/ve0DjXICyw", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904675146415112204/photo/1", + "id_str": "1904675139788173312", + "indices": [ + 269, + 292 + ], + "media_key": "3_1904675139788173312", + "media_url_https": "https://pbs.twimg.com/media/Gm7D8r-XcAALziq.jpg", + "type": "photo", + "url": "https://t.co/ve0DjXICyw", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 741, + "y": 173, + "h": 113, + "w": 113 + }, + { + "x": 350, + "y": 123, + "h": 120, + "w": 120 + }, + { + "x": 947, + "y": 502, + "h": 145, + "w": 145 + }, + { + "x": 113, + "y": 515, + "h": 137, + "w": 137 + } + ] + }, + "medium": { + "faces": [ + { + "x": 694, + "y": 162, + "h": 105, + "w": 105 + }, + { + "x": 328, + "y": 115, + "h": 112, + "w": 112 + }, + { + "x": 887, + "y": 470, + "h": 135, + "w": 135 + }, + { + "x": 105, + "y": 482, + "h": 128, + "w": 128 + } + ] + }, + "small": { + "faces": [ + { + "x": 393, + "y": 91, + "h": 60, + "w": 60 + }, + { + "x": 185, + "y": 65, + "h": 63, + "w": 63 + }, + { + "x": 503, + "y": 266, + "h": 77, + "w": 77 + }, + { + "x": 60, + "y": 273, + "h": 72, + "w": 72 + } + ] + }, + "orig": { + "faces": [ + { + "x": 741, + "y": 173, + "h": 113, + "w": 113 + }, + { + "x": 350, + "y": 123, + "h": 120, + "w": 120 + }, + { + "x": 947, + "y": 502, + "h": 145, + "w": 145 + }, + { + "x": 113, + "y": 515, + "h": 137, + "w": 137 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 0, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 42, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 203, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904675139788173312" + } + } + }, + { + "display_url": "pic.x.com/ve0DjXICyw", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904675146415112204/photo/1", + "id_str": "1904675139788173315", + "indices": [ + 269, + 292 + ], + "media_key": "3_1904675139788173315", + "media_url_https": "https://pbs.twimg.com/media/Gm7D8r-XcAMuXiQ.jpg", + "type": "photo", + "url": "https://t.co/ve0DjXICyw", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 135, + "y": 438, + "h": 58, + "w": 58 + }, + { + "x": 247, + "y": 130, + "h": 153, + "w": 153 + }, + { + "x": 1121, + "y": 546, + "h": 138, + "w": 138 + }, + { + "x": 711, + "y": 196, + "h": 191, + "w": 191 + } + ] + }, + "medium": { + "faces": [ + { + "x": 126, + "y": 410, + "h": 54, + "w": 54 + }, + { + "x": 231, + "y": 121, + "h": 143, + "w": 143 + }, + { + "x": 1050, + "y": 511, + "h": 129, + "w": 129 + }, + { + "x": 666, + "y": 183, + "h": 179, + "w": 179 + } + ] + }, + "small": { + "faces": [ + { + "x": 71, + "y": 232, + "h": 30, + "w": 30 + }, + { + "x": 131, + "y": 69, + "h": 81, + "w": 81 + }, + { + "x": 595, + "y": 290, + "h": 73, + "w": 73 + }, + { + "x": 377, + "y": 104, + "h": 101, + "w": 101 + } + ] + }, + "orig": { + "faces": [ + { + "x": 135, + "y": 438, + "h": 58, + "w": 58 + }, + { + "x": 247, + "y": 130, + "h": 153, + "w": 153 + }, + { + "x": 1121, + "y": 546, + "h": 138, + "w": 138 + }, + { + "x": 711, + "y": 196, + "h": 191, + "w": 191 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 0, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 75, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904675139788173315" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [ + { + "indices": [ + 253, + 268 + ], + "text": "AquíSeResuelve" + } + ], + "media": [ + { + "display_url": "pic.x.com/ve0DjXICyw", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904675146415112204/photo/1", + "id_str": "1904675139788173312", + "indices": [ + 269, + 292 + ], + "media_key": "3_1904675139788173312", + "media_url_https": "https://pbs.twimg.com/media/Gm7D8r-XcAALziq.jpg", + "type": "photo", + "url": "https://t.co/ve0DjXICyw", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 741, + "y": 173, + "h": 113, + "w": 113 + }, + { + "x": 350, + "y": 123, + "h": 120, + "w": 120 + }, + { + "x": 947, + "y": 502, + "h": 145, + "w": 145 + }, + { + "x": 113, + "y": 515, + "h": 137, + "w": 137 + } + ] + }, + "medium": { + "faces": [ + { + "x": 694, + "y": 162, + "h": 105, + "w": 105 + }, + { + "x": 328, + "y": 115, + "h": 112, + "w": 112 + }, + { + "x": 887, + "y": 470, + "h": 135, + "w": 135 + }, + { + "x": 105, + "y": 482, + "h": 128, + "w": 128 + } + ] + }, + "small": { + "faces": [ + { + "x": 393, + "y": 91, + "h": 60, + "w": 60 + }, + { + "x": 185, + "y": 65, + "h": 63, + "w": 63 + }, + { + "x": 503, + "y": 266, + "h": 77, + "w": 77 + }, + { + "x": 60, + "y": 273, + "h": 72, + "w": 72 + } + ] + }, + "orig": { + "faces": [ + { + "x": 741, + "y": 173, + "h": 113, + "w": 113 + }, + { + "x": 350, + "y": 123, + "h": 120, + "w": 120 + }, + { + "x": 947, + "y": 502, + "h": 145, + "w": 145 + }, + { + "x": 113, + "y": 515, + "h": 137, + "w": 137 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 0, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 42, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 203, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904675139788173312" + } + } + }, + { + "display_url": "pic.x.com/ve0DjXICyw", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904675146415112204/photo/1", + "id_str": "1904675139788173315", + "indices": [ + 269, + 292 + ], + "media_key": "3_1904675139788173315", + "media_url_https": "https://pbs.twimg.com/media/Gm7D8r-XcAMuXiQ.jpg", + "type": "photo", + "url": "https://t.co/ve0DjXICyw", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 135, + "y": 438, + "h": 58, + "w": 58 + }, + { + "x": 247, + "y": 130, + "h": 153, + "w": 153 + }, + { + "x": 1121, + "y": 546, + "h": 138, + "w": 138 + }, + { + "x": 711, + "y": 196, + "h": 191, + "w": 191 + } + ] + }, + "medium": { + "faces": [ + { + "x": 126, + "y": 410, + "h": 54, + "w": 54 + }, + { + "x": 231, + "y": 121, + "h": 143, + "w": 143 + }, + { + "x": 1050, + "y": 511, + "h": 129, + "w": 129 + }, + { + "x": 666, + "y": 183, + "h": 179, + "w": 179 + } + ] + }, + "small": { + "faces": [ + { + "x": 71, + "y": 232, + "h": 30, + "w": 30 + }, + { + "x": 131, + "y": 69, + "h": 81, + "w": 81 + }, + { + "x": 595, + "y": 290, + "h": 73, + "w": 73 + }, + { + "x": 377, + "y": 104, + "h": 101, + "w": 101 + } + ] + }, + "orig": { + "faces": [ + { + "x": 135, + "y": 438, + "h": 58, + "w": 58 + }, + { + "x": 247, + "y": 130, + "h": 153, + "w": 153 + }, + { + "x": 1121, + "y": 546, + "h": 138, + "w": 138 + }, + { + "x": 711, + "y": 196, + "h": 191, + "w": 191 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 0, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 75, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904675139788173315" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/media/Gm7D8r-XcAALziq.jpg", + "https://pbs.twimg.com/media/Gm7D8r-XcAMuXiQ.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904640970614071526", + "url": "https://x.com/AdrianDeLaGarza/status/1904640970614071526", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1904640970614071526", + "text": "¡Corramos por una causa! \nEl 21K Monterrey y la carrera 5K están de regreso este 6 de abril, en una ruta ya conocida como es la Vía Deportiva y en el circuito interior del Parque España.\n\nTodo lo recaudado será para apoyar a atletas con discapacidad de nuestra ciudad.\n\nInscríbete antes del 3 de abril en Innovasport o Trotime.\n\n#AquíSeResuelve", + "fullText": "¡Corramos por una causa! \nEl 21K Monterrey y la carrera 5K están de regreso este 6 de abril, en una ruta ya conocida como es la Vía Deportiva y en el circuito interior del Parque España.\n\nTodo lo recaudado será para apoyar a atletas con discapacidad de nuestra ciudad.\n\nInscríbete https://t.co/KLRfejrvOr", + "source": "Twitter for iPhone", + "retweetCount": 5, + "replyCount": 28, + "likeCount": 25, + "quoteCount": 0, + "viewCount": 1328, + "createdAt": "Tue Mar 25 21:06:08 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": false, + "conversationId": "1904640970614071526", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/KLRfejrvOr", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904640970614071526/photo/1", + "id_str": "1904640964775260160", + "indices": [ + 281, + 304 + ], + "media_key": "3_1904640964775260160", + "media_url_https": "https://pbs.twimg.com/media/Gm6k3cIWMAAQEsB.jpg", + "type": "photo", + "url": "https://t.co/KLRfejrvOr", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [] + }, + "medium": { + "faces": [] + }, + "small": { + "faces": [] + }, + "orig": { + "faces": [] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 54, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 106, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 267, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904640964775260160" + } + } + }, + { + "display_url": "pic.x.com/KLRfejrvOr", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904640970614071526/photo/1", + "id_str": "1904640964779491328", + "indices": [ + 281, + 304 + ], + "media_key": "3_1904640964779491328", + "media_url_https": "https://pbs.twimg.com/media/Gm6k3cJWwAArQFG.jpg", + "type": "photo", + "url": "https://t.co/KLRfejrvOr", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 810, + "y": 238, + "h": 61, + "w": 61 + }, + { + "x": 483, + "y": 302, + "h": 53, + "w": 53 + }, + { + "x": 653, + "y": 242, + "h": 63, + "w": 63 + }, + { + "x": 332, + "y": 310, + "h": 61, + "w": 61 + }, + { + "x": 205, + "y": 512, + "h": 45, + "w": 45 + }, + { + "x": 832, + "y": 393, + "h": 62, + "w": 62 + }, + { + "x": 735, + "y": 468, + "h": 267, + "w": 267 + } + ] + }, + "medium": { + "faces": [ + { + "x": 759, + "y": 223, + "h": 57, + "w": 57 + }, + { + "x": 452, + "y": 283, + "h": 49, + "w": 49 + }, + { + "x": 612, + "y": 226, + "h": 59, + "w": 59 + }, + { + "x": 311, + "y": 290, + "h": 57, + "w": 57 + }, + { + "x": 192, + "y": 480, + "h": 42, + "w": 42 + }, + { + "x": 780, + "y": 368, + "h": 58, + "w": 58 + }, + { + "x": 689, + "y": 438, + "h": 250, + "w": 250 + } + ] + }, + "small": { + "faces": [ + { + "x": 430, + "y": 126, + "h": 32, + "w": 32 + }, + { + "x": 256, + "y": 160, + "h": 28, + "w": 28 + }, + { + "x": 346, + "y": 128, + "h": 33, + "w": 33 + }, + { + "x": 176, + "y": 164, + "h": 32, + "w": 32 + }, + { + "x": 108, + "y": 272, + "h": 23, + "w": 23 + }, + { + "x": 442, + "y": 208, + "h": 32, + "w": 32 + }, + { + "x": 390, + "y": 248, + "h": 141, + "w": 141 + } + ] + }, + "orig": { + "faces": [ + { + "x": 810, + "y": 238, + "h": 61, + "w": 61 + }, + { + "x": 483, + "y": 302, + "h": 53, + "w": 53 + }, + { + "x": 653, + "y": 242, + "h": 63, + "w": 63 + }, + { + "x": 332, + "y": 310, + "h": 61, + "w": 61 + }, + { + "x": 205, + "y": 512, + "h": 45, + "w": 45 + }, + { + "x": 832, + "y": 393, + "h": 62, + "w": 62 + }, + { + "x": 735, + "y": 468, + "h": 267, + "w": 267 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 310, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 362, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 523, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904640964779491328" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [ + { + "indices": [ + 329, + 344 + ], + "text": "AquíSeResuelve" + } + ], + "symbols": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/media/Gm6k3cIWMAAQEsB.jpg", + "https://pbs.twimg.com/media/Gm6k3cJWwAArQFG.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904543049268572301", + "url": "https://x.com/AdrianDeLaGarza/status/1904543049268572301", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1904543049268572301", + "text": "En relación a la situación de maltrato animal que sufrió un tlacuache, por parte de un trabajador de un carwash al poniente de la ciudad. Les comento que desde ayer se realizó una inspección al lugar de los hechos y ya se tiene identificado al responsable, el Municipio de Monterrey dará seguimiento al caso a través de Justicia Cívica, donde se determinarán las sanciones o medidas que apliquen en este caso conforme al reglamento de Protección y Bienestar Animal.\n\nAdemás, colaboraremos con autoridades estatales y federales de protección de fauna silvestre para que estas agresiones sean sancionadas y evitar su repetición.", + "fullText": "En relación a la situación de maltrato animal que sufrió un tlacuache, por parte de un trabajador de un carwash al poniente de la ciudad. Les comento que desde ayer se realizó una inspección al lugar de los hechos y ya se tiene identificado al responsable, el Municipio de https://t.co/3xUuYVbrOZ", + "source": "Twitter for iPhone", + "retweetCount": 104, + "replyCount": 117, + "likeCount": 553, + "quoteCount": 13, + "viewCount": 49667, + "createdAt": "Tue Mar 25 14:37:02 +0000 2025", + "lang": "es", + "bookmarkCount": 19, + "isReply": false, + "conversationId": "1904543049268572301", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/3xUuYVbrOZ", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904543049268572301/photo/1", + "id_str": "1904543044126318592", + "indices": [ + 273, + 296 + ], + "media_key": "3_1904543044126318592", + "media_url_https": "https://pbs.twimg.com/media/Gm5LztSXIAA3k-s.jpg", + "type": "photo", + "url": "https://t.co/3xUuYVbrOZ", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 15, + "y": 725, + "h": 78, + "w": 78 + } + ] + }, + "medium": { + "faces": [ + { + "x": 11, + "y": 543, + "h": 58, + "w": 58 + } + ] + }, + "small": { + "faces": [ + { + "x": 6, + "y": 308, + "h": 33, + "w": 33 + } + ] + }, + "orig": { + "faces": [ + { + "x": 15, + "y": 725, + "h": 78, + "w": 78 + } + ] + } + }, + "sizes": { + "large": { + "h": 1200, + "w": 1600, + "resize": "fit" + }, + "medium": { + "h": 900, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 510, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1200, + "width": 1600, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1600, + "h": 896 + }, + { + "x": 0, + "y": 0, + "w": 1200, + "h": 1200 + }, + { + "x": 74, + "y": 0, + "w": 1053, + "h": 1200 + }, + { + "x": 300, + "y": 0, + "w": 600, + "h": 1200 + }, + { + "x": 0, + "y": 0, + "w": 1600, + "h": 1200 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904543044126318592" + } + } + }, + { + "display_url": "pic.x.com/3xUuYVbrOZ", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904543049268572301/photo/1", + "id_str": "1904543044113780736", + "indices": [ + 273, + 296 + ], + "media_key": "3_1904543044113780736", + "media_url_https": "https://pbs.twimg.com/media/Gm5LztPX0AATgx5.jpg", + "type": "photo", + "url": "https://t.co/3xUuYVbrOZ", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [] + }, + "medium": { + "faces": [] + }, + "small": { + "faces": [] + }, + "orig": { + "faces": [] + } + }, + "sizes": { + "large": { + "h": 1200, + "w": 1600, + "resize": "fit" + }, + "medium": { + "h": 900, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 510, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1200, + "width": 1600, + "focus_rects": [ + { + "x": 0, + "y": 304, + "w": 1600, + "h": 896 + }, + { + "x": 400, + "y": 0, + "w": 1200, + "h": 1200 + }, + { + "x": 547, + "y": 0, + "w": 1053, + "h": 1200 + }, + { + "x": 780, + "y": 0, + "w": 600, + "h": 1200 + }, + { + "x": 0, + "y": 0, + "w": 1600, + "h": 1200 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904543044113780736" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [], + "symbols": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/media/Gm5LztSXIAA3k-s.jpg", + "https://pbs.twimg.com/media/Gm5LztPX0AATgx5.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904279979539431737", + "url": "https://x.com/AdrianDeLaGarza/status/1904279979539431737", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1904279979539431737", + "text": "¡Se va a armar la carnita asada! \n\nBienvenidos los @RedSox a tierras regias. Aquí tienen su casa y esperamos verlos de nuevo. Felicidades a todo el equipo de @SultanesOficial por la gran fiesta que han armado. \n\n¡Play Ball! ⚾️ https://t.co/6R4jhd2rf9", + "fullText": "¡Se va a armar la carnita asada! \n\nBienvenidos los @RedSox a tierras regias. Aquí tienen su casa y esperamos verlos de nuevo. Felicidades a todo el equipo de @SultanesOficial por la gran fiesta que han armado. \n\n¡Play Ball! ⚾️ https://t.co/6R4jhd2rf9", + "source": "Twitter for iPhone", + "retweetCount": 7, + "replyCount": 31, + "likeCount": 46, + "quoteCount": 0, + "viewCount": 1463, + "createdAt": "Mon Mar 24 21:11:41 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": false, + "conversationId": "1904279979539431737", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/6R4jhd2rf9", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904279979539431737/photo/1", + "id_str": "1904279976058253312", + "indices": [ + 227, + 250 + ], + "media_key": "3_1904279976058253312", + "media_url_https": "https://pbs.twimg.com/media/Gm1cjIXXcAAPCqO.jpg", + "type": "photo", + "url": "https://t.co/6R4jhd2rf9", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 747, + "y": 888, + "h": 83, + "w": 83 + } + ] + }, + "medium": { + "faces": [ + { + "x": 664, + "y": 789, + "h": 73, + "w": 73 + } + ] + }, + "small": { + "faces": [ + { + "x": 376, + "y": 447, + "h": 41, + "w": 41 + } + ] + }, + "orig": { + "faces": [ + { + "x": 747, + "y": 888, + "h": 83, + "w": 83 + } + ] + } + }, + "sizes": { + "large": { + "h": 1350, + "w": 1080, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 960, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 544, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1350, + "width": 1080, + "focus_rects": [ + { + "x": 0, + "y": 744, + "w": 1080, + "h": 605 + }, + { + "x": 0, + "y": 270, + "w": 1080, + "h": 1080 + }, + { + "x": 0, + "y": 119, + "w": 1080, + "h": 1231 + }, + { + "x": 405, + "y": 0, + "w": 675, + "h": 1350 + }, + { + "x": 0, + "y": 0, + "w": 1080, + "h": 1350 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904279976058253312" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [], + "media": [ + { + "display_url": "pic.x.com/6R4jhd2rf9", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904279979539431737/photo/1", + "id_str": "1904279976058253312", + "indices": [ + 227, + 250 + ], + "media_key": "3_1904279976058253312", + "media_url_https": "https://pbs.twimg.com/media/Gm1cjIXXcAAPCqO.jpg", + "type": "photo", + "url": "https://t.co/6R4jhd2rf9", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 747, + "y": 888, + "h": 83, + "w": 83 + } + ] + }, + "medium": { + "faces": [ + { + "x": 664, + "y": 789, + "h": 73, + "w": 73 + } + ] + }, + "small": { + "faces": [ + { + "x": 376, + "y": 447, + "h": 41, + "w": 41 + } + ] + }, + "orig": { + "faces": [ + { + "x": 747, + "y": 888, + "h": 83, + "w": 83 + } + ] + } + }, + "sizes": { + "large": { + "h": 1350, + "w": 1080, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 960, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 544, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1350, + "width": 1080, + "focus_rects": [ + { + "x": 0, + "y": 744, + "w": 1080, + "h": 605 + }, + { + "x": 0, + "y": 270, + "w": 1080, + "h": 1080 + }, + { + "x": 0, + "y": 119, + "w": 1080, + "h": 1231 + }, + { + "x": 405, + "y": 0, + "w": 675, + "h": 1350 + }, + { + "x": 0, + "y": 0, + "w": 1080, + "h": 1350 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904279976058253312" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [ + { + "id_str": "40918816", + "name": "Red Sox", + "screen_name": "RedSox", + "indices": [ + 51, + 58 + ] + }, + { + "id_str": "254287870", + "name": "Sultanes de Monterrey", + "screen_name": "SultanesOficial", + "indices": [ + 158, + 174 + ] + } + ] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/media/Gm1cjIXXcAAPCqO.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904237367273111711", + "url": "https://x.com/AdrianDeLaGarza/status/1904237367273111711", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1904237367273111711", + "text": "Bajo la estrategia ESCUDO nos comprometimos a una nueva proximidad de la Policía de Monterrey. \n\nCon esto, se ha fortalecido gradualmente la tranquilidad en las colonias. Por ejemplo, un oficial de Tránsito localizó a un menor de edad extraviado en las calles de la Col. Independencia. \n\nLuego de canalizarlo al área de la Unidad de Violencia Familiar y de Género, el adolescente fue entregado a su mamá. \n\nSeguimos resolviendo para que las familias regias tengan paz y tranquilidad.\n\n#AquíSeResuelve", + "fullText": "Bajo la estrategia ESCUDO nos comprometimos a una nueva proximidad de la Policía de Monterrey. \n\nCon esto, se ha fortalecido gradualmente la tranquilidad en las colonias. Por ejemplo, un oficial de Tránsito localizó a un menor de edad extraviado en las calles de la Col. https://t.co/jpXQgYpa6l", + "source": "Twitter for iPhone", + "retweetCount": 5, + "replyCount": 33, + "likeCount": 35, + "quoteCount": 0, + "viewCount": 1251, + "createdAt": "Mon Mar 24 18:22:22 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": false, + "conversationId": "1904237367273111711", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/jpXQgYpa6l", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904237367273111711/photo/1", + "id_str": "1904237361849851904", + "indices": [ + 271, + 294 + ], + "media_key": "3_1904237361849851904", + "media_url_https": "https://pbs.twimg.com/media/Gm01yqEWYAA4PTn.jpg", + "type": "photo", + "url": "https://t.co/jpXQgYpa6l", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 529, + "y": 86, + "h": 153, + "w": 153 + } + ] + }, + "medium": { + "faces": [ + { + "x": 444, + "y": 72, + "h": 128, + "w": 128 + } + ] + }, + "small": { + "faces": [ + { + "x": 251, + "y": 40, + "h": 72, + "w": 72 + } + ] + }, + "orig": { + "faces": [ + { + "x": 529, + "y": 86, + "h": 153, + "w": 153 + } + ] + } + }, + "sizes": { + "large": { + "h": 1071, + "w": 1428, + "resize": "fit" + }, + "medium": { + "h": 900, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 510, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1071, + "width": 1428, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1428, + "h": 800 + }, + { + "x": 357, + "y": 0, + "w": 1071, + "h": 1071 + }, + { + "x": 489, + "y": 0, + "w": 939, + "h": 1071 + }, + { + "x": 892, + "y": 0, + "w": 536, + "h": 1071 + }, + { + "x": 0, + "y": 0, + "w": 1428, + "h": 1071 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904237361849851904" + } + } + }, + { + "display_url": "pic.x.com/jpXQgYpa6l", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904237367273111711/photo/1", + "id_str": "1904237361997008896", + "indices": [ + 271, + 294 + ], + "media_key": "3_1904237361997008896", + "media_url_https": "https://pbs.twimg.com/media/Gm01yqnb0AANKMy.jpg", + "type": "photo", + "url": "https://t.co/jpXQgYpa6l", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 910, + "y": 701, + "h": 68, + "w": 68 + }, + { + "x": 603, + "y": 178, + "h": 125, + "w": 125 + }, + { + "x": 314, + "y": 176, + "h": 128, + "w": 128 + }, + { + "x": 1095, + "y": 70, + "h": 154, + "w": 154 + } + ] + }, + "medium": { + "faces": [ + { + "x": 682, + "y": 525, + "h": 51, + "w": 51 + }, + { + "x": 452, + "y": 133, + "h": 93, + "w": 93 + }, + { + "x": 235, + "y": 132, + "h": 96, + "w": 96 + }, + { + "x": 821, + "y": 52, + "h": 115, + "w": 115 + } + ] + }, + "small": { + "faces": [ + { + "x": 386, + "y": 297, + "h": 28, + "w": 28 + }, + { + "x": 256, + "y": 75, + "h": 53, + "w": 53 + }, + { + "x": 133, + "y": 74, + "h": 54, + "w": 54 + }, + { + "x": 465, + "y": 29, + "h": 65, + "w": 65 + } + ] + }, + "orig": { + "faces": [ + { + "x": 910, + "y": 701, + "h": 68, + "w": 68 + }, + { + "x": 603, + "y": 178, + "h": 125, + "w": 125 + }, + { + "x": 314, + "y": 176, + "h": 128, + "w": 128 + }, + { + "x": 1095, + "y": 70, + "h": 154, + "w": 154 + } + ] + } + }, + "sizes": { + "large": { + "h": 1200, + "w": 1600, + "resize": "fit" + }, + "medium": { + "h": 900, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 510, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1200, + "width": 1600, + "focus_rects": [ + { + "x": 0, + "y": 72, + "w": 1600, + "h": 896 + }, + { + "x": 0, + "y": 0, + "w": 1200, + "h": 1200 + }, + { + "x": 74, + "y": 0, + "w": 1053, + "h": 1200 + }, + { + "x": 300, + "y": 0, + "w": 600, + "h": 1200 + }, + { + "x": 0, + "y": 0, + "w": 1600, + "h": 1200 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904237361997008896" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [ + { + "indices": [ + 485, + 500 + ], + "text": "AquíSeResuelve" + } + ], + "symbols": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/media/Gm01yqEWYAA4PTn.jpg", + "https://pbs.twimg.com/media/Gm01yqnb0AANKMy.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1903948214320971904", + "url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1903948214320971904", + "text": "La espera terminó, el día de hoy inauguramos la tan esperada Temporada Acuática 2025 💦\n\n#AquíSeResuelve https://t.co/Ow0RB6Yurn", + "fullText": "La espera terminó, el día de hoy inauguramos la tan esperada Temporada Acuática 2025 💦\n\n#AquíSeResuelve https://t.co/Ow0RB6Yurn", + "source": "Twitter for iPhone", + "retweetCount": 14, + "replyCount": 46, + "likeCount": 89, + "quoteCount": 0, + "viewCount": 3108, + "createdAt": "Sun Mar 23 23:13:22 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": false, + "conversationId": "1903948214320971904", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948208029487104", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948208029487104", + "media_url_https": "https://pbs.twimg.com/media/GmwuzsJW0AArEK6.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 562, + "y": 225, + "h": 85, + "w": 85 + }, + { + "x": 733, + "y": 165, + "h": 102, + "w": 102 + } + ] + }, + "medium": { + "faces": [ + { + "x": 526, + "y": 210, + "h": 79, + "w": 79 + }, + { + "x": 687, + "y": 154, + "h": 95, + "w": 95 + } + ] + }, + "small": { + "faces": [ + { + "x": 298, + "y": 119, + "h": 45, + "w": 45 + }, + { + "x": 389, + "y": 87, + "h": 54, + "w": 54 + } + ] + }, + "orig": { + "faces": [ + { + "x": 562, + "y": 225, + "h": 85, + "w": 85 + }, + { + "x": 733, + "y": 165, + "h": 102, + "w": 102 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 310, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 362, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 523, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948208029487104" + } + } + }, + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948208033710080", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948208033710080", + "media_url_https": "https://pbs.twimg.com/media/GmwuzsKXQAAPID7.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 515, + "y": 645, + "h": 62, + "w": 62 + } + ] + }, + "medium": { + "faces": [ + { + "x": 482, + "y": 604, + "h": 58, + "w": 58 + } + ] + }, + "small": { + "faces": [ + { + "x": 273, + "y": 342, + "h": 32, + "w": 32 + } + ] + }, + "orig": { + "faces": [ + { + "x": 515, + "y": 645, + "h": 62, + "w": 62 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 122, + "w": 1280, + "h": 717 + }, + { + "x": 374, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 426, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 587, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948208033710080" + } + } + }, + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948207861735424", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948207861735424", + "media_url_https": "https://pbs.twimg.com/media/GmwuzrhXIAAFa_l.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 326, + "y": 251, + "h": 147, + "w": 147 + }, + { + "x": 1072, + "y": 261, + "h": 150, + "w": 150 + } + ] + }, + "medium": { + "faces": [ + { + "x": 305, + "y": 235, + "h": 137, + "w": 137 + }, + { + "x": 1005, + "y": 244, + "h": 140, + "w": 140 + } + ] + }, + "small": { + "faces": [ + { + "x": 173, + "y": 133, + "h": 78, + "w": 78 + }, + { + "x": 569, + "y": 138, + "h": 79, + "w": 79 + } + ] + }, + "orig": { + "faces": [ + { + "x": 326, + "y": 251, + "h": 147, + "w": 147 + }, + { + "x": 1072, + "y": 261, + "h": 150, + "w": 150 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 0, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 42, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 203, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948207861735424" + } + } + }, + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948207870160896", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948207870160896", + "media_url_https": "https://pbs.twimg.com/media/GmwuzrjXsAAGKd1.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 577, + "y": 205, + "h": 135, + "w": 135 + } + ] + }, + "medium": { + "faces": [ + { + "x": 540, + "y": 192, + "h": 126, + "w": 126 + } + ] + }, + "small": { + "faces": [ + { + "x": 306, + "y": 108, + "h": 71, + "w": 71 + } + ] + }, + "orig": { + "faces": [ + { + "x": 577, + "y": 205, + "h": 135, + "w": 135 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 182, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 234, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 395, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948207870160896" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [ + { + "indices": [ + 88, + 103 + ], + "text": "AquíSeResuelve" + } + ], + "media": [ + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948208029487104", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948208029487104", + "media_url_https": "https://pbs.twimg.com/media/GmwuzsJW0AArEK6.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 562, + "y": 225, + "h": 85, + "w": 85 + }, + { + "x": 733, + "y": 165, + "h": 102, + "w": 102 + } + ] + }, + "medium": { + "faces": [ + { + "x": 526, + "y": 210, + "h": 79, + "w": 79 + }, + { + "x": 687, + "y": 154, + "h": 95, + "w": 95 + } + ] + }, + "small": { + "faces": [ + { + "x": 298, + "y": 119, + "h": 45, + "w": 45 + }, + { + "x": 389, + "y": 87, + "h": 54, + "w": 54 + } + ] + }, + "orig": { + "faces": [ + { + "x": 562, + "y": 225, + "h": 85, + "w": 85 + }, + { + "x": 733, + "y": 165, + "h": 102, + "w": 102 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 310, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 362, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 523, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948208029487104" + } + } + }, + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948208033710080", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948208033710080", + "media_url_https": "https://pbs.twimg.com/media/GmwuzsKXQAAPID7.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 515, + "y": 645, + "h": 62, + "w": 62 + } + ] + }, + "medium": { + "faces": [ + { + "x": 482, + "y": 604, + "h": 58, + "w": 58 + } + ] + }, + "small": { + "faces": [ + { + "x": 273, + "y": 342, + "h": 32, + "w": 32 + } + ] + }, + "orig": { + "faces": [ + { + "x": 515, + "y": 645, + "h": 62, + "w": 62 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 122, + "w": 1280, + "h": 717 + }, + { + "x": 374, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 426, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 587, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948208033710080" + } + } + }, + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948207861735424", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948207861735424", + "media_url_https": "https://pbs.twimg.com/media/GmwuzrhXIAAFa_l.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 326, + "y": 251, + "h": 147, + "w": 147 + }, + { + "x": 1072, + "y": 261, + "h": 150, + "w": 150 + } + ] + }, + "medium": { + "faces": [ + { + "x": 305, + "y": 235, + "h": 137, + "w": 137 + }, + { + "x": 1005, + "y": 244, + "h": 140, + "w": 140 + } + ] + }, + "small": { + "faces": [ + { + "x": 173, + "y": 133, + "h": 78, + "w": 78 + }, + { + "x": 569, + "y": 138, + "h": 79, + "w": 79 + } + ] + }, + "orig": { + "faces": [ + { + "x": 326, + "y": 251, + "h": 147, + "w": 147 + }, + { + "x": 1072, + "y": 261, + "h": 150, + "w": 150 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 0, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 42, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 203, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948207861735424" + } + } + }, + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948207870160896", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948207870160896", + "media_url_https": "https://pbs.twimg.com/media/GmwuzrjXsAAGKd1.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 577, + "y": 205, + "h": 135, + "w": 135 + } + ] + }, + "medium": { + "faces": [ + { + "x": 540, + "y": 192, + "h": 126, + "w": 126 + } + ] + }, + "small": { + "faces": [ + { + "x": 306, + "y": 108, + "h": 71, + "w": 71 + } + ] + }, + "orig": { + "faces": [ + { + "x": 577, + "y": 205, + "h": 135, + "w": 135 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 182, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 234, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 395, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948207870160896" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/media/GmwuzsJW0AArEK6.jpg", + "https://pbs.twimg.com/media/GmwuzsKXQAAPID7.jpg", + "https://pbs.twimg.com/media/GmwuzrhXIAAFa_l.jpg", + "https://pbs.twimg.com/media/GmwuzrjXsAAGKd1.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1903617133097197760", + "url": "https://x.com/AdrianDeLaGarza/status/1903617133097197760", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1903617133097197760", + "text": "Es momento de sacar el short y las chanclas para disfrutar de la Temporada Acuática en Monterrey. 💦🛟\n\nVen con tu familia y amigos a los parques Aztlán, España, Tucán y Monterrey 400. \n\n¡Te esperamos!\n\n🎥: Video Gob MTY\n\n#AquíSeResuelve https://t.co/Ezv7eE6dnS", + "fullText": "Es momento de sacar el short y las chanclas para disfrutar de la Temporada Acuática en Monterrey. 💦🛟\n\nVen con tu familia y amigos a los parques Aztlán, España, Tucán y Monterrey 400. \n\n¡Te esperamos!\n\n🎥: Video Gob MTY\n\n#AquíSeResuelve https://t.co/Ezv7eE6dnS", + "source": "Twitter for iPhone", + "retweetCount": 10, + "replyCount": 52, + "likeCount": 50, + "quoteCount": 1, + "viewCount": 2206, + "createdAt": "Sun Mar 23 01:17:46 +0000 2025", + "lang": "es", + "bookmarkCount": 1, + "isReply": false, + "conversationId": "1903617133097197760", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/Ezv7eE6dnS", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903617133097197760/video/1", + "id_str": "1903617018823385088", + "indices": [ + 235, + 258 + ], + "media_key": "13_1903617018823385088", + "media_url_https": "https://pbs.twimg.com/amplify_video_thumb/1903617018823385088/img/8FaJCJkzNRIZa1Zg.jpg", + "type": "video", + "url": "https://t.co/Ezv7eE6dnS", + "additional_media_info": { + "monetizable": false + }, + "ext_media_availability": { + "status": "Available" + }, + "sizes": { + "large": { + "h": 1080, + "w": 1920, + "resize": "fit" + }, + "medium": { + "h": 675, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 383, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1080, + "width": 1920, + "focus_rects": [] + }, + "allow_download_status": { + "allow_download": true + }, + "video_info": { + "aspect_ratio": [ + 16, + 9 + ], + "duration_millis": 20020, + "variants": [ + { + "content_type": "application/x-mpegURL", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/pl/elWX8QgI3_z2VL_J.m3u8?tag=16&v=bf3" + }, + { + "bitrate": 288000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/480x270/nhuPuboxMDSyNXVF.mp4?tag=16" + }, + { + "bitrate": 832000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/640x360/EF0BzPe7UH38mjpE.mp4?tag=16" + }, + { + "bitrate": 2176000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/1280x720/XY38_gXA5afNrPLv.mp4?tag=16" + }, + { + "bitrate": 10368000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/1920x1080/zg-IbwGbn9Fitbln.mp4?tag=16" + } + ] + }, + "media_results": { + "result": { + "media_key": "13_1903617018823385088" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [ + { + "indices": [ + 219, + 234 + ], + "text": "AquíSeResuelve" + } + ], + "media": [ + { + "display_url": "pic.x.com/Ezv7eE6dnS", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903617133097197760/video/1", + "id_str": "1903617018823385088", + "indices": [ + 235, + 258 + ], + "media_key": "13_1903617018823385088", + "media_url_https": "https://pbs.twimg.com/amplify_video_thumb/1903617018823385088/img/8FaJCJkzNRIZa1Zg.jpg", + "type": "video", + "url": "https://t.co/Ezv7eE6dnS", + "additional_media_info": { + "monetizable": false + }, + "ext_media_availability": { + "status": "Available" + }, + "sizes": { + "large": { + "h": 1080, + "w": 1920, + "resize": "fit" + }, + "medium": { + "h": 675, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 383, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1080, + "width": 1920, + "focus_rects": [] + }, + "allow_download_status": { + "allow_download": true + }, + "video_info": { + "aspect_ratio": [ + 16, + 9 + ], + "duration_millis": 20020, + "variants": [ + { + "content_type": "application/x-mpegURL", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/pl/elWX8QgI3_z2VL_J.m3u8?tag=16&v=bf3" + }, + { + "bitrate": 288000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/480x270/nhuPuboxMDSyNXVF.mp4?tag=16" + }, + { + "bitrate": 832000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/640x360/EF0BzPe7UH38mjpE.mp4?tag=16" + }, + { + "bitrate": 2176000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/1280x720/XY38_gXA5afNrPLv.mp4?tag=16" + }, + { + "bitrate": 10368000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/1920x1080/zg-IbwGbn9Fitbln.mp4?tag=16" + } + ] + }, + "media_results": { + "result": { + "media_key": "13_1903617018823385088" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/amplify_video_thumb/1903617018823385088/img/8FaJCJkzNRIZa1Zg.jpg" + ], + "isConversationControlled": false + } +] \ No newline at end of file diff --git a/backend/app/testing/data/x/profile_samples.json b/backend/app/testing/data/x/profile_samples.json new file mode 100644 index 0000000000..b32f2efa2f --- /dev/null +++ b/backend/app/testing/data/x/profile_samples.json @@ -0,0 +1,4557 @@ +[ + { + "type": "tweet", + "id": "1905018863219364194", + "url": "https://x.com/AdrianDeLaGarza/status/1905018863219364194", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1905018863219364194", + "text": "Mejorar la seguridad es una de nuestras prioridades; hoy damos un paso más para tener una ciudad con orden y paz. \n\nPresenté la nueva imagen de nuestra Policía y relanzamos la campaña de reclutamiento para seguir fortaleciendo a nuestra corporación. \n\nEn Monterrey, reconocemos el esfuerzo de quienes nos protegen, por eso mejoramos sus condiciones laborales, con un salario competitivo, prestaciones de calidad y un plan de vida y carrera que garantiza su bienestar y el de sus familias.\n\n¡Únete a la Policía de Monterrey! Más que un trabajo, un proyecto de vida. \n\n📞 Teléfono: 81 5102 6750\n📲 WhatsApp: 81 1533 1942\n\n#AquíSeResuelve", + "fullText": "Mejorar la seguridad es una de nuestras prioridades; hoy damos un paso más para tener una ciudad con orden y paz. \n\nPresenté la nueva imagen de nuestra Policía y relanzamos la campaña de reclutamiento para seguir fortaleciendo a nuestra corporación. \n\nEn Monterrey, reconocemos el https://t.co/QftMT1FQ0e", + "source": "Twitter for iPhone", + "retweetCount": 4, + "replyCount": 4, + "likeCount": 12, + "quoteCount": 0, + "viewCount": 627, + "createdAt": "Wed Mar 26 22:07:45 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": false, + "conversationId": "1905018863219364194", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/QftMT1FQ0e", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1905018863219364194/photo/1", + "id_str": "1905018858219487232", + "indices": [ + 281, + 304 + ], + "media_key": "3_1905018858219487232", + "media_url_https": "https://pbs.twimg.com/media/Gm_8ju6XwAAstFg.jpg", + "type": "photo", + "url": "https://t.co/QftMT1FQ0e", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 656, + "y": 495, + "h": 48, + "w": 48 + } + ] + }, + "medium": { + "faces": [ + { + "x": 583, + "y": 440, + "h": 42, + "w": 42 + } + ] + }, + "small": { + "faces": [ + { + "x": 330, + "y": 249, + "h": 24, + "w": 24 + } + ] + }, + "orig": { + "faces": [ + { + "x": 656, + "y": 495, + "h": 48, + "w": 48 + } + ] + } + }, + "sizes": { + "large": { + "h": 1350, + "w": 1080, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 960, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 544, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1350, + "width": 1080, + "focus_rects": [ + { + "x": 0, + "y": 744, + "w": 1080, + "h": 605 + }, + { + "x": 0, + "y": 270, + "w": 1080, + "h": 1080 + }, + { + "x": 0, + "y": 119, + "w": 1080, + "h": 1231 + }, + { + "x": 405, + "y": 0, + "w": 675, + "h": 1350 + }, + { + "x": 0, + "y": 0, + "w": 1080, + "h": 1350 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1905018858219487232" + } + } + }, + { + "display_url": "pic.x.com/QftMT1FQ0e", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1905018863219364194/photo/1", + "id_str": "1905018858223665152", + "indices": [ + 281, + 304 + ], + "media_key": "3_1905018858223665152", + "media_url_https": "https://pbs.twimg.com/media/Gm_8ju7XgAAVS6I.jpg", + "type": "photo", + "url": "https://t.co/QftMT1FQ0e", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 842, + "y": 191, + "h": 48, + "w": 48 + }, + { + "x": 721, + "y": 930, + "h": 60, + "w": 60 + } + ] + }, + "medium": { + "faces": [ + { + "x": 748, + "y": 169, + "h": 42, + "w": 42 + }, + { + "x": 640, + "y": 826, + "h": 53, + "w": 53 + } + ] + }, + "small": { + "faces": [ + { + "x": 424, + "y": 96, + "h": 24, + "w": 24 + }, + { + "x": 363, + "y": 468, + "h": 30, + "w": 30 + } + ] + }, + "orig": { + "faces": [ + { + "x": 842, + "y": 191, + "h": 48, + "w": 48 + }, + { + "x": 721, + "y": 930, + "h": 60, + "w": 60 + } + ] + } + }, + "sizes": { + "large": { + "h": 1350, + "w": 1080, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 960, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 544, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1350, + "width": 1080, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1080, + "h": 605 + }, + { + "x": 0, + "y": 0, + "w": 1080, + "h": 1080 + }, + { + "x": 0, + "y": 0, + "w": 1080, + "h": 1231 + }, + { + "x": 304, + "y": 0, + "w": 675, + "h": 1350 + }, + { + "x": 0, + "y": 0, + "w": 1080, + "h": 1350 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1905018858223665152" + } + } + }, + { + "display_url": "pic.x.com/QftMT1FQ0e", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1905018863219364194/photo/1", + "id_str": "1905018858219384832", + "indices": [ + 281, + 304 + ], + "media_key": "3_1905018858219384832", + "media_url_https": "https://pbs.twimg.com/media/Gm_8ju6WMAAX98A.jpg", + "type": "photo", + "url": "https://t.co/QftMT1FQ0e", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 458, + "y": 444, + "h": 77, + "w": 77 + }, + { + "x": 742, + "y": 341, + "h": 105, + "w": 105 + }, + { + "x": 411, + "y": 185, + "h": 100, + "w": 100 + } + ] + }, + "medium": { + "faces": [ + { + "x": 407, + "y": 394, + "h": 68, + "w": 68 + }, + { + "x": 659, + "y": 303, + "h": 93, + "w": 93 + }, + { + "x": 365, + "y": 164, + "h": 88, + "w": 88 + } + ] + }, + "small": { + "faces": [ + { + "x": 230, + "y": 223, + "h": 38, + "w": 38 + }, + { + "x": 373, + "y": 171, + "h": 52, + "w": 52 + }, + { + "x": 207, + "y": 93, + "h": 50, + "w": 50 + } + ] + }, + "orig": { + "faces": [ + { + "x": 458, + "y": 444, + "h": 77, + "w": 77 + }, + { + "x": 742, + "y": 341, + "h": 105, + "w": 105 + }, + { + "x": 411, + "y": 185, + "h": 100, + "w": 100 + } + ] + } + }, + "sizes": { + "large": { + "h": 1350, + "w": 1080, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 960, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 544, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1350, + "width": 1080, + "focus_rects": [ + { + "x": 0, + "y": 541, + "w": 1080, + "h": 605 + }, + { + "x": 0, + "y": 270, + "w": 1080, + "h": 1080 + }, + { + "x": 0, + "y": 119, + "w": 1080, + "h": 1231 + }, + { + "x": 0, + "y": 0, + "w": 675, + "h": 1350 + }, + { + "x": 0, + "y": 0, + "w": 1080, + "h": 1350 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1905018858219384832" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [ + { + "indices": [ + 618, + 633 + ], + "text": "AquíSeResuelve" + } + ], + "symbols": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/media/Gm_8ju6XwAAstFg.jpg", + "https://pbs.twimg.com/media/Gm_8ju7XgAAVS6I.jpg", + "https://pbs.twimg.com/media/Gm_8ju6WMAAX98A.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904944171422474407", + "url": "https://x.com/AdrianDeLaGarza/status/1904944171422474407", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1904944171422474407", + "text": "Hoy más que nunca, buscamos personas valientes, con vocación de servicio y el deseo de sumar por la seguridad en Monterrey. \n\nSi tienes el compromiso, la disciplina y el orgullo de proteger a tu comunidad, este es tu momento para unirte a la Policía de Monterrey. No es sólo un trabajo, es un proyecto de vida. \n\nÚnete a la mejor policía de México y sé parte del equipo que mantiene segura nuestra ciudad.\n\n¡Inscríbete hoy y transforma tu futuro! 🚓\n\n📞 Teléfono: 81 5102 6750\n📲 WhatsApp: 81 1533 1942\n\n#AquíSeResuelve", + "fullText": "Hoy más que nunca, buscamos personas valientes, con vocación de servicio y el deseo de sumar por la seguridad en Monterrey. \n\nSi tienes el compromiso, la disciplina y el orgullo de proteger a tu comunidad, este es tu momento para unirte a la Policía de Monterrey. No es sólo un https://t.co/aFbCzB8qQa", + "source": "Twitter for iPhone", + "retweetCount": 50, + "replyCount": 25, + "likeCount": 91, + "quoteCount": 13, + "viewCount": 9294, + "createdAt": "Wed Mar 26 17:10:57 +0000 2025", + "lang": "es", + "bookmarkCount": 2, + "isReply": false, + "conversationId": "1904944171422474407", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/aFbCzB8qQa", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904944171422474407/video/1", + "id_str": "1904944027696320512", + "indices": [ + 278, + 301 + ], + "media_key": "13_1904944027696320512", + "media_url_https": "https://pbs.twimg.com/amplify_video_thumb/1904944027696320512/img/XcoguvI4BV2Sap1r.jpg", + "type": "video", + "url": "https://t.co/aFbCzB8qQa", + "additional_media_info": { + "monetizable": false + }, + "ext_media_availability": { + "status": "Available" + }, + "sizes": { + "large": { + "h": 720, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 675, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 383, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 720, + "width": 1280, + "focus_rects": [] + }, + "allow_download_status": { + "allow_download": true + }, + "video_info": { + "aspect_ratio": [ + 16, + 9 + ], + "duration_millis": 60000, + "variants": [ + { + "content_type": "application/x-mpegURL", + "url": "https://video.twimg.com/amplify_video/1904944027696320512/pl/iRvK1LAeMXJgyAc7.m3u8?tag=16" + }, + { + "bitrate": 288000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1904944027696320512/vid/avc1/480x270/e6FRIqoCgq30SuEl.mp4?tag=16" + }, + { + "bitrate": 832000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1904944027696320512/vid/avc1/640x360/ll5fw51aIg7sVA-l.mp4?tag=16" + }, + { + "bitrate": 2176000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1904944027696320512/vid/avc1/1280x720/vPu5x7snMsEd6TTO.mp4?tag=16" + } + ] + }, + "media_results": { + "result": { + "media_key": "13_1904944027696320512" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [ + { + "indices": [ + 501, + 516 + ], + "text": "AquíSeResuelve" + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/amplify_video_thumb/1904944027696320512/img/XcoguvI4BV2Sap1r.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904885698240389472", + "url": "https://x.com/AdrianDeLaGarza/status/1904885698240389472", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1904885698240389472", + "text": "¡Mucha precaución al salir de casa! ⚠️\n\nLa lluvia estará constante en nuestra ciudad durante el día, es importante tomar medidas de precauciones: \n🚗 Maneja despacio. \n⏰ Toma tu tiempo. \n🚧 Verifica rutas de traslado.\n⛅️ Revisa constantemente el pronóstico del tiempo.\n📞 Ante una emergencia marca al 911.", + "fullText": "¡Mucha precaución al salir de casa! ⚠️\n\nLa lluvia estará constante en nuestra ciudad durante el día, es importante tomar medidas de precauciones: \n🚗 Maneja despacio. \n⏰ Toma tu tiempo. \n🚧 Verifica rutas de traslado.\n⛅️ Revisa constantemente el pronóstico del tiempo.\n📞 Ante", + "source": "Twitter for iPhone", + "retweetCount": 5, + "replyCount": 13, + "likeCount": 41, + "quoteCount": 0, + "viewCount": 1783, + "createdAt": "Wed Mar 26 13:18:36 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": false, + "conversationId": "1904885698240389472", + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": {}, + "card": {}, + "place": {}, + "entities": { + "hashtags": [], + "symbols": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904675146415112204", + "url": "https://x.com/AdrianDeLaGarza/status/1904675146415112204", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1904675146415112204", + "text": "Hoy me reuní con el Embajador de Taiwán, Iván Yueh-Jung Lee, para fortalecer lazos y explorar oportunidades de inversión e innovación para Monterrey. Compartimos la visión de trabajar y resolver para seguir impulsando el desarrollo de nuestra ciudad. \n\n#AquíSeResuelve https://t.co/ve0DjXICyw", + "fullText": "Hoy me reuní con el Embajador de Taiwán, Iván Yueh-Jung Lee, para fortalecer lazos y explorar oportunidades de inversión e innovación para Monterrey. Compartimos la visión de trabajar y resolver para seguir impulsando el desarrollo de nuestra ciudad. \n\n#AquíSeResuelve https://t.co/ve0DjXICyw", + "source": "Twitter for iPhone", + "retweetCount": 6, + "replyCount": 20, + "likeCount": 42, + "quoteCount": 0, + "viewCount": 1320, + "createdAt": "Tue Mar 25 23:21:56 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": false, + "conversationId": "1904675146415112204", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/ve0DjXICyw", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904675146415112204/photo/1", + "id_str": "1904675139788173312", + "indices": [ + 269, + 292 + ], + "media_key": "3_1904675139788173312", + "media_url_https": "https://pbs.twimg.com/media/Gm7D8r-XcAALziq.jpg", + "type": "photo", + "url": "https://t.co/ve0DjXICyw", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 741, + "y": 173, + "h": 113, + "w": 113 + }, + { + "x": 350, + "y": 123, + "h": 120, + "w": 120 + }, + { + "x": 947, + "y": 502, + "h": 145, + "w": 145 + }, + { + "x": 113, + "y": 515, + "h": 137, + "w": 137 + } + ] + }, + "medium": { + "faces": [ + { + "x": 694, + "y": 162, + "h": 105, + "w": 105 + }, + { + "x": 328, + "y": 115, + "h": 112, + "w": 112 + }, + { + "x": 887, + "y": 470, + "h": 135, + "w": 135 + }, + { + "x": 105, + "y": 482, + "h": 128, + "w": 128 + } + ] + }, + "small": { + "faces": [ + { + "x": 393, + "y": 91, + "h": 60, + "w": 60 + }, + { + "x": 185, + "y": 65, + "h": 63, + "w": 63 + }, + { + "x": 503, + "y": 266, + "h": 77, + "w": 77 + }, + { + "x": 60, + "y": 273, + "h": 72, + "w": 72 + } + ] + }, + "orig": { + "faces": [ + { + "x": 741, + "y": 173, + "h": 113, + "w": 113 + }, + { + "x": 350, + "y": 123, + "h": 120, + "w": 120 + }, + { + "x": 947, + "y": 502, + "h": 145, + "w": 145 + }, + { + "x": 113, + "y": 515, + "h": 137, + "w": 137 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 0, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 42, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 203, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904675139788173312" + } + } + }, + { + "display_url": "pic.x.com/ve0DjXICyw", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904675146415112204/photo/1", + "id_str": "1904675139788173315", + "indices": [ + 269, + 292 + ], + "media_key": "3_1904675139788173315", + "media_url_https": "https://pbs.twimg.com/media/Gm7D8r-XcAMuXiQ.jpg", + "type": "photo", + "url": "https://t.co/ve0DjXICyw", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 135, + "y": 438, + "h": 58, + "w": 58 + }, + { + "x": 247, + "y": 130, + "h": 153, + "w": 153 + }, + { + "x": 1121, + "y": 546, + "h": 138, + "w": 138 + }, + { + "x": 711, + "y": 196, + "h": 191, + "w": 191 + } + ] + }, + "medium": { + "faces": [ + { + "x": 126, + "y": 410, + "h": 54, + "w": 54 + }, + { + "x": 231, + "y": 121, + "h": 143, + "w": 143 + }, + { + "x": 1050, + "y": 511, + "h": 129, + "w": 129 + }, + { + "x": 666, + "y": 183, + "h": 179, + "w": 179 + } + ] + }, + "small": { + "faces": [ + { + "x": 71, + "y": 232, + "h": 30, + "w": 30 + }, + { + "x": 131, + "y": 69, + "h": 81, + "w": 81 + }, + { + "x": 595, + "y": 290, + "h": 73, + "w": 73 + }, + { + "x": 377, + "y": 104, + "h": 101, + "w": 101 + } + ] + }, + "orig": { + "faces": [ + { + "x": 135, + "y": 438, + "h": 58, + "w": 58 + }, + { + "x": 247, + "y": 130, + "h": 153, + "w": 153 + }, + { + "x": 1121, + "y": 546, + "h": 138, + "w": 138 + }, + { + "x": 711, + "y": 196, + "h": 191, + "w": 191 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 0, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 75, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904675139788173315" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [ + { + "indices": [ + 253, + 268 + ], + "text": "AquíSeResuelve" + } + ], + "media": [ + { + "display_url": "pic.x.com/ve0DjXICyw", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904675146415112204/photo/1", + "id_str": "1904675139788173312", + "indices": [ + 269, + 292 + ], + "media_key": "3_1904675139788173312", + "media_url_https": "https://pbs.twimg.com/media/Gm7D8r-XcAALziq.jpg", + "type": "photo", + "url": "https://t.co/ve0DjXICyw", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 741, + "y": 173, + "h": 113, + "w": 113 + }, + { + "x": 350, + "y": 123, + "h": 120, + "w": 120 + }, + { + "x": 947, + "y": 502, + "h": 145, + "w": 145 + }, + { + "x": 113, + "y": 515, + "h": 137, + "w": 137 + } + ] + }, + "medium": { + "faces": [ + { + "x": 694, + "y": 162, + "h": 105, + "w": 105 + }, + { + "x": 328, + "y": 115, + "h": 112, + "w": 112 + }, + { + "x": 887, + "y": 470, + "h": 135, + "w": 135 + }, + { + "x": 105, + "y": 482, + "h": 128, + "w": 128 + } + ] + }, + "small": { + "faces": [ + { + "x": 393, + "y": 91, + "h": 60, + "w": 60 + }, + { + "x": 185, + "y": 65, + "h": 63, + "w": 63 + }, + { + "x": 503, + "y": 266, + "h": 77, + "w": 77 + }, + { + "x": 60, + "y": 273, + "h": 72, + "w": 72 + } + ] + }, + "orig": { + "faces": [ + { + "x": 741, + "y": 173, + "h": 113, + "w": 113 + }, + { + "x": 350, + "y": 123, + "h": 120, + "w": 120 + }, + { + "x": 947, + "y": 502, + "h": 145, + "w": 145 + }, + { + "x": 113, + "y": 515, + "h": 137, + "w": 137 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 0, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 42, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 203, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904675139788173312" + } + } + }, + { + "display_url": "pic.x.com/ve0DjXICyw", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904675146415112204/photo/1", + "id_str": "1904675139788173315", + "indices": [ + 269, + 292 + ], + "media_key": "3_1904675139788173315", + "media_url_https": "https://pbs.twimg.com/media/Gm7D8r-XcAMuXiQ.jpg", + "type": "photo", + "url": "https://t.co/ve0DjXICyw", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 135, + "y": 438, + "h": 58, + "w": 58 + }, + { + "x": 247, + "y": 130, + "h": 153, + "w": 153 + }, + { + "x": 1121, + "y": 546, + "h": 138, + "w": 138 + }, + { + "x": 711, + "y": 196, + "h": 191, + "w": 191 + } + ] + }, + "medium": { + "faces": [ + { + "x": 126, + "y": 410, + "h": 54, + "w": 54 + }, + { + "x": 231, + "y": 121, + "h": 143, + "w": 143 + }, + { + "x": 1050, + "y": 511, + "h": 129, + "w": 129 + }, + { + "x": 666, + "y": 183, + "h": 179, + "w": 179 + } + ] + }, + "small": { + "faces": [ + { + "x": 71, + "y": 232, + "h": 30, + "w": 30 + }, + { + "x": 131, + "y": 69, + "h": 81, + "w": 81 + }, + { + "x": 595, + "y": 290, + "h": 73, + "w": 73 + }, + { + "x": 377, + "y": 104, + "h": 101, + "w": 101 + } + ] + }, + "orig": { + "faces": [ + { + "x": 135, + "y": 438, + "h": 58, + "w": 58 + }, + { + "x": 247, + "y": 130, + "h": 153, + "w": 153 + }, + { + "x": 1121, + "y": 546, + "h": 138, + "w": 138 + }, + { + "x": 711, + "y": 196, + "h": 191, + "w": 191 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 0, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 75, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904675139788173315" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/media/Gm7D8r-XcAALziq.jpg", + "https://pbs.twimg.com/media/Gm7D8r-XcAMuXiQ.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904640970614071526", + "url": "https://x.com/AdrianDeLaGarza/status/1904640970614071526", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1904640970614071526", + "text": "¡Corramos por una causa! \nEl 21K Monterrey y la carrera 5K están de regreso este 6 de abril, en una ruta ya conocida como es la Vía Deportiva y en el circuito interior del Parque España.\n\nTodo lo recaudado será para apoyar a atletas con discapacidad de nuestra ciudad.\n\nInscríbete antes del 3 de abril en Innovasport o Trotime.\n\n#AquíSeResuelve", + "fullText": "¡Corramos por una causa! \nEl 21K Monterrey y la carrera 5K están de regreso este 6 de abril, en una ruta ya conocida como es la Vía Deportiva y en el circuito interior del Parque España.\n\nTodo lo recaudado será para apoyar a atletas con discapacidad de nuestra ciudad.\n\nInscríbete https://t.co/KLRfejrvOr", + "source": "Twitter for iPhone", + "retweetCount": 5, + "replyCount": 28, + "likeCount": 25, + "quoteCount": 0, + "viewCount": 1328, + "createdAt": "Tue Mar 25 21:06:08 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": false, + "conversationId": "1904640970614071526", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/KLRfejrvOr", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904640970614071526/photo/1", + "id_str": "1904640964775260160", + "indices": [ + 281, + 304 + ], + "media_key": "3_1904640964775260160", + "media_url_https": "https://pbs.twimg.com/media/Gm6k3cIWMAAQEsB.jpg", + "type": "photo", + "url": "https://t.co/KLRfejrvOr", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [] + }, + "medium": { + "faces": [] + }, + "small": { + "faces": [] + }, + "orig": { + "faces": [] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 54, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 106, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 267, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904640964775260160" + } + } + }, + { + "display_url": "pic.x.com/KLRfejrvOr", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904640970614071526/photo/1", + "id_str": "1904640964779491328", + "indices": [ + 281, + 304 + ], + "media_key": "3_1904640964779491328", + "media_url_https": "https://pbs.twimg.com/media/Gm6k3cJWwAArQFG.jpg", + "type": "photo", + "url": "https://t.co/KLRfejrvOr", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 810, + "y": 238, + "h": 61, + "w": 61 + }, + { + "x": 483, + "y": 302, + "h": 53, + "w": 53 + }, + { + "x": 653, + "y": 242, + "h": 63, + "w": 63 + }, + { + "x": 332, + "y": 310, + "h": 61, + "w": 61 + }, + { + "x": 205, + "y": 512, + "h": 45, + "w": 45 + }, + { + "x": 832, + "y": 393, + "h": 62, + "w": 62 + }, + { + "x": 735, + "y": 468, + "h": 267, + "w": 267 + } + ] + }, + "medium": { + "faces": [ + { + "x": 759, + "y": 223, + "h": 57, + "w": 57 + }, + { + "x": 452, + "y": 283, + "h": 49, + "w": 49 + }, + { + "x": 612, + "y": 226, + "h": 59, + "w": 59 + }, + { + "x": 311, + "y": 290, + "h": 57, + "w": 57 + }, + { + "x": 192, + "y": 480, + "h": 42, + "w": 42 + }, + { + "x": 780, + "y": 368, + "h": 58, + "w": 58 + }, + { + "x": 689, + "y": 438, + "h": 250, + "w": 250 + } + ] + }, + "small": { + "faces": [ + { + "x": 430, + "y": 126, + "h": 32, + "w": 32 + }, + { + "x": 256, + "y": 160, + "h": 28, + "w": 28 + }, + { + "x": 346, + "y": 128, + "h": 33, + "w": 33 + }, + { + "x": 176, + "y": 164, + "h": 32, + "w": 32 + }, + { + "x": 108, + "y": 272, + "h": 23, + "w": 23 + }, + { + "x": 442, + "y": 208, + "h": 32, + "w": 32 + }, + { + "x": 390, + "y": 248, + "h": 141, + "w": 141 + } + ] + }, + "orig": { + "faces": [ + { + "x": 810, + "y": 238, + "h": 61, + "w": 61 + }, + { + "x": 483, + "y": 302, + "h": 53, + "w": 53 + }, + { + "x": 653, + "y": 242, + "h": 63, + "w": 63 + }, + { + "x": 332, + "y": 310, + "h": 61, + "w": 61 + }, + { + "x": 205, + "y": 512, + "h": 45, + "w": 45 + }, + { + "x": 832, + "y": 393, + "h": 62, + "w": 62 + }, + { + "x": 735, + "y": 468, + "h": 267, + "w": 267 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 310, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 362, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 523, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904640964779491328" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [ + { + "indices": [ + 329, + 344 + ], + "text": "AquíSeResuelve" + } + ], + "symbols": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/media/Gm6k3cIWMAAQEsB.jpg", + "https://pbs.twimg.com/media/Gm6k3cJWwAArQFG.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904543049268572301", + "url": "https://x.com/AdrianDeLaGarza/status/1904543049268572301", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1904543049268572301", + "text": "En relación a la situación de maltrato animal que sufrió un tlacuache, por parte de un trabajador de un carwash al poniente de la ciudad. Les comento que desde ayer se realizó una inspección al lugar de los hechos y ya se tiene identificado al responsable, el Municipio de Monterrey dará seguimiento al caso a través de Justicia Cívica, donde se determinarán las sanciones o medidas que apliquen en este caso conforme al reglamento de Protección y Bienestar Animal.\n\nAdemás, colaboraremos con autoridades estatales y federales de protección de fauna silvestre para que estas agresiones sean sancionadas y evitar su repetición.", + "fullText": "En relación a la situación de maltrato animal que sufrió un tlacuache, por parte de un trabajador de un carwash al poniente de la ciudad. Les comento que desde ayer se realizó una inspección al lugar de los hechos y ya se tiene identificado al responsable, el Municipio de https://t.co/3xUuYVbrOZ", + "source": "Twitter for iPhone", + "retweetCount": 104, + "replyCount": 117, + "likeCount": 553, + "quoteCount": 13, + "viewCount": 49667, + "createdAt": "Tue Mar 25 14:37:02 +0000 2025", + "lang": "es", + "bookmarkCount": 19, + "isReply": false, + "conversationId": "1904543049268572301", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/3xUuYVbrOZ", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904543049268572301/photo/1", + "id_str": "1904543044126318592", + "indices": [ + 273, + 296 + ], + "media_key": "3_1904543044126318592", + "media_url_https": "https://pbs.twimg.com/media/Gm5LztSXIAA3k-s.jpg", + "type": "photo", + "url": "https://t.co/3xUuYVbrOZ", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 15, + "y": 725, + "h": 78, + "w": 78 + } + ] + }, + "medium": { + "faces": [ + { + "x": 11, + "y": 543, + "h": 58, + "w": 58 + } + ] + }, + "small": { + "faces": [ + { + "x": 6, + "y": 308, + "h": 33, + "w": 33 + } + ] + }, + "orig": { + "faces": [ + { + "x": 15, + "y": 725, + "h": 78, + "w": 78 + } + ] + } + }, + "sizes": { + "large": { + "h": 1200, + "w": 1600, + "resize": "fit" + }, + "medium": { + "h": 900, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 510, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1200, + "width": 1600, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1600, + "h": 896 + }, + { + "x": 0, + "y": 0, + "w": 1200, + "h": 1200 + }, + { + "x": 74, + "y": 0, + "w": 1053, + "h": 1200 + }, + { + "x": 300, + "y": 0, + "w": 600, + "h": 1200 + }, + { + "x": 0, + "y": 0, + "w": 1600, + "h": 1200 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904543044126318592" + } + } + }, + { + "display_url": "pic.x.com/3xUuYVbrOZ", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904543049268572301/photo/1", + "id_str": "1904543044113780736", + "indices": [ + 273, + 296 + ], + "media_key": "3_1904543044113780736", + "media_url_https": "https://pbs.twimg.com/media/Gm5LztPX0AATgx5.jpg", + "type": "photo", + "url": "https://t.co/3xUuYVbrOZ", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [] + }, + "medium": { + "faces": [] + }, + "small": { + "faces": [] + }, + "orig": { + "faces": [] + } + }, + "sizes": { + "large": { + "h": 1200, + "w": 1600, + "resize": "fit" + }, + "medium": { + "h": 900, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 510, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1200, + "width": 1600, + "focus_rects": [ + { + "x": 0, + "y": 304, + "w": 1600, + "h": 896 + }, + { + "x": 400, + "y": 0, + "w": 1200, + "h": 1200 + }, + { + "x": 547, + "y": 0, + "w": 1053, + "h": 1200 + }, + { + "x": 780, + "y": 0, + "w": 600, + "h": 1200 + }, + { + "x": 0, + "y": 0, + "w": 1600, + "h": 1200 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904543044113780736" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [], + "symbols": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/media/Gm5LztSXIAA3k-s.jpg", + "https://pbs.twimg.com/media/Gm5LztPX0AATgx5.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904279979539431737", + "url": "https://x.com/AdrianDeLaGarza/status/1904279979539431737", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1904279979539431737", + "text": "¡Se va a armar la carnita asada! \n\nBienvenidos los @RedSox a tierras regias. Aquí tienen su casa y esperamos verlos de nuevo. Felicidades a todo el equipo de @SultanesOficial por la gran fiesta que han armado. \n\n¡Play Ball! ⚾️ https://t.co/6R4jhd2rf9", + "fullText": "¡Se va a armar la carnita asada! \n\nBienvenidos los @RedSox a tierras regias. Aquí tienen su casa y esperamos verlos de nuevo. Felicidades a todo el equipo de @SultanesOficial por la gran fiesta que han armado. \n\n¡Play Ball! ⚾️ https://t.co/6R4jhd2rf9", + "source": "Twitter for iPhone", + "retweetCount": 7, + "replyCount": 31, + "likeCount": 46, + "quoteCount": 0, + "viewCount": 1463, + "createdAt": "Mon Mar 24 21:11:41 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": false, + "conversationId": "1904279979539431737", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/6R4jhd2rf9", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904279979539431737/photo/1", + "id_str": "1904279976058253312", + "indices": [ + 227, + 250 + ], + "media_key": "3_1904279976058253312", + "media_url_https": "https://pbs.twimg.com/media/Gm1cjIXXcAAPCqO.jpg", + "type": "photo", + "url": "https://t.co/6R4jhd2rf9", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 747, + "y": 888, + "h": 83, + "w": 83 + } + ] + }, + "medium": { + "faces": [ + { + "x": 664, + "y": 789, + "h": 73, + "w": 73 + } + ] + }, + "small": { + "faces": [ + { + "x": 376, + "y": 447, + "h": 41, + "w": 41 + } + ] + }, + "orig": { + "faces": [ + { + "x": 747, + "y": 888, + "h": 83, + "w": 83 + } + ] + } + }, + "sizes": { + "large": { + "h": 1350, + "w": 1080, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 960, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 544, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1350, + "width": 1080, + "focus_rects": [ + { + "x": 0, + "y": 744, + "w": 1080, + "h": 605 + }, + { + "x": 0, + "y": 270, + "w": 1080, + "h": 1080 + }, + { + "x": 0, + "y": 119, + "w": 1080, + "h": 1231 + }, + { + "x": 405, + "y": 0, + "w": 675, + "h": 1350 + }, + { + "x": 0, + "y": 0, + "w": 1080, + "h": 1350 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904279976058253312" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [], + "media": [ + { + "display_url": "pic.x.com/6R4jhd2rf9", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904279979539431737/photo/1", + "id_str": "1904279976058253312", + "indices": [ + 227, + 250 + ], + "media_key": "3_1904279976058253312", + "media_url_https": "https://pbs.twimg.com/media/Gm1cjIXXcAAPCqO.jpg", + "type": "photo", + "url": "https://t.co/6R4jhd2rf9", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 747, + "y": 888, + "h": 83, + "w": 83 + } + ] + }, + "medium": { + "faces": [ + { + "x": 664, + "y": 789, + "h": 73, + "w": 73 + } + ] + }, + "small": { + "faces": [ + { + "x": 376, + "y": 447, + "h": 41, + "w": 41 + } + ] + }, + "orig": { + "faces": [ + { + "x": 747, + "y": 888, + "h": 83, + "w": 83 + } + ] + } + }, + "sizes": { + "large": { + "h": 1350, + "w": 1080, + "resize": "fit" + }, + "medium": { + "h": 1200, + "w": 960, + "resize": "fit" + }, + "small": { + "h": 680, + "w": 544, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1350, + "width": 1080, + "focus_rects": [ + { + "x": 0, + "y": 744, + "w": 1080, + "h": 605 + }, + { + "x": 0, + "y": 270, + "w": 1080, + "h": 1080 + }, + { + "x": 0, + "y": 119, + "w": 1080, + "h": 1231 + }, + { + "x": 405, + "y": 0, + "w": 675, + "h": 1350 + }, + { + "x": 0, + "y": 0, + "w": 1080, + "h": 1350 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904279976058253312" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [ + { + "id_str": "40918816", + "name": "Red Sox", + "screen_name": "RedSox", + "indices": [ + 51, + 58 + ] + }, + { + "id_str": "254287870", + "name": "Sultanes de Monterrey", + "screen_name": "SultanesOficial", + "indices": [ + 158, + 174 + ] + } + ] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/media/Gm1cjIXXcAAPCqO.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1904237367273111711", + "url": "https://x.com/AdrianDeLaGarza/status/1904237367273111711", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1904237367273111711", + "text": "Bajo la estrategia ESCUDO nos comprometimos a una nueva proximidad de la Policía de Monterrey. \n\nCon esto, se ha fortalecido gradualmente la tranquilidad en las colonias. Por ejemplo, un oficial de Tránsito localizó a un menor de edad extraviado en las calles de la Col. Independencia. \n\nLuego de canalizarlo al área de la Unidad de Violencia Familiar y de Género, el adolescente fue entregado a su mamá. \n\nSeguimos resolviendo para que las familias regias tengan paz y tranquilidad.\n\n#AquíSeResuelve", + "fullText": "Bajo la estrategia ESCUDO nos comprometimos a una nueva proximidad de la Policía de Monterrey. \n\nCon esto, se ha fortalecido gradualmente la tranquilidad en las colonias. Por ejemplo, un oficial de Tránsito localizó a un menor de edad extraviado en las calles de la Col. https://t.co/jpXQgYpa6l", + "source": "Twitter for iPhone", + "retweetCount": 5, + "replyCount": 33, + "likeCount": 35, + "quoteCount": 0, + "viewCount": 1251, + "createdAt": "Mon Mar 24 18:22:22 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": false, + "conversationId": "1904237367273111711", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/jpXQgYpa6l", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904237367273111711/photo/1", + "id_str": "1904237361849851904", + "indices": [ + 271, + 294 + ], + "media_key": "3_1904237361849851904", + "media_url_https": "https://pbs.twimg.com/media/Gm01yqEWYAA4PTn.jpg", + "type": "photo", + "url": "https://t.co/jpXQgYpa6l", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 529, + "y": 86, + "h": 153, + "w": 153 + } + ] + }, + "medium": { + "faces": [ + { + "x": 444, + "y": 72, + "h": 128, + "w": 128 + } + ] + }, + "small": { + "faces": [ + { + "x": 251, + "y": 40, + "h": 72, + "w": 72 + } + ] + }, + "orig": { + "faces": [ + { + "x": 529, + "y": 86, + "h": 153, + "w": 153 + } + ] + } + }, + "sizes": { + "large": { + "h": 1071, + "w": 1428, + "resize": "fit" + }, + "medium": { + "h": 900, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 510, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1071, + "width": 1428, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1428, + "h": 800 + }, + { + "x": 357, + "y": 0, + "w": 1071, + "h": 1071 + }, + { + "x": 489, + "y": 0, + "w": 939, + "h": 1071 + }, + { + "x": 892, + "y": 0, + "w": 536, + "h": 1071 + }, + { + "x": 0, + "y": 0, + "w": 1428, + "h": 1071 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904237361849851904" + } + } + }, + { + "display_url": "pic.x.com/jpXQgYpa6l", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1904237367273111711/photo/1", + "id_str": "1904237361997008896", + "indices": [ + 271, + 294 + ], + "media_key": "3_1904237361997008896", + "media_url_https": "https://pbs.twimg.com/media/Gm01yqnb0AANKMy.jpg", + "type": "photo", + "url": "https://t.co/jpXQgYpa6l", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 910, + "y": 701, + "h": 68, + "w": 68 + }, + { + "x": 603, + "y": 178, + "h": 125, + "w": 125 + }, + { + "x": 314, + "y": 176, + "h": 128, + "w": 128 + }, + { + "x": 1095, + "y": 70, + "h": 154, + "w": 154 + } + ] + }, + "medium": { + "faces": [ + { + "x": 682, + "y": 525, + "h": 51, + "w": 51 + }, + { + "x": 452, + "y": 133, + "h": 93, + "w": 93 + }, + { + "x": 235, + "y": 132, + "h": 96, + "w": 96 + }, + { + "x": 821, + "y": 52, + "h": 115, + "w": 115 + } + ] + }, + "small": { + "faces": [ + { + "x": 386, + "y": 297, + "h": 28, + "w": 28 + }, + { + "x": 256, + "y": 75, + "h": 53, + "w": 53 + }, + { + "x": 133, + "y": 74, + "h": 54, + "w": 54 + }, + { + "x": 465, + "y": 29, + "h": 65, + "w": 65 + } + ] + }, + "orig": { + "faces": [ + { + "x": 910, + "y": 701, + "h": 68, + "w": 68 + }, + { + "x": 603, + "y": 178, + "h": 125, + "w": 125 + }, + { + "x": 314, + "y": 176, + "h": 128, + "w": 128 + }, + { + "x": 1095, + "y": 70, + "h": 154, + "w": 154 + } + ] + } + }, + "sizes": { + "large": { + "h": 1200, + "w": 1600, + "resize": "fit" + }, + "medium": { + "h": 900, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 510, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1200, + "width": 1600, + "focus_rects": [ + { + "x": 0, + "y": 72, + "w": 1600, + "h": 896 + }, + { + "x": 0, + "y": 0, + "w": 1200, + "h": 1200 + }, + { + "x": 74, + "y": 0, + "w": 1053, + "h": 1200 + }, + { + "x": 300, + "y": 0, + "w": 600, + "h": 1200 + }, + { + "x": 0, + "y": 0, + "w": 1600, + "h": 1200 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1904237361997008896" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [ + { + "indices": [ + 485, + 500 + ], + "text": "AquíSeResuelve" + } + ], + "symbols": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/media/Gm01yqEWYAA4PTn.jpg", + "https://pbs.twimg.com/media/Gm01yqnb0AANKMy.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1903948214320971904", + "url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1903948214320971904", + "text": "La espera terminó, el día de hoy inauguramos la tan esperada Temporada Acuática 2025 💦\n\n#AquíSeResuelve https://t.co/Ow0RB6Yurn", + "fullText": "La espera terminó, el día de hoy inauguramos la tan esperada Temporada Acuática 2025 💦\n\n#AquíSeResuelve https://t.co/Ow0RB6Yurn", + "source": "Twitter for iPhone", + "retweetCount": 14, + "replyCount": 46, + "likeCount": 89, + "quoteCount": 0, + "viewCount": 3108, + "createdAt": "Sun Mar 23 23:13:22 +0000 2025", + "lang": "es", + "bookmarkCount": 0, + "isReply": false, + "conversationId": "1903948214320971904", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948208029487104", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948208029487104", + "media_url_https": "https://pbs.twimg.com/media/GmwuzsJW0AArEK6.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 562, + "y": 225, + "h": 85, + "w": 85 + }, + { + "x": 733, + "y": 165, + "h": 102, + "w": 102 + } + ] + }, + "medium": { + "faces": [ + { + "x": 526, + "y": 210, + "h": 79, + "w": 79 + }, + { + "x": 687, + "y": 154, + "h": 95, + "w": 95 + } + ] + }, + "small": { + "faces": [ + { + "x": 298, + "y": 119, + "h": 45, + "w": 45 + }, + { + "x": 389, + "y": 87, + "h": 54, + "w": 54 + } + ] + }, + "orig": { + "faces": [ + { + "x": 562, + "y": 225, + "h": 85, + "w": 85 + }, + { + "x": 733, + "y": 165, + "h": 102, + "w": 102 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 310, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 362, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 523, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948208029487104" + } + } + }, + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948208033710080", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948208033710080", + "media_url_https": "https://pbs.twimg.com/media/GmwuzsKXQAAPID7.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 515, + "y": 645, + "h": 62, + "w": 62 + } + ] + }, + "medium": { + "faces": [ + { + "x": 482, + "y": 604, + "h": 58, + "w": 58 + } + ] + }, + "small": { + "faces": [ + { + "x": 273, + "y": 342, + "h": 32, + "w": 32 + } + ] + }, + "orig": { + "faces": [ + { + "x": 515, + "y": 645, + "h": 62, + "w": 62 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 122, + "w": 1280, + "h": 717 + }, + { + "x": 374, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 426, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 587, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948208033710080" + } + } + }, + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948207861735424", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948207861735424", + "media_url_https": "https://pbs.twimg.com/media/GmwuzrhXIAAFa_l.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 326, + "y": 251, + "h": 147, + "w": 147 + }, + { + "x": 1072, + "y": 261, + "h": 150, + "w": 150 + } + ] + }, + "medium": { + "faces": [ + { + "x": 305, + "y": 235, + "h": 137, + "w": 137 + }, + { + "x": 1005, + "y": 244, + "h": 140, + "w": 140 + } + ] + }, + "small": { + "faces": [ + { + "x": 173, + "y": 133, + "h": 78, + "w": 78 + }, + { + "x": 569, + "y": 138, + "h": 79, + "w": 79 + } + ] + }, + "orig": { + "faces": [ + { + "x": 326, + "y": 251, + "h": 147, + "w": 147 + }, + { + "x": 1072, + "y": 261, + "h": 150, + "w": 150 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 0, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 42, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 203, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948207861735424" + } + } + }, + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948207870160896", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948207870160896", + "media_url_https": "https://pbs.twimg.com/media/GmwuzrjXsAAGKd1.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 577, + "y": 205, + "h": 135, + "w": 135 + } + ] + }, + "medium": { + "faces": [ + { + "x": 540, + "y": 192, + "h": 126, + "w": 126 + } + ] + }, + "small": { + "faces": [ + { + "x": 306, + "y": 108, + "h": 71, + "w": 71 + } + ] + }, + "orig": { + "faces": [ + { + "x": 577, + "y": 205, + "h": 135, + "w": 135 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 182, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 234, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 395, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948207870160896" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [ + { + "indices": [ + 88, + 103 + ], + "text": "AquíSeResuelve" + } + ], + "media": [ + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948208029487104", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948208029487104", + "media_url_https": "https://pbs.twimg.com/media/GmwuzsJW0AArEK6.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 562, + "y": 225, + "h": 85, + "w": 85 + }, + { + "x": 733, + "y": 165, + "h": 102, + "w": 102 + } + ] + }, + "medium": { + "faces": [ + { + "x": 526, + "y": 210, + "h": 79, + "w": 79 + }, + { + "x": 687, + "y": 154, + "h": 95, + "w": 95 + } + ] + }, + "small": { + "faces": [ + { + "x": 298, + "y": 119, + "h": 45, + "w": 45 + }, + { + "x": 389, + "y": 87, + "h": 54, + "w": 54 + } + ] + }, + "orig": { + "faces": [ + { + "x": 562, + "y": 225, + "h": 85, + "w": 85 + }, + { + "x": 733, + "y": 165, + "h": 102, + "w": 102 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 310, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 362, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 523, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948208029487104" + } + } + }, + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948208033710080", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948208033710080", + "media_url_https": "https://pbs.twimg.com/media/GmwuzsKXQAAPID7.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 515, + "y": 645, + "h": 62, + "w": 62 + } + ] + }, + "medium": { + "faces": [ + { + "x": 482, + "y": 604, + "h": 58, + "w": 58 + } + ] + }, + "small": { + "faces": [ + { + "x": 273, + "y": 342, + "h": 32, + "w": 32 + } + ] + }, + "orig": { + "faces": [ + { + "x": 515, + "y": 645, + "h": 62, + "w": 62 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 122, + "w": 1280, + "h": 717 + }, + { + "x": 374, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 426, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 587, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948208033710080" + } + } + }, + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948207861735424", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948207861735424", + "media_url_https": "https://pbs.twimg.com/media/GmwuzrhXIAAFa_l.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 326, + "y": 251, + "h": 147, + "w": 147 + }, + { + "x": 1072, + "y": 261, + "h": 150, + "w": 150 + } + ] + }, + "medium": { + "faces": [ + { + "x": 305, + "y": 235, + "h": 137, + "w": 137 + }, + { + "x": 1005, + "y": 244, + "h": 140, + "w": 140 + } + ] + }, + "small": { + "faces": [ + { + "x": 173, + "y": 133, + "h": 78, + "w": 78 + }, + { + "x": 569, + "y": 138, + "h": 79, + "w": 79 + } + ] + }, + "orig": { + "faces": [ + { + "x": 326, + "y": 251, + "h": 147, + "w": 147 + }, + { + "x": 1072, + "y": 261, + "h": 150, + "w": 150 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 0, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 42, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 203, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948207861735424" + } + } + }, + { + "display_url": "pic.x.com/Ow0RB6Yurn", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903948214320971904/photo/1", + "id_str": "1903948207870160896", + "indices": [ + 104, + 127 + ], + "media_key": "3_1903948207870160896", + "media_url_https": "https://pbs.twimg.com/media/GmwuzrjXsAAGKd1.jpg", + "type": "photo", + "url": "https://t.co/Ow0RB6Yurn", + "ext_media_availability": { + "status": "Available" + }, + "features": { + "large": { + "faces": [ + { + "x": 577, + "y": 205, + "h": 135, + "w": 135 + } + ] + }, + "medium": { + "faces": [ + { + "x": 540, + "y": 192, + "h": 126, + "w": 126 + } + ] + }, + "small": { + "faces": [ + { + "x": 306, + "y": 108, + "h": 71, + "w": 71 + } + ] + }, + "orig": { + "faces": [ + { + "x": 577, + "y": 205, + "h": 135, + "w": 135 + } + ] + } + }, + "sizes": { + "large": { + "h": 853, + "w": 1280, + "resize": "fit" + }, + "medium": { + "h": 800, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 453, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 853, + "width": 1280, + "focus_rects": [ + { + "x": 0, + "y": 0, + "w": 1280, + "h": 717 + }, + { + "x": 182, + "y": 0, + "w": 853, + "h": 853 + }, + { + "x": 234, + "y": 0, + "w": 748, + "h": 853 + }, + { + "x": 395, + "y": 0, + "w": 427, + "h": 853 + }, + { + "x": 0, + "y": 0, + "w": 1280, + "h": 853 + } + ] + }, + "allow_download_status": { + "allow_download": true + }, + "media_results": { + "result": { + "media_key": "3_1903948207870160896" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/media/GmwuzsJW0AArEK6.jpg", + "https://pbs.twimg.com/media/GmwuzsKXQAAPID7.jpg", + "https://pbs.twimg.com/media/GmwuzrhXIAAFa_l.jpg", + "https://pbs.twimg.com/media/GmwuzrjXsAAGKd1.jpg" + ], + "isConversationControlled": false + }, + { + "type": "tweet", + "id": "1903617133097197760", + "url": "https://x.com/AdrianDeLaGarza/status/1903617133097197760", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza/status/1903617133097197760", + "text": "Es momento de sacar el short y las chanclas para disfrutar de la Temporada Acuática en Monterrey. 💦🛟\n\nVen con tu familia y amigos a los parques Aztlán, España, Tucán y Monterrey 400. \n\n¡Te esperamos!\n\n🎥: Video Gob MTY\n\n#AquíSeResuelve https://t.co/Ezv7eE6dnS", + "fullText": "Es momento de sacar el short y las chanclas para disfrutar de la Temporada Acuática en Monterrey. 💦🛟\n\nVen con tu familia y amigos a los parques Aztlán, España, Tucán y Monterrey 400. \n\n¡Te esperamos!\n\n🎥: Video Gob MTY\n\n#AquíSeResuelve https://t.co/Ezv7eE6dnS", + "source": "Twitter for iPhone", + "retweetCount": 10, + "replyCount": 52, + "likeCount": 50, + "quoteCount": 1, + "viewCount": 2206, + "createdAt": "Sun Mar 23 01:17:46 +0000 2025", + "lang": "es", + "bookmarkCount": 1, + "isReply": false, + "conversationId": "1903617133097197760", + "possiblySensitive": false, + "isPinned": false, + "author": { + "type": "user", + "userName": "AdrianDeLaGarza", + "url": "https://x.com/AdrianDeLaGarza", + "twitterUrl": "https://twitter.com/AdrianDeLaGarza", + "id": "2357040230", + "name": "Adrián de la Garza", + "isVerified": false, + "isBlueVerified": true, + "profilePicture": "https://pbs.twimg.com/profile_images/1886823379035963392/pzJUw5aV_normal.jpg", + "coverPicture": "https://pbs.twimg.com/profile_banners/2357040230/1740779857", + "description": "Alcalde de Monterrey 2024 - 2027", + "location": "Monterrey, Nuevo León, México", + "followers": 72178, + "following": 309, + "status": "", + "canDm": false, + "canMediaTag": true, + "createdAt": "Sat Feb 22 22:40:14 +0000 2014", + "entities": { + "description": { + "urls": [] + } + }, + "fastFollowersCount": 0, + "favouritesCount": 6630, + "hasCustomTimelines": false, + "isTranslator": false, + "mediaCount": 11495, + "statusesCount": 36076, + "withheldInCountries": [], + "affiliatesHighlightedLabel": {}, + "possiblySensitive": false, + "pinnedTweetIds": [] + }, + "extendedEntities": { + "media": [ + { + "display_url": "pic.x.com/Ezv7eE6dnS", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903617133097197760/video/1", + "id_str": "1903617018823385088", + "indices": [ + 235, + 258 + ], + "media_key": "13_1903617018823385088", + "media_url_https": "https://pbs.twimg.com/amplify_video_thumb/1903617018823385088/img/8FaJCJkzNRIZa1Zg.jpg", + "type": "video", + "url": "https://t.co/Ezv7eE6dnS", + "additional_media_info": { + "monetizable": false + }, + "ext_media_availability": { + "status": "Available" + }, + "sizes": { + "large": { + "h": 1080, + "w": 1920, + "resize": "fit" + }, + "medium": { + "h": 675, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 383, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1080, + "width": 1920, + "focus_rects": [] + }, + "allow_download_status": { + "allow_download": true + }, + "video_info": { + "aspect_ratio": [ + 16, + 9 + ], + "duration_millis": 20020, + "variants": [ + { + "content_type": "application/x-mpegURL", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/pl/elWX8QgI3_z2VL_J.m3u8?tag=16&v=bf3" + }, + { + "bitrate": 288000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/480x270/nhuPuboxMDSyNXVF.mp4?tag=16" + }, + { + "bitrate": 832000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/640x360/EF0BzPe7UH38mjpE.mp4?tag=16" + }, + { + "bitrate": 2176000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/1280x720/XY38_gXA5afNrPLv.mp4?tag=16" + }, + { + "bitrate": 10368000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/1920x1080/zg-IbwGbn9Fitbln.mp4?tag=16" + } + ] + }, + "media_results": { + "result": { + "media_key": "13_1903617018823385088" + } + } + } + ] + }, + "card": {}, + "place": {}, + "entities": { + "hashtags": [ + { + "indices": [ + 219, + 234 + ], + "text": "AquíSeResuelve" + } + ], + "media": [ + { + "display_url": "pic.x.com/Ezv7eE6dnS", + "expanded_url": "https://x.com/AdrianDeLaGarza/status/1903617133097197760/video/1", + "id_str": "1903617018823385088", + "indices": [ + 235, + 258 + ], + "media_key": "13_1903617018823385088", + "media_url_https": "https://pbs.twimg.com/amplify_video_thumb/1903617018823385088/img/8FaJCJkzNRIZa1Zg.jpg", + "type": "video", + "url": "https://t.co/Ezv7eE6dnS", + "additional_media_info": { + "monetizable": false + }, + "ext_media_availability": { + "status": "Available" + }, + "sizes": { + "large": { + "h": 1080, + "w": 1920, + "resize": "fit" + }, + "medium": { + "h": 675, + "w": 1200, + "resize": "fit" + }, + "small": { + "h": 383, + "w": 680, + "resize": "fit" + }, + "thumb": { + "h": 150, + "w": 150, + "resize": "crop" + } + }, + "original_info": { + "height": 1080, + "width": 1920, + "focus_rects": [] + }, + "allow_download_status": { + "allow_download": true + }, + "video_info": { + "aspect_ratio": [ + 16, + 9 + ], + "duration_millis": 20020, + "variants": [ + { + "content_type": "application/x-mpegURL", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/pl/elWX8QgI3_z2VL_J.m3u8?tag=16&v=bf3" + }, + { + "bitrate": 288000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/480x270/nhuPuboxMDSyNXVF.mp4?tag=16" + }, + { + "bitrate": 832000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/640x360/EF0BzPe7UH38mjpE.mp4?tag=16" + }, + { + "bitrate": 2176000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/1280x720/XY38_gXA5afNrPLv.mp4?tag=16" + }, + { + "bitrate": 10368000, + "content_type": "video/mp4", + "url": "https://video.twimg.com/amplify_video/1903617018823385088/vid/avc1/1920x1080/zg-IbwGbn9Fitbln.mp4?tag=16" + } + ] + }, + "media_results": { + "result": { + "media_key": "13_1903617018823385088" + } + } + } + ], + "symbols": [], + "timestamps": [], + "urls": [], + "user_mentions": [] + }, + "isRetweet": false, + "isQuote": false, + "media": [ + "https://pbs.twimg.com/amplify_video_thumb/1903617018823385088/img/8FaJCJkzNRIZa1Zg.jpg" + ], + "isConversationControlled": false + } +] \ No newline at end of file diff --git a/backend/app/testing/test_transform_facebook.py b/backend/app/testing/test_transform_facebook.py new file mode 100644 index 0000000000..1fda166e0b --- /dev/null +++ b/backend/app/testing/test_transform_facebook.py @@ -0,0 +1,206 @@ +""" +Tests for Facebook data transformer + +This module tests the transformation of Facebook raw data from APIFY +to the format expected by the application's repositories. +""" + +import json +import os +import sys +import uuid +from datetime import datetime +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +# Add the project root to the Python path +sys.path.append(str(Path(__file__).parent.parent.parent)) + +from app.db.models.social_media_account import Platform + + +class TestFacebookTransforms: + """ + Test the transformation of Facebook data from APIFY to the application format. + """ + + @pytest.fixture + def sample_profile_data(self): + """Load sample Facebook profile data.""" + data_path = Path(__file__).parent / "data" / "facebook" / "profile_samples.json" + with open(data_path, "r", encoding="utf-8") as f: + return json.load(f) + + @pytest.fixture + def sample_post_data(self): + """Load sample Facebook post data.""" + data_path = Path(__file__).parent / "data" / "facebook" / "post_samples.json" + with open(data_path, "r", encoding="utf-8") as f: + return json.load(f) + + @pytest.fixture + def sample_comment_data(self): + """Load sample Facebook comment data.""" + data_path = Path(__file__).parent / "data" / "facebook" / "comment_samples.json" + with open(data_path, "r", encoding="utf-8") as f: + return json.load(f) + + def test_transform_profile(self, sample_profile_data): + """Test the transformation of a Facebook profile by manually creating the transform.""" + # Get the first profile from the sample data + raw_profile = sample_profile_data[0] + + # Extract the page ID and handle + page_id = raw_profile.get("pageId", raw_profile.get("facebookId", "")) + page_url = raw_profile.get("pageUrl", "") + handle = "" + + # Try to extract handle from URL + if page_url: + try: + path = page_url.split("/")[-2] if page_url.endswith("/") else page_url.split("/")[-1] + if path and path != "/": + handle = path + except Exception: + pass + + # Use pageName if handle extraction failed + if not handle: + handle = raw_profile.get("pageName", "").lower().replace(" ", "") + + # Manual transformation (same logic as in the collector) + transformed = { + "platform": Platform.FACEBOOK, + "platform_id": page_id, + "handle": handle, + "name": raw_profile.get("title", ""), + "url": page_url, + "verified": False, # Facebook data doesn't consistently show verification + "follower_count": raw_profile.get("followers", raw_profile.get("likes", 0)), + "following_count": raw_profile.get("followings") + } + + # Check if transformation matches expectations for social_media_account.py + assert transformed["platform"] == Platform.FACEBOOK + assert transformed["platform_id"] == raw_profile["pageId"] + assert transformed["handle"] == raw_profile["pageName"] + assert transformed["name"] == raw_profile["title"] + assert transformed["url"] == raw_profile["pageUrl"] + assert transformed["follower_count"] == raw_profile["followers"] + assert transformed["following_count"] == raw_profile["followings"] + + # Ensure political_entity_id is not included (should be set when account is created) + assert "political_entity_id" not in transformed + + def test_transform_post(self, sample_post_data): + """Test the transformation of a Facebook post by manually validating key fields.""" + # Get the first post from the sample data + raw_post = sample_post_data[0] + + # Create a fake account ID + account_id = str(uuid.uuid4()) + + # Check if key fields are present in the raw post + assert "postId" in raw_post + assert "text" in raw_post + assert "url" in raw_post + assert "likes" in raw_post + assert "comments" in raw_post + assert "shares" in raw_post + assert "media" in raw_post + assert "timestamp" in raw_post + + # Validate expected field types and structures + assert isinstance(raw_post["postId"], str) + assert isinstance(raw_post["text"], str) + assert isinstance(raw_post["url"], str) + assert isinstance(raw_post["likes"], int) + assert isinstance(raw_post["comments"], int) + assert isinstance(raw_post["shares"], int) + + # Test MongoDB schema compatibility + # These are the key fields needed by the MongoDB schema + required_fields = { + "platform_id": raw_post["postId"], + "platform": "facebook", + "account_id": account_id, + "content_type": "post", # Default type for Facebook + "content": { + "text": raw_post["text"] + }, + "metadata": { + "created_at": datetime.fromtimestamp(raw_post["timestamp"]) + }, + "engagement": { + "likes_count": raw_post["likes"], + "comments_count": raw_post["comments"], + "shares_count": raw_post["shares"] + } + } + + # Check if media is extracted correctly + if "media" in raw_post and raw_post["media"]: + assert len(raw_post["media"]) > 0 + if "photo_image" in raw_post["media"][0]: + assert "uri" in raw_post["media"][0]["photo_image"] + + # All required fields should be present in raw data + for field, value in required_fields.items(): + if field in ["content", "metadata", "engagement"]: + # These are nested fields, continue + continue + assert value is not None, f"Field {field} should not be None" + + def test_transform_comment(self, sample_comment_data): + """Test the transformation of a Facebook comment by manually validating key fields.""" + # Get the first comment from the sample data + raw_comment = sample_comment_data[0] + + # Create a fake post ID + post_id = "fake_post_id" + + # Check if key fields are present in the raw comment + assert "id" in raw_comment + assert "text" in raw_comment + assert "profileName" in raw_comment + assert "profileId" in raw_comment + assert "date" in raw_comment + assert "likesCount" in raw_comment + + # Validate expected field types and structures + assert isinstance(raw_comment["id"], str) + assert isinstance(raw_comment["text"], str) + assert isinstance(raw_comment["profileName"], str) + assert isinstance(raw_comment["profileId"], str) + + # Test MongoDB schema compatibility + # These are the key fields needed by the MongoDB schema + required_fields = { + "platform_id": raw_comment["id"], + "platform": "facebook", + "post_id": post_id, + "user_id": raw_comment["profileId"], + "user_name": raw_comment["profileName"], + "content": { + "text": raw_comment["text"] + }, + "metadata": { + "created_at": datetime.fromisoformat(raw_comment["date"].replace('Z', '+00:00')) + }, + "engagement": { + "likes_count": int(raw_comment["likesCount"]) if isinstance(raw_comment["likesCount"], str) else raw_comment["likesCount"] + } + } + + # All required fields should be present in raw data + for field, value in required_fields.items(): + if field in ["content", "metadata", "engagement"]: + # These are nested fields, continue + continue + assert value is not None, f"Field {field} should not be None" + + +if __name__ == "__main__": + pytest.main(["-xvs", __file__]) \ No newline at end of file diff --git a/backend/app/testing/test_transform_instagram.py b/backend/app/testing/test_transform_instagram.py new file mode 100644 index 0000000000..460d843612 --- /dev/null +++ b/backend/app/testing/test_transform_instagram.py @@ -0,0 +1,212 @@ +""" +Tests for Instagram data transformer + +This module tests the transformation of Instagram raw data from APIFY +to the format expected by the application's repositories. +""" + +import json +import os +import sys +import uuid +from datetime import datetime +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +# Add the project root to the Python path +sys.path.append(str(Path(__file__).parent.parent.parent)) + +from app.db.models.social_media_account import Platform + + +class TestInstagramTransforms: + """ + Test the transformation of Instagram data from APIFY to the application format. + """ + + @pytest.fixture + def sample_profile_data(self): + """Load sample Instagram profile data.""" + data_path = Path(__file__).parent / "data" / "instagram" / "profile_samples.json" + with open(data_path, "r", encoding="utf-8") as f: + return json.load(f) + + @pytest.fixture + def sample_post_data(self): + """Load sample Instagram post data.""" + data_path = Path(__file__).parent / "data" / "instagram" / "post_samples.json" + with open(data_path, "r", encoding="utf-8") as f: + return json.load(f) + + @pytest.fixture + def sample_comment_data(self): + """Load sample Instagram comment data.""" + data_path = Path(__file__).parent / "data" / "instagram" / "comment_samples.json" + with open(data_path, "r", encoding="utf-8") as f: + return json.load(f) + + def test_transform_profile(self, sample_profile_data): + """Test the transformation of an Instagram profile by manually creating the transform.""" + # Get the first profile from the sample data + raw_profile = sample_profile_data[0] + + # Extract basic info + username = raw_profile.get("username", "") + profile_url = f"https://www.instagram.com/{username}/" if username else "" + + # Manual transformation (same logic as in the collector) + transformed = { + "platform": Platform.INSTAGRAM, + "platform_id": raw_profile.get("id", ""), + "handle": username, + "name": raw_profile.get("fullName", ""), + "url": profile_url, + "verified": raw_profile.get("verified", False), + "follower_count": raw_profile.get("followersCount", 0), + "following_count": raw_profile.get("followsCount", 0) + } + + # Check if transformation matches expectations for social_media_account.py + assert transformed["platform"] == Platform.INSTAGRAM + assert transformed["platform_id"] == raw_profile["id"] + assert transformed["handle"] == raw_profile["username"] + assert transformed["name"] == raw_profile["fullName"] + assert transformed["url"] == f"https://www.instagram.com/{raw_profile['username']}/" + assert transformed["verified"] == raw_profile["verified"] + assert transformed["follower_count"] == raw_profile["followersCount"] + assert transformed["following_count"] == raw_profile["followsCount"] + + # Ensure political_entity_id is not included (should be set when account is created) + assert "political_entity_id" not in transformed + + def test_transform_post(self, sample_post_data): + """Test the transformation of an Instagram post by manually validating key fields.""" + # Get the first post from the sample data + raw_post = sample_post_data[0] + + # Create a fake account ID + account_id = str(uuid.uuid4()) + + # Check if key fields are present in the raw post + assert "id" in raw_post + assert "shortCode" in raw_post + assert "caption" in raw_post + assert "url" in raw_post + assert "likesCount" in raw_post + assert "commentsCount" in raw_post + assert "type" in raw_post + assert raw_post["type"] == "Sidecar" # This is a carousel post + assert "childPosts" in raw_post + assert "timestamp" in raw_post + + # Validate expected field types and structures + assert isinstance(raw_post["id"], str) + assert isinstance(raw_post["shortCode"], str) + assert isinstance(raw_post["caption"], str) + assert isinstance(raw_post["url"], str) + assert isinstance(raw_post["likesCount"], int) + assert isinstance(raw_post["commentsCount"], int) + assert isinstance(raw_post["childPosts"], list) + + # Test MongoDB schema compatibility + # These are the key fields needed by the MongoDB schema + required_fields = { + "platform_id": raw_post["id"], + "platform": "instagram", + "account_id": account_id, + "content_type": "carousel", # Derived from type field + "short_code": raw_post["shortCode"], + "url": raw_post["url"], + "content": { + "text": raw_post["caption"], + "hashtags": raw_post["hashtags"] + }, + "metadata": { + "created_at": datetime.fromisoformat(raw_post["timestamp"].replace('Z', '+00:00')) + }, + "engagement": { + "likes_count": raw_post["likesCount"], + "comments_count": raw_post["commentsCount"] + } + } + + # All required fields should be present in raw data + for field, value in required_fields.items(): + if field in ["content", "metadata", "engagement"]: + # These are nested fields, continue + continue + assert value is not None, f"Field {field} should not be None" + + def test_transform_comment(self, sample_comment_data): + """Test the transformation of an Instagram comment by manually validating key fields.""" + # Get the first comment from the sample data + raw_comment = sample_comment_data[0] + + # Create a fake post ID + post_id = "fake_post_id" + + # Check if key fields are present in the raw comment + assert "id" in raw_comment + assert "text" in raw_comment + assert "postUrl" in raw_comment + assert "owner" in raw_comment + assert "likesCount" in raw_comment + assert "repliesCount" in raw_comment + assert "replies" in raw_comment + assert "timestamp" in raw_comment + + # Validate expected field types and structures + assert isinstance(raw_comment["id"], str) + assert isinstance(raw_comment["text"], str) + assert isinstance(raw_comment["postUrl"], str) + assert isinstance(raw_comment["owner"], dict) + assert isinstance(raw_comment["likesCount"], int) + assert isinstance(raw_comment["repliesCount"], int) + assert isinstance(raw_comment["replies"], list) + + # Check owner fields + owner = raw_comment["owner"] + assert "id" in owner + assert "username" in owner + assert "full_name" in owner + assert "profile_pic_url" in owner + assert "is_verified" in owner + assert "is_private" in owner + + # Test MongoDB schema compatibility + # These are the key fields needed by the MongoDB schema + required_fields = { + "platform_id": raw_comment["id"], + "platform": "instagram", + "post_id": post_id, + "post_url": raw_comment["postUrl"], + "user_id": raw_comment["owner"]["id"], + "user_name": raw_comment["owner"]["username"], + "user_full_name": raw_comment["owner"]["full_name"], + "user_profile_pic": raw_comment["owner"]["profile_pic_url"], + "user_verified": raw_comment["owner"]["is_verified"], + "user_private": raw_comment["owner"]["is_private"], + "content": { + "text": raw_comment["text"] + }, + "metadata": { + "created_at": datetime.fromisoformat(raw_comment["timestamp"].replace('Z', '+00:00')) + }, + "engagement": { + "likes_count": raw_comment["likesCount"], + "replies_count": raw_comment["repliesCount"] + } + } + + # All required fields should be present in raw data + for field, value in required_fields.items(): + if field in ["content", "metadata", "engagement"]: + # These are nested fields, continue + continue + assert value is not None, f"Field {field} should not be None" + + +if __name__ == "__main__": + pytest.main(["-xvs", __file__]) \ No newline at end of file diff --git a/backend/app/testing/test_transform_tiktok.py b/backend/app/testing/test_transform_tiktok.py new file mode 100644 index 0000000000..6229c0bb1a --- /dev/null +++ b/backend/app/testing/test_transform_tiktok.py @@ -0,0 +1,225 @@ +""" +Tests for TikTok data transformer + +This module tests the transformation of TikTok raw data from APIFY +to the format expected by the application's repositories. +""" + +import json +import os +import sys +import uuid +from datetime import datetime +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +# Add the project root to the Python path +sys.path.append(str(Path(__file__).parent.parent.parent)) + +from app.db.models.social_media_account import Platform + + +class TestTikTokTransforms: + """ + Test the transformation of TikTok data from APIFY to the application format. + + Note: Since we don't have actual TikTok sample data files yet, these tests + create mock data that mimics the expected structure from APIFY's TikTok scraper. + """ + + @pytest.fixture + def mock_profile_data(self): + """Create mock TikTok profile data.""" + return { + "id": "12345678", + "uniqueId": "tiktok_user", + "nickname": "TikTok User Name", + "signature": "Bio text here", + "verified": True, + "privateAccount": False, + "followerCount": 100000, + "followingCount": 1000, + "heartCount": 500000, + "videoCount": 200, + "avatarLarger": "https://p16-sign-va.tiktokcdn.com/profile_pic_large.jpg", + "avatarMedium": "https://p16-sign-va.tiktokcdn.com/profile_pic_medium.jpg", + "avatarThumb": "https://p16-sign-va.tiktokcdn.com/profile_pic_thumb.jpg" + } + + @pytest.fixture + def mock_post_data(self): + """Create mock TikTok post data.""" + return { + "id": "7123456789012345678", + "desc": "This is a TikTok video #tiktok #test @mention", + "createTime": 1640995200, # 2022-01-01T00:00:00 + "webVideoUrl": "https://www.tiktok.com/@tiktok_user/video/7123456789012345678", + "videoUrl": "https://v16-webapp.tiktok.com/video.mp4", + "covers": [ + "https://p16-sign-va.tiktokcdn.com/thumbnail.jpg" + ], + "diggCount": 50000, + "shareCount": 5000, + "commentCount": 2000, + "playCount": 100000, + "collectCount": 3000, + "hashtags": [ + {"id": "123", "name": "tiktok"}, + {"id": "456", "name": "test"} + ], + "videoMeta": { + "width": 1080, + "height": 1920, + "duration": 15.5 + }, + "authorMeta": { + "id": "12345678", + "name": "tiktok_user", + "nickname": "TikTok User Name", + "verified": True + } + } + + @pytest.fixture + def mock_comment_data(self): + """Create mock TikTok comment data.""" + return { + "id": "7123456789012345679", + "text": "This is a comment @reply", + "createTime": 1641081600, # 2022-01-02T00:00:00 + "user": { + "id": "87654321", + "uniqueId": "commenter", + "nickname": "Commenter Name", + "avatarThumb": "https://p16-sign-va.tiktokcdn.com/commenter_avatar.jpg", + "verified": False, + "privateAccount": False + }, + "diggCount": 1000, + "replyCount": 5, + "isReply": False, + "replies": [ + { + "id": "7123456789012345680", + "text": "This is a reply", + "createTime": 1641168000, # 2022-01-03T00:00:00 + "userId": "12345678", + "uniqueId": "tiktok_user", + "nickname": "TikTok User Name", + "avatarThumb": "https://p16-sign-va.tiktokcdn.com/profile_pic_thumb.jpg", + "verified": True, + "diggCount": 500, + "replyCount": 0 + } + ] + } + + def test_transform_profile(self, mock_profile_data): + """Test the transformation of a TikTok profile.""" + # Import and patch the transform_profile function directly + from app.processing.collection.tiktok import transform_profile + + # Transform the profile data + transformed = transform_profile(mock_profile_data) + + # Check if transformation matches expectations for social_media_account.py + assert transformed["platform_id"] == mock_profile_data["id"] + assert transformed["handle"] == mock_profile_data["uniqueId"] + assert transformed["name"] == mock_profile_data["nickname"] + assert transformed["url"] == f"https://www.tiktok.com/@{mock_profile_data['uniqueId']}" + assert transformed["verified"] == mock_profile_data["verified"] + assert transformed["follower_count"] == mock_profile_data["followerCount"] + assert transformed["following_count"] == mock_profile_data["followingCount"] + + # Ensure the platform isn't included (should be set when using with SocialMediaAccountRepository) + assert "platform" not in transformed + + # Ensure political_entity_id is not included (should be set when account is created) + assert "political_entity_id" not in transformed + + def test_transform_post(self, mock_post_data): + """Test the transformation of a TikTok post.""" + # Import and patch the transform_post function directly + from app.processing.collection.tiktok import transform_post + + # Create a fake account ID + account_id = str(uuid.uuid4()) + + # Transform the post data + transformed = transform_post(mock_post_data, account_id) + + # Check if transformation matches expectations for MongoDB schema + assert transformed["platform_id"] == mock_post_data["id"] + assert transformed["platform"] == "tiktok" + assert transformed["account_id"] == account_id + assert transformed["content_type"] == "video" + assert transformed["short_code"] == mock_post_data["id"] + assert transformed["url"] == mock_post_data["webVideoUrl"] + + # Check content + assert transformed["content"]["text"] == mock_post_data["desc"] + assert mock_post_data["videoUrl"] in transformed["content"]["media"] + assert len(transformed["content"]["hashtags"]) == 2 + assert "tiktok" in transformed["content"]["hashtags"] + assert "test" in transformed["content"]["hashtags"] + + # Check metadata + assert isinstance(transformed["metadata"]["created_at"], datetime) + assert transformed["metadata"]["dimensions"]["width"] == mock_post_data["videoMeta"]["width"] + assert transformed["metadata"]["dimensions"]["height"] == mock_post_data["videoMeta"]["height"] + assert transformed["metadata"]["owner"]["username"] == mock_post_data["authorMeta"]["name"] + + # Check engagement + assert transformed["engagement"]["likes_count"] == mock_post_data["diggCount"] + assert transformed["engagement"]["shares_count"] == mock_post_data["shareCount"] + assert transformed["engagement"]["comments_count"] == mock_post_data["commentCount"] + assert transformed["engagement"]["views_count"] == mock_post_data["playCount"] + assert transformed["engagement"]["saves_count"] == mock_post_data["collectCount"] + + # Check video data + assert transformed["video_data"]["duration"] == mock_post_data["videoMeta"]["duration"] + assert transformed["video_data"]["video_url"] == mock_post_data["videoUrl"] + assert transformed["video_data"]["thumbnail_url"] == mock_post_data["covers"][0] + + def test_transform_comment(self, mock_comment_data): + """Test the transformation of a TikTok comment.""" + # Import and patch the transform_comment function directly + from app.processing.collection.tiktok import transform_comment + + # Create a fake post ID + post_id = str(uuid.uuid4()) + + # Transform the comment data + transformed = transform_comment(mock_comment_data, post_id) + + # Check if transformation matches expectations for MongoDB schema + assert transformed["platform_id"] == mock_comment_data["id"] + assert transformed["platform"] == "tiktok" + assert transformed["post_id"] == post_id + assert transformed["user_id"] == mock_comment_data["user"]["id"] + assert transformed["user_name"] == mock_comment_data["user"]["uniqueId"] + assert transformed["user_full_name"] == mock_comment_data["user"]["nickname"] + assert transformed["user_profile_pic"] == mock_comment_data["user"]["avatarThumb"] + assert transformed["user_verified"] == mock_comment_data["user"]["verified"] + + # Check content + assert transformed["content"]["text"] == mock_comment_data["text"] + + # Check metadata + assert isinstance(transformed["metadata"]["created_at"], datetime) + assert transformed["metadata"]["is_reply"] == mock_comment_data["isReply"] + + # Check engagement + assert transformed["engagement"]["likes_count"] == mock_comment_data["diggCount"] + assert transformed["engagement"]["replies_count"] == mock_comment_data["replyCount"] + + # Check replies + assert len(transformed["replies"]) == len(mock_comment_data["replies"]) + assert transformed["replies"][0]["platform_id"] == mock_comment_data["replies"][0]["id"] + assert transformed["replies"][0]["text"] == mock_comment_data["replies"][0]["text"] + + +if __name__ == "__main__": + pytest.main(["-xvs", __file__]) \ No newline at end of file diff --git a/backend/app/testing/test_transform_twitter.py b/backend/app/testing/test_transform_twitter.py new file mode 100644 index 0000000000..a79d44275c --- /dev/null +++ b/backend/app/testing/test_transform_twitter.py @@ -0,0 +1,226 @@ +""" +Tests for Twitter/X data transformer + +This module tests the transformation of Twitter/X raw data from APIFY +to the format expected by the application's repositories. +""" + +import json +import os +import sys +import uuid +from datetime import datetime +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +# Add the project root to the Python path +sys.path.append(str(Path(__file__).parent.parent.parent)) + +from app.db.models.social_media_account import Platform + + +class TestTwitterTransforms: + """ + Test the transformation of Twitter/X data from APIFY to the application format. + """ + + @pytest.fixture + def sample_profile_data(self): + """Load sample Twitter/X profile data.""" + data_path = Path(__file__).parent / "data" / "x" / "profile_samples.json" + with open(data_path, "r", encoding="utf-8") as f: + return json.load(f) + + @pytest.fixture + def sample_post_data(self): + """Load sample Twitter/X post data.""" + data_path = Path(__file__).parent / "data" / "x" / "post_samples.json" + with open(data_path, "r", encoding="utf-8") as f: + return json.load(f) + + @pytest.fixture + def sample_comment_data(self): + """Load sample Twitter/X comment data.""" + data_path = Path(__file__).parent / "data" / "x" / "comment_samples.json" + with open(data_path, "r", encoding="utf-8") as f: + return json.load(f) + + def test_transform_profile(self, sample_profile_data): + """Test the transformation of a Twitter/X profile by manually creating the transform.""" + # Get the first profile from the sample data - author field in a post contains profile data + raw_profile = sample_profile_data[0]["author"] + + # Manual transformation (same logic as in the collector) + transformed = { + "platform": Platform.TWITTER, + "platform_id": raw_profile["id"], + "handle": raw_profile["userName"], + "name": raw_profile["name"], + "url": f"https://twitter.com/{raw_profile['userName']}", + "verified": raw_profile["isVerified"] or raw_profile["isBlueVerified"], + "follower_count": raw_profile["followers"], + "following_count": raw_profile["following"] + } + + # Check if transformation matches expectations for social_media_account.py + assert transformed["platform"] == Platform.TWITTER + assert transformed["platform_id"] == raw_profile["id"] + assert transformed["handle"] == raw_profile["userName"] + assert transformed["name"] == raw_profile["name"] + assert transformed["url"] == f"https://twitter.com/{raw_profile['userName']}" + assert isinstance(transformed["follower_count"], int) + assert isinstance(transformed["following_count"], int) + + # Ensure political_entity_id is not included (should be set when account is created) + assert "political_entity_id" not in transformed + + def test_transform_post(self, sample_post_data): + """Test the transformation of a Twitter/X post by manually validating key fields.""" + # Get the first post from the sample data + raw_post = sample_post_data[0] + + # Create a fake account ID + account_id = str(uuid.uuid4()) + + # Check if key fields are present in the raw post + assert "id" in raw_post + assert "text" in raw_post + assert "url" in raw_post + assert "likeCount" in raw_post + assert "retweetCount" in raw_post + assert "replyCount" in raw_post + assert "createdAt" in raw_post + + # Validate expected field types and structures + assert isinstance(raw_post["id"], str) + assert isinstance(raw_post["text"], str) + assert isinstance(raw_post["url"], str) + assert isinstance(raw_post["likeCount"], int) + assert isinstance(raw_post["retweetCount"], int) + assert isinstance(raw_post["replyCount"], int) + + # Parse created_at date + created_at = None + try: + # Try the format with year + created_at = datetime.strptime( + raw_post["createdAt"].split("+")[0].strip(), + "%a %b %d %H:%M:%S %Y" + ) + except ValueError: + # If that fails, assume the current year + date_str = raw_post["createdAt"].split("+")[0].strip() + created_at = datetime.strptime( + date_str, + "%a %b %d %H:%M:%S" + ).replace(year=2025) # Sample data uses 2025 as the year + + # Test MongoDB schema compatibility + # These are the key fields needed by the MongoDB schema + required_fields = { + "platform_id": raw_post["id"], + "platform": "twitter", + "account_id": account_id, + "content_type": "retweet" if raw_post.get("isRetweet", False) else "post", + "content": { + "text": raw_post["text"] + }, + "metadata": { + "created_at": created_at, + "language": raw_post["lang"], + "is_repost": raw_post.get("isRetweet", False), + "is_reply": raw_post.get("isReply", False) + }, + "engagement": { + "likes_count": raw_post["likeCount"], + "shares_count": raw_post["retweetCount"], + "comments_count": raw_post["replyCount"], + "views_count": raw_post.get("viewCount") + } + } + + # Check if media is extracted correctly + if "extendedEntities" in raw_post and "media" in raw_post["extendedEntities"]: + media_items = raw_post["extendedEntities"]["media"] + assert len(media_items) > 0 + assert "media_url_https" in media_items[0] + + # All required fields should be present in raw data + for field, value in required_fields.items(): + if field in ["content", "metadata", "engagement"]: + # These are nested fields, continue + continue + assert value is not None, f"Field {field} should not be None" + + def test_transform_comment(self, sample_comment_data): + """Test the transformation of a Twitter/X comment by manually validating key fields.""" + # Get the first comment from the sample data + raw_comment = sample_comment_data[0] + + # Create a fake post ID + post_id = "fake_post_id" + + # Check if key fields are present in the raw comment + assert "id" in raw_comment + assert "text" in raw_comment + assert "author" in raw_comment + assert "createdAt" in raw_comment + assert "likeCount" in raw_comment + assert "replyCount" in raw_comment + + # Validate expected field types and structures + assert isinstance(raw_comment["id"], str) + assert isinstance(raw_comment["text"], str) + assert isinstance(raw_comment["author"], dict) + assert isinstance(raw_comment["likeCount"], int) + assert isinstance(raw_comment["replyCount"], int) + + # Parse created_at date + created_at = None + try: + # Try the format with year + created_at = datetime.strptime( + raw_comment["createdAt"].split("+")[0].strip(), + "%a %b %d %H:%M:%S %Y" + ) + except ValueError: + # If that fails, assume the current year + date_str = raw_comment["createdAt"].split("+")[0].strip() + created_at = datetime.strptime( + date_str, + "%a %b %d %H:%M:%S" + ).replace(year=2025) # Sample data uses 2025 as the year + + # Test MongoDB schema compatibility + # These are the key fields needed by the MongoDB schema + required_fields = { + "platform_id": raw_comment["id"], + "platform": "twitter", + "post_id": post_id, + "user_id": raw_comment["author"]["id"], + "user_name": raw_comment["author"]["userName"], + "content": { + "text": raw_comment["text"] + }, + "metadata": { + "created_at": created_at, + "language": raw_comment["lang"] + }, + "engagement": { + "likes_count": raw_comment["likeCount"], + "replies_count": raw_comment["replyCount"] + } + } + + # All required fields should be present in raw data + for field, value in required_fields.items(): + if field in ["content", "metadata", "engagement"]: + # These are nested fields, continue + continue + assert value is not None, f"Field {field} should not be None" + + +if __name__ == "__main__": + pytest.main(["-xvs", __file__]) \ No newline at end of file diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 78cc61ccae..bd8b93f469 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ "motor==3.3.2", # MongoDB async driver (fixed version for compatibility) "pymongo==4.5.0", # MongoDB sync driver (fixed version for compatibility) "redis<5.0.0,>=4.6.0", # Redis client - "pinecone-client==2.2.1", # Pinecone vector DB client (fixed at 2.2.1) + "pinecone>=6.0.0,<7.0.0", # Pinecone vector DB (use the official package name) # Task Processing "celery<6.0.0,>=5.3.0", # Task queue @@ -43,6 +43,15 @@ dependencies = [ # "torch>=2.0.0,<3.0.0", # PyTorch - install separately if needed "numpy<2.0.0,>=1.24.0", # Required for ML operations "pandas<2.0.0,>=1.5.3", # Data processing + "openai<2.0.0,>=1.6.0", # OpenAI API client + "backoff<2.0.0,>=1.11.0", # For API retries + + # External Service Clients (MVP) + "apify-client>=1.1.0", # APIFY client for web scraping + "anthropic>=0.5.0", # Anthropic (Claude) client for LLM analysis + + # Optional - not used in MVP + # "redis-py-cluster>=2.1.3; python_version >= '3.8'" ] [tool.uv] diff --git a/backend/scripts/prestart.sh b/backend/scripts/prestart.sh index 1b395d513f..2e0fd67e2f 100644 --- a/backend/scripts/prestart.sh +++ b/backend/scripts/prestart.sh @@ -7,7 +7,7 @@ set -x python app/backend_pre_start.py # Run migrations -alembic upgrade head +python -m alembic upgrade head # Create initial data in DB python app/initial_data.py diff --git a/backend/uv.lock b/backend/uv.lock index daf6263620..a2df35591e 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -111,6 +111,7 @@ dependencies = [ { name = "jinja2" }, { name = "motor" }, { name = "numpy" }, + { name = "openai" }, { name = "pandas" }, { name = "passlib", extra = ["bcrypt"] }, { name = "pika" }, @@ -156,10 +157,11 @@ requires-dist = [ { name = "jinja2", specifier = ">=3.1.4,<4.0.0" }, { name = "motor", specifier = "==3.3.2" }, { name = "numpy", specifier = ">=1.24.0,<2.0.0" }, + { name = "openai", specifier = ">=1.6.0,<2.0.0" }, { name = "pandas", specifier = ">=1.5.3,<2.0.0" }, { name = "passlib", extras = ["bcrypt"], specifier = ">=1.7.4,<2.0.0" }, { name = "pika", specifier = ">=1.3.2,<2.0.0" }, - { name = "pinecone-client", specifier = ">=2.2.1,<3.0.0" }, + { name = "pinecone-client", specifier = "==2.2.1" }, { name = "psycopg", extras = ["binary"], specifier = ">=3.1.13,<4.0.0" }, { name = "pydantic", specifier = ">2.0" }, { name = "pydantic-settings", specifier = ">=2.2.1,<3.0.0" }, @@ -582,6 +584,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/41/9307e4f5f9976bc8b7fea0b66367734e8faf3ec84bc0d412d8cfabbb66cd/distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784", size = 468850 }, ] +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, +] + [[package]] name = "dnspython" version = "2.6.1" @@ -890,6 +901,65 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, ] +[[package]] +name = "jiter" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/c2/e4562507f52f0af7036da125bb699602ead37a2332af0788f8e0a3417f36/jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893", size = 162604 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/82/39f7c9e67b3b0121f02a0b90d433626caa95a565c3d2449fea6bcfa3f5f5/jiter-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:816ec9b60fdfd1fec87da1d7ed46c66c44ffec37ab2ef7de5b147b2fce3fd5ad", size = 314540 }, + { url = "https://files.pythonhosted.org/packages/01/07/7bf6022c5a152fca767cf5c086bb41f7c28f70cf33ad259d023b53c0b858/jiter-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b1d3086f8a3ee0194ecf2008cf81286a5c3e540d977fa038ff23576c023c0ea", size = 321065 }, + { url = "https://files.pythonhosted.org/packages/6c/b2/de3f3446ecba7c48f317568e111cc112613da36c7b29a6de45a1df365556/jiter-0.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1339f839b91ae30b37c409bf16ccd3dc453e8b8c3ed4bd1d6a567193651a4a51", size = 341664 }, + { url = "https://files.pythonhosted.org/packages/13/cf/6485a4012af5d407689c91296105fcdb080a3538e0658d2abf679619c72f/jiter-0.9.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ffba79584b3b670fefae66ceb3a28822365d25b7bf811e030609a3d5b876f538", size = 364635 }, + { url = "https://files.pythonhosted.org/packages/0d/f7/4a491c568f005553240b486f8e05c82547340572d5018ef79414b4449327/jiter-0.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cfc7d0a8e899089d11f065e289cb5b2daf3d82fbe028f49b20d7b809193958d", size = 406288 }, + { url = "https://files.pythonhosted.org/packages/d3/ca/f4263ecbce7f5e6bded8f52a9f1a66540b270c300b5c9f5353d163f9ac61/jiter-0.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e00a1a2bbfaaf237e13c3d1592356eab3e9015d7efd59359ac8b51eb56390a12", size = 397499 }, + { url = "https://files.pythonhosted.org/packages/ac/a2/522039e522a10bac2f2194f50e183a49a360d5f63ebf46f6d890ef8aa3f9/jiter-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1d9870561eb26b11448854dce0ff27a9a27cb616b632468cafc938de25e9e51", size = 352926 }, + { url = "https://files.pythonhosted.org/packages/b1/67/306a5c5abc82f2e32bd47333a1c9799499c1c3a415f8dde19dbf876f00cb/jiter-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9872aeff3f21e437651df378cb75aeb7043e5297261222b6441a620218b58708", size = 384506 }, + { url = "https://files.pythonhosted.org/packages/0f/89/c12fe7b65a4fb74f6c0d7b5119576f1f16c79fc2953641f31b288fad8a04/jiter-0.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1fd19112d1049bdd47f17bfbb44a2c0001061312dcf0e72765bfa8abd4aa30e5", size = 520621 }, + { url = "https://files.pythonhosted.org/packages/c4/2b/d57900c5c06e6273fbaa76a19efa74dbc6e70c7427ab421bf0095dfe5d4a/jiter-0.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6ef5da104664e526836070e4a23b5f68dec1cc673b60bf1edb1bfbe8a55d0678", size = 512613 }, + { url = "https://files.pythonhosted.org/packages/89/05/d8b90bfb21e58097d5a4e0224f2940568366f68488a079ae77d4b2653500/jiter-0.9.0-cp310-cp310-win32.whl", hash = "sha256:cb12e6d65ebbefe5518de819f3eda53b73187b7089040b2d17f5b39001ff31c4", size = 206613 }, + { url = "https://files.pythonhosted.org/packages/2c/1d/5767f23f88e4f885090d74bbd2755518050a63040c0f59aa059947035711/jiter-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:c43ca669493626d8672be3b645dbb406ef25af3f4b6384cfd306da7eb2e70322", size = 208371 }, + { url = "https://files.pythonhosted.org/packages/23/44/e241a043f114299254e44d7e777ead311da400517f179665e59611ab0ee4/jiter-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6c4d99c71508912a7e556d631768dcdef43648a93660670986916b297f1c54af", size = 314654 }, + { url = "https://files.pythonhosted.org/packages/fb/1b/a7e5e42db9fa262baaa9489d8d14ca93f8663e7f164ed5e9acc9f467fc00/jiter-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f60fb8ce7df529812bf6c625635a19d27f30806885139e367af93f6e734ef58", size = 320909 }, + { url = "https://files.pythonhosted.org/packages/60/bf/8ebdfce77bc04b81abf2ea316e9c03b4a866a7d739cf355eae4d6fd9f6fe/jiter-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c4e1a4f8ea84d98b7b98912aa4290ac3d1eabfde8e3c34541fae30e9d1f08b", size = 341733 }, + { url = "https://files.pythonhosted.org/packages/a8/4e/754ebce77cff9ab34d1d0fa0fe98f5d42590fd33622509a3ba6ec37ff466/jiter-0.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f4c677c424dc76684fea3e7285a7a2a7493424bea89ac441045e6a1fb1d7b3b", size = 365097 }, + { url = "https://files.pythonhosted.org/packages/32/2c/6019587e6f5844c612ae18ca892f4cd7b3d8bbf49461ed29e384a0f13d98/jiter-0.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2221176dfec87f3470b21e6abca056e6b04ce9bff72315cb0b243ca9e835a4b5", size = 406603 }, + { url = "https://files.pythonhosted.org/packages/da/e9/c9e6546c817ab75a1a7dab6dcc698e62e375e1017113e8e983fccbd56115/jiter-0.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c7adb66f899ffa25e3c92bfcb593391ee1947dbdd6a9a970e0d7e713237d572", size = 396625 }, + { url = "https://files.pythonhosted.org/packages/be/bd/976b458add04271ebb5a255e992bd008546ea04bb4dcadc042a16279b4b4/jiter-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98d27330fdfb77913c1097a7aab07f38ff2259048949f499c9901700789ac15", size = 351832 }, + { url = "https://files.pythonhosted.org/packages/07/51/fe59e307aaebec9265dbad44d9d4381d030947e47b0f23531579b9a7c2df/jiter-0.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eda3f8cc74df66892b1d06b5d41a71670c22d95a1ca2cbab73654745ce9d0419", size = 384590 }, + { url = "https://files.pythonhosted.org/packages/db/55/5dcd2693794d8e6f4889389ff66ef3be557a77f8aeeca8973a97a7c00557/jiter-0.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd5ab5ddc11418dce28343123644a100f487eaccf1de27a459ab36d6cca31043", size = 520690 }, + { url = "https://files.pythonhosted.org/packages/54/d5/9f51dc90985e9eb251fbbb747ab2b13b26601f16c595a7b8baba964043bd/jiter-0.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42f8a68a69f047b310319ef8e2f52fdb2e7976fb3313ef27df495cf77bcad965", size = 512649 }, + { url = "https://files.pythonhosted.org/packages/a6/e5/4e385945179bcf128fa10ad8dca9053d717cbe09e258110e39045c881fe5/jiter-0.9.0-cp311-cp311-win32.whl", hash = "sha256:a25519efb78a42254d59326ee417d6f5161b06f5da827d94cf521fed961b1ff2", size = 206920 }, + { url = "https://files.pythonhosted.org/packages/4c/47/5e0b94c603d8e54dd1faab439b40b832c277d3b90743e7835879ab663757/jiter-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:923b54afdd697dfd00d368b7ccad008cccfeb1efb4e621f32860c75e9f25edbd", size = 210119 }, + { url = "https://files.pythonhosted.org/packages/af/d7/c55086103d6f29b694ec79156242304adf521577530d9031317ce5338c59/jiter-0.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7b46249cfd6c48da28f89eb0be3f52d6fdb40ab88e2c66804f546674e539ec11", size = 309203 }, + { url = "https://files.pythonhosted.org/packages/b0/01/f775dfee50beb420adfd6baf58d1c4d437de41c9b666ddf127c065e5a488/jiter-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:609cf3c78852f1189894383cf0b0b977665f54cb38788e3e6b941fa6d982c00e", size = 319678 }, + { url = "https://files.pythonhosted.org/packages/ab/b8/09b73a793714726893e5d46d5c534a63709261af3d24444ad07885ce87cb/jiter-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d726a3890a54561e55a9c5faea1f7655eda7f105bd165067575ace6e65f80bb2", size = 341816 }, + { url = "https://files.pythonhosted.org/packages/35/6f/b8f89ec5398b2b0d344257138182cc090302854ed63ed9c9051e9c673441/jiter-0.9.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e89dc075c1fef8fa9be219e249f14040270dbc507df4215c324a1839522ea75", size = 364152 }, + { url = "https://files.pythonhosted.org/packages/9b/ca/978cc3183113b8e4484cc7e210a9ad3c6614396e7abd5407ea8aa1458eef/jiter-0.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04e8ffa3c353b1bc4134f96f167a2082494351e42888dfcf06e944f2729cbe1d", size = 406991 }, + { url = "https://files.pythonhosted.org/packages/13/3a/72861883e11a36d6aa314b4922125f6ae90bdccc225cd96d24cc78a66385/jiter-0.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:203f28a72a05ae0e129b3ed1f75f56bc419d5f91dfacd057519a8bd137b00c42", size = 395824 }, + { url = "https://files.pythonhosted.org/packages/87/67/22728a86ef53589c3720225778f7c5fdb617080e3deaed58b04789418212/jiter-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fca1a02ad60ec30bb230f65bc01f611c8608b02d269f998bc29cca8619a919dc", size = 351318 }, + { url = "https://files.pythonhosted.org/packages/69/b9/f39728e2e2007276806d7a6609cda7fac44ffa28ca0d02c49a4f397cc0d9/jiter-0.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:237e5cee4d5d2659aaf91bbf8ec45052cc217d9446070699441a91b386ae27dc", size = 384591 }, + { url = "https://files.pythonhosted.org/packages/eb/8f/8a708bc7fd87b8a5d861f1c118a995eccbe6d672fe10c9753e67362d0dd0/jiter-0.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:528b6b71745e7326eed73c53d4aa57e2a522242320b6f7d65b9c5af83cf49b6e", size = 520746 }, + { url = "https://files.pythonhosted.org/packages/95/1e/65680c7488bd2365dbd2980adaf63c562d3d41d3faac192ebc7ef5b4ae25/jiter-0.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9f48e86b57bc711eb5acdfd12b6cb580a59cc9a993f6e7dcb6d8b50522dcd50d", size = 512754 }, + { url = "https://files.pythonhosted.org/packages/78/f3/fdc43547a9ee6e93c837685da704fb6da7dba311fc022e2766d5277dfde5/jiter-0.9.0-cp312-cp312-win32.whl", hash = "sha256:699edfde481e191d81f9cf6d2211debbfe4bd92f06410e7637dffb8dd5dfde06", size = 207075 }, + { url = "https://files.pythonhosted.org/packages/cd/9d/742b289016d155f49028fe1bfbeb935c9bf0ffeefdf77daf4a63a42bb72b/jiter-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:099500d07b43f61d8bd780466d429c45a7b25411b334c60ca875fa775f68ccb0", size = 207999 }, + { url = "https://files.pythonhosted.org/packages/e7/1b/4cd165c362e8f2f520fdb43245e2b414f42a255921248b4f8b9c8d871ff1/jiter-0.9.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2764891d3f3e8b18dce2cff24949153ee30c9239da7c00f032511091ba688ff7", size = 308197 }, + { url = "https://files.pythonhosted.org/packages/13/aa/7a890dfe29c84c9a82064a9fe36079c7c0309c91b70c380dc138f9bea44a/jiter-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:387b22fbfd7a62418d5212b4638026d01723761c75c1c8232a8b8c37c2f1003b", size = 318160 }, + { url = "https://files.pythonhosted.org/packages/6a/38/5888b43fc01102f733f085673c4f0be5a298f69808ec63de55051754e390/jiter-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d8da8629ccae3606c61d9184970423655fb4e33d03330bcdfe52d234d32f69", size = 341259 }, + { url = "https://files.pythonhosted.org/packages/3d/5e/bbdbb63305bcc01006de683b6228cd061458b9b7bb9b8d9bc348a58e5dc2/jiter-0.9.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1be73d8982bdc278b7b9377426a4b44ceb5c7952073dd7488e4ae96b88e1103", size = 363730 }, + { url = "https://files.pythonhosted.org/packages/75/85/53a3edc616992fe4af6814c25f91ee3b1e22f7678e979b6ea82d3bc0667e/jiter-0.9.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2228eaaaa111ec54b9e89f7481bffb3972e9059301a878d085b2b449fbbde635", size = 405126 }, + { url = "https://files.pythonhosted.org/packages/ae/b3/1ee26b12b2693bd3f0b71d3188e4e5d817b12e3c630a09e099e0a89e28fa/jiter-0.9.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11509bfecbc319459647d4ac3fd391d26fdf530dad00c13c4dadabf5b81f01a4", size = 393668 }, + { url = "https://files.pythonhosted.org/packages/11/87/e084ce261950c1861773ab534d49127d1517b629478304d328493f980791/jiter-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f22238da568be8bbd8e0650e12feeb2cfea15eda4f9fc271d3b362a4fa0604d", size = 352350 }, + { url = "https://files.pythonhosted.org/packages/f0/06/7dca84b04987e9df563610aa0bc154ea176e50358af532ab40ffb87434df/jiter-0.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17f5d55eb856597607562257c8e36c42bc87f16bef52ef7129b7da11afc779f3", size = 384204 }, + { url = "https://files.pythonhosted.org/packages/16/2f/82e1c6020db72f397dd070eec0c85ebc4df7c88967bc86d3ce9864148f28/jiter-0.9.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6a99bed9fbb02f5bed416d137944419a69aa4c423e44189bc49718859ea83bc5", size = 520322 }, + { url = "https://files.pythonhosted.org/packages/36/fd/4f0cd3abe83ce208991ca61e7e5df915aa35b67f1c0633eb7cf2f2e88ec7/jiter-0.9.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e057adb0cd1bd39606100be0eafe742de2de88c79df632955b9ab53a086b3c8d", size = 512184 }, + { url = "https://files.pythonhosted.org/packages/a0/3c/8a56f6d547731a0b4410a2d9d16bf39c861046f91f57c98f7cab3d2aa9ce/jiter-0.9.0-cp313-cp313-win32.whl", hash = "sha256:f7e6850991f3940f62d387ccfa54d1a92bd4bb9f89690b53aea36b4364bcab53", size = 206504 }, + { url = "https://files.pythonhosted.org/packages/f4/1c/0c996fd90639acda75ed7fa698ee5fd7d80243057185dc2f63d4c1c9f6b9/jiter-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:c8ae3bf27cd1ac5e6e8b7a27487bf3ab5f82318211ec2e1346a5b058756361f7", size = 204943 }, + { url = "https://files.pythonhosted.org/packages/78/0f/77a63ca7aa5fed9a1b9135af57e190d905bcd3702b36aca46a01090d39ad/jiter-0.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0b2827fb88dda2cbecbbc3e596ef08d69bda06c6f57930aec8e79505dc17001", size = 317281 }, + { url = "https://files.pythonhosted.org/packages/f9/39/a3a1571712c2bf6ec4c657f0d66da114a63a2e32b7e4eb8e0b83295ee034/jiter-0.9.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062b756ceb1d40b0b28f326cba26cfd575a4918415b036464a52f08632731e5a", size = 350273 }, + { url = "https://files.pythonhosted.org/packages/ee/47/3729f00f35a696e68da15d64eb9283c330e776f3b5789bac7f2c0c4df209/jiter-0.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6f7838bc467ab7e8ef9f387bd6de195c43bad82a569c1699cb822f6609dd4cdf", size = 206867 }, +] + [[package]] name = "joblib" version = "1.4.2" @@ -1441,6 +1511,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144 }, ] +[[package]] +name = "openai" +version = "1.68.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/6b/6b002d5d38794645437ae3ddb42083059d556558493408d39a0fcea608bc/openai-1.68.2.tar.gz", hash = "sha256:b720f0a95a1dbe1429c0d9bb62096a0d98057bcda82516f6e8af10284bdd5b19", size = 413429 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/34/cebce15f64eb4a3d609a83ac3568d43005cc9a1cba9d7fde5590fd415423/openai-1.68.2-py3-none-any.whl", hash = "sha256:24484cb5c9a33b58576fdc5acf0e5f92603024a4e39d0b99793dfa1eb14c2b36", size = 606073 }, +] + [[package]] name = "packaging" version = "24.1" @@ -1567,7 +1656,7 @@ wheels = [ [[package]] name = "pinecone-client" -version = "2.2.4" +version = "2.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dnspython" }, @@ -1580,9 +1669,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/fd/893821aa47ff69925f378a42a2deedb3285a8097c0886e0de564bb700891/pinecone-client-2.2.4.tar.gz", hash = "sha256:2c1cc1d6648b2be66e944db2ffa59166a37b9164d1135ad525d9cd8b1e298168", size = 96565 } +sdist = { url = "https://files.pythonhosted.org/packages/25/af/2ea3b6f5498bc4618ad54b03ec00b4a3f2c4eda48839f119ca339984dc01/pinecone-client-2.2.1.tar.gz", hash = "sha256:0878dcaee447c46c8d1b3d71c854689daa7e548e5009a171780907c7d4e74789", size = 96153 } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/d4/cffbb61236c6c1d7510e835c1ff843e4e7d705ed59d21c0e5b6dc1cb4fd8/pinecone_client-2.2.4-py3-none-any.whl", hash = "sha256:5bf496c01c2f82f4e5c2dc977cc5062ecd7168b8ed90743b09afcc8c7eb242ec", size = 179357 }, + { url = "https://files.pythonhosted.org/packages/43/f2/a6b15f7fed393cd81757ed1bc0a5c080af840b8e563d2bd1f818893f91ee/pinecone_client-2.2.1-py3-none-any.whl", hash = "sha256:6976a22aee57a9813378607506c8c36b0317dfa36a08a5397aaaeab2eef66c1b", size = 177204 }, ] [[package]] diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 151fe7ac01..87fbc87d44 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -261,7 +261,7 @@ services: POSTGRES_PORT: "5432" POSTGRES_DB: "app" POSTGRES_USER: "postgres" - POSTGRES_PASSWORD: "changethis" + POSTGRES_PASSWORD: "postgres" FIRST_SUPERUSER: "admin@example.com" FIRST_SUPERUSER_PASSWORD: "password" depends_on: diff --git a/docker-compose.yml b/docker-compose.yml index aa71dabfe9..77f376a1cd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,85 +41,6 @@ services: retries: 5 start_period: 40s - # Redis service for caching and real-time operations - redis: - image: redis:7.0 - restart: always - networks: - - traefik-public - - default - ports: - - "6379:6379" - volumes: - - redis-data:/data - command: redis-server --appendonly yes - healthcheck: - test: [ "CMD", "redis-cli", "ping" ] - interval: 5s - timeout: 5s - retries: 5 - - # RabbitMQ service for message broker - rabbitmq: - image: rabbitmq:3.12-management - restart: always - networks: - - traefik-public - - default - ports: - - "5672:5672" # AMQP port - - "15672:15672" # Management UI - environment: - - RABBITMQ_DEFAULT_USER=${RABBITMQ_USER:-rabbitmquser} - - RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD:-rabbitmqpassword} - volumes: - - rabbitmq-data:/var/lib/rabbitmq - healthcheck: - test: [ "CMD", "rabbitmq-diagnostics", "check_port_connectivity" ] - interval: 10s - timeout: 5s - retries: 5 - start_period: 40s - - # Kafka service with Zookeeper for stream processing - zookeeper: - image: bitnami/zookeeper:latest - restart: always - networks: - - traefik-public - - default - ports: - - "2181:2181" - environment: - - ALLOW_ANONYMOUS_LOGIN=yes - volumes: - - zookeeper-data:/bitnami/zookeeper - - kafka: - image: bitnami/kafka:3.4 - restart: always - networks: - - traefik-public - - default - ports: - - "9092:9092" - environment: - - KAFKA_BROKER_ID=1 - - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092 - - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092 - - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 - - ALLOW_PLAINTEXT_LISTENER=yes - volumes: - - kafka-data:/bitnami/kafka - depends_on: - - zookeeper - healthcheck: - test: [ "CMD-SHELL", "kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --list" ] - interval: 10s - timeout: 5s - retries: 5 - start_period: 40s - adminer: image: adminer restart: always @@ -154,6 +75,9 @@ services: db: condition: service_healthy restart: true + mongodb: + condition: service_healthy + restart: true command: bash scripts/prestart.sh env_file: - .env @@ -175,16 +99,11 @@ services: - POSTGRES_USER=${POSTGRES_USER?Variable not set} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} - SENTRY_DSN=${SENTRY_DSN} - - MONGO_SERVER=mongodb - - MONGO_USER=${MONGO_USER:-mongouser} - - MONGO_PASSWORD=${MONGO_PASSWORD:-mongopassword} - - MONGO_DB=${MONGO_DB:-socialmediadb} - - REDIS_SERVER=redis - - REDIS_PORT=6379 - - RABBITMQ_SERVER=rabbitmq - - RABBITMQ_USER=${RABBITMQ_USER:-rabbitmquser} - - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD:-rabbitmqpassword} - - KAFKA_BOOTSTRAP_SERVERS=kafka:9092 + - MONGODB_SERVER=mongodb + - MONGODB_PORT=27017 + - MONGODB_DB=${MONGO_DB:-socialmediadb} + - MONGODB_USER=${MONGO_USER:-mongouser} + - MONGODB_PASSWORD=${MONGO_PASSWORD:-mongopassword} backend: image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}' @@ -199,12 +118,6 @@ services: mongodb: condition: service_healthy restart: true - redis: - condition: service_healthy - restart: true - rabbitmq: - condition: service_healthy - restart: true prestart: condition: service_completed_successfully env_file: @@ -227,19 +140,14 @@ services: - POSTGRES_USER=${POSTGRES_USER?Variable not set} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} - SENTRY_DSN=${SENTRY_DSN} - - MONGO_SERVER=mongodb - - MONGO_USER=${MONGO_USER:-mongouser} - - MONGO_PASSWORD=${MONGO_PASSWORD:-mongopassword} - - MONGO_DB=${MONGO_DB:-socialmediadb} - - REDIS_SERVER=redis - - REDIS_PORT=6379 - - RABBITMQ_SERVER=rabbitmq - - RABBITMQ_USER=${RABBITMQ_USER:-rabbitmquser} - - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD:-rabbitmqpassword} - - KAFKA_BOOTSTRAP_SERVERS=kafka:9092 + - MONGODB_SERVER=mongodb + - MONGODB_PORT=27017 + - MONGODB_DB=${MONGO_DB:-socialmediadb} + - MONGODB_USER=${MONGO_USER:-mongouser} + - MONGODB_PASSWORD=${MONGO_PASSWORD:-mongopassword} healthcheck: - test: [ "CMD", "curl", "-f", "http://localhost:8000/api/v1/utils/health-check/" ] + test: [ "CMD", "curl", "-f", "http://localhost:8000/health" ] interval: 10s timeout: 5s retries: 5 @@ -264,133 +172,10 @@ services: # Enable redirection for HTTP and HTTPS - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.middlewares=https-redirect - # Celery Worker for background task processing - celery-worker: - image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}' - restart: always - networks: - - traefik-public - - default - depends_on: - db: - condition: service_healthy - restart: true - mongodb: - condition: service_healthy - restart: true - redis: - condition: service_healthy - restart: true - rabbitmq: - condition: service_healthy - restart: true - command: celery -A app.tasks.worker worker --loglevel=info --concurrency=2 - env_file: - - .env - environment: - - DOMAIN=${DOMAIN} - - ENVIRONMENT=${ENVIRONMENT} - - SECRET_KEY=${SECRET_KEY?Variable not set} - - POSTGRES_SERVER=db - - POSTGRES_PORT=${POSTGRES_PORT} - - POSTGRES_DB=${POSTGRES_DB} - - POSTGRES_USER=${POSTGRES_USER?Variable not set} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} - - SENTRY_DSN=${SENTRY_DSN} - - MONGO_SERVER=mongodb - - MONGO_USER=${MONGO_USER:-mongouser} - - MONGO_PASSWORD=${MONGO_PASSWORD:-mongopassword} - - MONGO_DB=${MONGO_DB:-socialmediadb} - - REDIS_SERVER=redis - - REDIS_PORT=6379 - - RABBITMQ_SERVER=rabbitmq - - RABBITMQ_USER=${RABBITMQ_USER:-rabbitmquser} - - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD:-rabbitmqpassword} - - KAFKA_BOOTSTRAP_SERVERS=kafka:9092 - - C_FORCE_ROOT=true - - # Celery Beat for scheduled tasks - celery-beat: - image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}' - restart: always - networks: - - traefik-public - - default - depends_on: - db: - condition: service_healthy - restart: true - mongodb: - condition: service_healthy - restart: true - redis: - condition: service_healthy - restart: true - rabbitmq: - condition: service_healthy - restart: true - celery-worker: - condition: service_started - command: celery -A app.tasks.worker beat --loglevel=info - env_file: - - .env - environment: - - DOMAIN=${DOMAIN} - - ENVIRONMENT=${ENVIRONMENT} - - SECRET_KEY=${SECRET_KEY?Variable not set} - - POSTGRES_SERVER=db - - POSTGRES_PORT=${POSTGRES_PORT} - - POSTGRES_DB=${POSTGRES_DB} - - POSTGRES_USER=${POSTGRES_USER?Variable not set} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} - - SENTRY_DSN=${SENTRY_DSN} - - MONGO_SERVER=mongodb - - MONGO_USER=${MONGO_USER:-mongouser} - - MONGO_PASSWORD=${MONGO_PASSWORD:-mongopassword} - - MONGO_DB=${MONGO_DB:-socialmediadb} - - REDIS_SERVER=redis - - REDIS_PORT=6379 - - RABBITMQ_SERVER=rabbitmq - - RABBITMQ_USER=${RABBITMQ_USER:-rabbitmquser} - - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD:-rabbitmqpassword} - - KAFKA_BOOTSTRAP_SERVERS=kafka:9092 - - frontend: - image: '${DOCKER_IMAGE_FRONTEND?Variable not set}:${TAG-latest}' - restart: always - networks: - - traefik-public - - default - build: - context: ./frontend - args: - - VITE_API_URL=https://api.${DOMAIN?Variable not set} - - NODE_ENV=production - labels: - - traefik.enable=true - - traefik.docker.network=traefik-public - - traefik.constraint-label=traefik-public - - - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 - - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`dashboard.${DOMAIN?Variable not set}`) - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.entrypoints=http - - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.rule=Host(`dashboard.${DOMAIN?Variable not set}`) - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.entrypoints=https - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls=true - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls.certresolver=le - - # Enable redirection for HTTP and HTTPS - - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.middlewares=https-redirect - volumes: app-db-data: mongodb-data: - redis-data: - rabbitmq-data: - zookeeper-data: - kafka-data: + traefik-public-certificates: networks: