diff --git a/services/giphy_service.py b/services/giphy_service.py index 165d5c3..4e1b215 100644 --- a/services/giphy_service.py +++ b/services/giphy_service.py @@ -98,6 +98,13 @@ class GiphyService: self.api_key = self.config.giphy_api_key self.translate_url = self.config.giphy_translate_url self.logger = get_contextual_logger(f"{__name__}.GiphyService") + self._session: Optional[aiohttp.ClientSession] = None + + def _get_session(self) -> aiohttp.ClientSession: + """Return the shared aiohttp session, creating it lazily if needed.""" + if self._session is None or self._session.closed: + self._session = aiohttp.ClientSession() + return self._session def get_tier_for_seconds(self, seconds_elapsed: Optional[int]) -> str: """ @@ -181,55 +188,53 @@ class GiphyService: # Shuffle phrases for variety and retry capability shuffled_phrases = random.sample(phrases, len(phrases)) - async with aiohttp.ClientSession() as session: - for phrase in shuffled_phrases: - try: - url = f"{self.translate_url}?s={quote(phrase)}&api_key={quote(self.api_key)}" + session = self._get_session() + for phrase in shuffled_phrases: + try: + url = f"{self.translate_url}?s={quote(phrase)}&api_key={quote(self.api_key)}" - async with session.get( - url, timeout=aiohttp.ClientTimeout(total=5) - ) as resp: - if resp.status == 200: - data = await resp.json() + async with session.get( + url, timeout=aiohttp.ClientTimeout(total=5) + ) as resp: + if resp.status == 200: + data = await resp.json() - # Filter out Trump GIFs (legacy behavior) - gif_title = data.get("data", {}).get("title", "").lower() - if "trump" in gif_title: - self.logger.debug( - f"Filtered out Trump GIF for phrase: {phrase}" - ) - continue - - # Get the actual GIF image URL, not the web page URL - gif_url = ( - data.get("data", {}) - .get("images", {}) - .get("original", {}) - .get("url") + # Filter out Trump GIFs (legacy behavior) + gif_title = data.get("data", {}).get("title", "").lower() + if "trump" in gif_title: + self.logger.debug( + f"Filtered out Trump GIF for phrase: {phrase}" ) - if gif_url: - self.logger.info( - f"Successfully fetched GIF for phrase: {phrase}", - gif_url=gif_url, - ) - return gif_url - else: - self.logger.warning( - f"No GIF URL in response for phrase: {phrase}" - ) + continue + + # Get the actual GIF image URL, not the web page URL + gif_url = ( + data.get("data", {}) + .get("images", {}) + .get("original", {}) + .get("url") + ) + if gif_url: + self.logger.info( + f"Successfully fetched GIF for phrase: {phrase}", + gif_url=gif_url, + ) + return gif_url else: self.logger.warning( - f"Giphy API returned status {resp.status} for phrase: {phrase}" + f"No GIF URL in response for phrase: {phrase}" ) + else: + self.logger.warning( + f"Giphy API returned status {resp.status} for phrase: {phrase}" + ) - except aiohttp.ClientError as e: - self.logger.error( - f"HTTP error fetching GIF for phrase '{phrase}': {e}" - ) - except Exception as e: - self.logger.error( - f"Unexpected error fetching GIF for phrase '{phrase}': {e}" - ) + except aiohttp.ClientError as e: + self.logger.error(f"HTTP error fetching GIF for phrase '{phrase}': {e}") + except Exception as e: + self.logger.error( + f"Unexpected error fetching GIF for phrase '{phrase}': {e}" + ) # All phrases failed error_msg = f"Failed to fetch any GIF for tier: {tier_key}" @@ -264,58 +269,58 @@ class GiphyService: elif phrase_options is not None: search_phrase = random.choice(phrase_options) - async with aiohttp.ClientSession() as session: - attempts = 0 - while attempts < 3: - attempts += 1 - try: - url = f"{self.translate_url}?s={quote(search_phrase)}&api_key={quote(self.api_key)}" + session = self._get_session() + attempts = 0 + while attempts < 3: + attempts += 1 + try: + url = f"{self.translate_url}?s={quote(search_phrase)}&api_key={quote(self.api_key)}" - async with session.get( - url, timeout=aiohttp.ClientTimeout(total=3) - ) as resp: - if resp.status != 200: - self.logger.warning( - f"Giphy API returned status {resp.status} for phrase: {search_phrase}" - ) - continue - - data = await resp.json() - - # Filter out Trump GIFs (legacy behavior) - gif_title = data.get("data", {}).get("title", "").lower() - if "trump" in gif_title: - self.logger.debug( - f"Filtered out Trump GIF for phrase: {search_phrase}" - ) - continue - - # Get the actual GIF image URL, not the web page URL - gif_url = ( - data.get("data", {}) - .get("images", {}) - .get("original", {}) - .get("url") + async with session.get( + url, timeout=aiohttp.ClientTimeout(total=3) + ) as resp: + if resp.status != 200: + self.logger.warning( + f"Giphy API returned status {resp.status} for phrase: {search_phrase}" ) - if gif_url: - self.logger.info( - f"Successfully fetched GIF for phrase: {search_phrase}", - gif_url=gif_url, - ) - return gif_url - else: - self.logger.warning( - f"No GIF URL in response for phrase: {search_phrase}" - ) + continue - except aiohttp.ClientError as e: - self.logger.error( - f"HTTP error fetching GIF for phrase '{search_phrase}': {e}" - ) - except Exception as e: - self.logger.error( - f"Unexpected error fetching GIF for phrase '{search_phrase}': {e}" + data = await resp.json() + + # Filter out Trump GIFs (legacy behavior) + gif_title = data.get("data", {}).get("title", "").lower() + if "trump" in gif_title: + self.logger.debug( + f"Filtered out Trump GIF for phrase: {search_phrase}" + ) + continue + + # Get the actual GIF image URL, not the web page URL + gif_url = ( + data.get("data", {}) + .get("images", {}) + .get("original", {}) + .get("url") ) + if gif_url: + self.logger.info( + f"Successfully fetched GIF for phrase: {search_phrase}", + gif_url=gif_url, + ) + return gif_url + else: + self.logger.warning( + f"No GIF URL in response for phrase: {search_phrase}" + ) + + except aiohttp.ClientError as e: + self.logger.error( + f"HTTP error fetching GIF for phrase '{search_phrase}': {e}" + ) + except Exception as e: + self.logger.error( + f"Unexpected error fetching GIF for phrase '{search_phrase}': {e}" + ) # All attempts failed error_msg = f"Failed to fetch any GIF for phrase: {search_phrase}"