14 socials integration #34
@@ -0,0 +1,22 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def unified_timestamp(timestamp_ms: int | None = None, timestamp_s: int | None = None) -> str:
|
||||||
|
"""
|
||||||
|
Transform the timestamp from milliseconds or seconds to a unified string format.
|
||||||
|
The resulting string is a formatted string 'YYYY-MM-DD HH:MM'.
|
||||||
|
Args:
|
||||||
|
timestamp_ms: Timestamp in milliseconds.
|
||||||
|
timestamp_s: Timestamp in seconds.
|
||||||
|
Raises:
|
||||||
|
ValueError: If neither timestamp_ms nor timestamp_s is provided.
|
||||||
|
"""
|
||||||
|
if timestamp_ms is not None:
|
||||||
|
timestamp = timestamp_ms // 1000
|
||||||
|
elif timestamp_s is not None:
|
||||||
|
timestamp = timestamp_s
|
||||||
|
else:
|
||||||
|
raise ValueError("Either timestamp_ms or timestamp_s must be provided")
|
||||||
|
assert timestamp > 0, "Invalid timestamp data received"
|
||||||
|
|
||||||
|
return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M')
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import statistics
|
import statistics
|
||||||
from datetime import datetime
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from app.api.core import unified_timestamp
|
||||||
|
|
||||||
|
|
||||||
class ProductInfo(BaseModel):
|
class ProductInfo(BaseModel):
|
||||||
@@ -64,24 +64,8 @@ class Price(BaseModel):
|
|||||||
"""Timestamp in format YYYY-MM-DD HH:MM"""
|
"""Timestamp in format YYYY-MM-DD HH:MM"""
|
||||||
|
|
||||||
def set_timestamp(self, timestamp_ms: int | None = None, timestamp_s: int | None = None) -> None:
|
def set_timestamp(self, timestamp_ms: int | None = None, timestamp_s: int | None = None) -> None:
|
||||||
"""
|
""" Use the unified_timestamp function to set the timestamp."""
|
||||||
Sets the timestamp from milliseconds or seconds.
|
self.timestamp = unified_timestamp(timestamp_ms, timestamp_s)
|
||||||
The timestamp is saved as a formatted string 'YYYY-MM-DD HH:MM'.
|
|
||||||
Args:
|
|
||||||
timestamp_ms: Timestamp in milliseconds.
|
|
||||||
timestamp_s: Timestamp in seconds.
|
|
||||||
Raises:
|
|
||||||
ValueError: If neither timestamp_ms nor timestamp_s is provided.
|
|
||||||
"""
|
|
||||||
if timestamp_ms is not None:
|
|
||||||
timestamp = timestamp_ms // 1000
|
|
||||||
elif timestamp_s is not None:
|
|
||||||
timestamp = timestamp_s
|
|
||||||
else:
|
|
||||||
raise ValueError("Either timestamp_ms or timestamp_s must be provided")
|
|
||||||
assert timestamp > 0, "Invalid timestamp data received"
|
|
||||||
|
|
||||||
self.timestamp = datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M')
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def aggregate(prices: dict[str, list['Price']]) -> list['Price']:
|
def aggregate(prices: dict[str, list['Price']]) -> list['Price']:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from app.api.core import unified_timestamp
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -13,6 +14,10 @@ class SocialPost(BaseModel):
|
|||||||
description: str = ""
|
description: str = ""
|
||||||
comments: list["SocialComment"] = []
|
comments: list["SocialComment"] = []
|
||||||
|
|
||||||
|
def set_timestamp(self, timestamp_ms: int | None = None, timestamp_s: int | None = None) -> None:
|
||||||
|
""" Use the unified_timestamp function to set the time."""
|
||||||
|
self.time = unified_timestamp(timestamp_ms, timestamp_s)
|
||||||
|
|
||||||
class SocialComment(BaseModel):
|
class SocialComment(BaseModel):
|
||||||
"""
|
"""
|
||||||
Represents a comment on a social media post.
|
Represents a comment on a social media post.
|
||||||
@@ -20,6 +25,10 @@ class SocialComment(BaseModel):
|
|||||||
time: str = ""
|
time: str = ""
|
||||||
description: str = ""
|
description: str = ""
|
||||||
|
|
||||||
|
def set_timestamp(self, timestamp_ms: int | None = None, timestamp_s: int | None = None) -> None:
|
||||||
|
""" Use the unified_timestamp function to set the time."""
|
||||||
|
self.time = unified_timestamp(timestamp_ms, timestamp_s)
|
||||||
|
|
||||||
|
|
||||||
class SocialWrapper:
|
class SocialWrapper:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import re
|
|||||||
|
|
|||||||
import html
|
import html
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from datetime import datetime
|
||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
from app.api.core.social import *
|
from app.api.core.social import *
|
||||||
|
|
||||||
|
|
||||||
@@ -12,14 +13,10 @@ class ChanWrapper(SocialWrapper):
|
|||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def __time_str(self, timestamp: str) -> str:
|
def __time_str(self, timestamp: str) -> int:
|
||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
"""Converte una stringa da MM/GG/AA di timestamp nel formato GG/MM/AA"""
|
"""Converte una stringa da MM/GG/AA(DAY)HH:MM:SS di 4chan a millisecondi"""
|
||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
if len(timestamp) < 8: return ""
|
time = datetime.strptime(timestamp, "%m/%d/%y(%a)%H:%M:%S")
|
||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
|
return int(time.timestamp() * 1000)
|
||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
month = timestamp[:2]
|
|
||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
day = timestamp[3:5]
|
|
||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
year = timestamp[6:8]
|
|
||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
return f"{day}/{month}/{year}"
|
|
||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
|
|
||||||
def __unformat_html_str(self, html_element: str) -> str:
|
def __unformat_html_str(self, html_element: str) -> str:
|
||||||
"""Pulisce il commento rimuovendo HTML e formattazioni inutili"""
|
"""Pulisce il commento rimuovendo HTML e formattazioni inutili"""
|
||||||
@@ -78,15 +75,16 @@ class ChanWrapper(SocialWrapper):
|
|||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
if not comment:
|
if not comment:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
social_comment = SocialComment(time=time, description=comment)
|
social_comment = SocialComment(description=comment)
|
||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
|
social_comment.set_timestamp(timestamp_ms=time)
|
||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
comments_list.append(social_comment)
|
comments_list.append(social_comment)
|
||||||
|
|
||||||
social_post: SocialPost = SocialPost(
|
social_post: SocialPost = SocialPost(
|
||||||
time=time,
|
|
||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
title=title,
|
title=title,
|
||||||
description=thread_description,
|
description=thread_description,
|
||||||
comments=comments_list
|
comments=comments_list
|
||||||
)
|
)
|
||||||
|
social_post.set_timestamp(timestamp_ms=time)
|
||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
social_posts.append(social_post)
|
social_posts.append(social_post)
|
||||||
|
|
||||||
return social_posts[:limit]
|
return social_posts[:limit]
|
||||||
|
|||||||
|
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
|
|||||||
@@ -23,13 +23,13 @@ SUBREDDITS = [
|
|||||||
|
|
||||||
def extract_post(post: Submission) -> SocialPost:
|
def extract_post(post: Submission) -> SocialPost:
|
||||||
social = SocialPost()
|
social = SocialPost()
|
||||||
social.time = str(post.created)
|
social.set_timestamp(timestamp_ms=post.created)
|
||||||
social.title = post.title
|
social.title = post.title
|
||||||
social.description = post.selftext
|
social.description = post.selftext
|
||||||
|
|
||||||
for top_comment in post.comments:
|
for top_comment in post.comments:
|
||||||
comment = SocialComment()
|
comment = SocialComment()
|
||||||
comment.time = str(top_comment.created)
|
comment.set_timestamp(timestamp_ms=top_comment.created)
|
||||||
comment.description = top_comment.body
|
comment.description = top_comment.body
|
||||||
social.comments.append(comment)
|
social.comments.append(comment)
|
||||||
|
|
||||||
|
|||||||
22
tests/api/test_social_4chan.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import re
|
||||||
|
import pytest
|
||||||
|
from app.api.social.chan import ChanWrapper
|
||||||
|
|
||||||
|
@pytest.mark.social
|
||||||
|
@pytest.mark.api
|
||||||
|
class TestChanWrapper:
|
||||||
|
def test_initialization(self):
|
||||||
|
wrapper = ChanWrapper()
|
||||||
|
assert wrapper is not None
|
||||||
|
|
||||||
|
def test_get_top_crypto_posts(self):
|
||||||
|
wrapper = ChanWrapper()
|
||||||
|
posts = wrapper.get_top_crypto_posts(limit=2)
|
||||||
|
assert isinstance(posts, list)
|
||||||
|
assert len(posts) == 2
|
||||||
|
for post in posts:
|
||||||
|
assert post.title != ""
|
||||||
|
assert post.time != ""
|
||||||
|
assert re.match(r'\d{4}-\d{2}-\d{2}', post.time)
|
||||||
|
assert isinstance(post.comments, list)
|
||||||
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import pytest
|
import pytest
|
||||||
from app.api.social.reddit import MAX_COMMENTS, RedditWrapper
|
from app.api.social.reddit import MAX_COMMENTS, RedditWrapper
|
||||||
|
|
||||||
@@ -18,6 +19,8 @@ class TestRedditWrapper:
|
|||||||
assert len(posts) == 2
|
assert len(posts) == 2
|
||||||
for post in posts:
|
for post in posts:
|
||||||
assert post.title != ""
|
assert post.title != ""
|
||||||
|
assert re.match(r'\d{4}-\d{2}-\d{2}', post.time)
|
||||||
|
|
||||||
assert isinstance(post.comments, list)
|
assert isinstance(post.comments, list)
|
||||||
assert len(post.comments) <= MAX_COMMENTS
|
assert len(post.comments) <= MAX_COMMENTS
|
||||||
for comment in post.comments:
|
for comment in post.comments:
|
||||||
22
tests/api/test_social_x_api.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import pytest
|
||||||
|
from app.api.social.x import XWrapper
|
||||||
|
|
||||||
|
@pytest.mark.social
|
||||||
|
@pytest.mark.api
|
||||||
|
@pytest.mark.skipif(not os.getenv("X_API_KEY"), reason="X_API_KEY not set in environment variables")
|
||||||
|
class TestXWrapper:
|
||||||
|
def test_initialization(self):
|
||||||
|
wrapper = XWrapper()
|
||||||
|
assert wrapper is not None
|
||||||
|
|
||||||
|
def test_get_top_crypto_posts(self):
|
||||||
|
wrapper = XWrapper()
|
||||||
|
posts = wrapper.get_top_crypto_posts(limit=2)
|
||||||
|
assert isinstance(posts, list)
|
||||||
|
assert len(posts) == 2
|
||||||
|
for post in posts:
|
||||||
|
assert post.title != ""
|
||||||
|
assert re.match(r'\d{4}-\d{2}-\d{2}', post.time)
|
||||||
|
assert isinstance(post.comments, list)
|
||||||
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore
Pulire il file che ha troppi commenti, in modo da rendere il codice più leggibile
Rimuovere inoltre la quantità di try non necessaria, dato che se l'API ha dei problemi non voglio che mi restituisca un oggetto vuoto, ma voglio l'errore