Skip to content

Commit

Permalink
add email refresh
Browse files Browse the repository at this point in the history
  • Loading branch information
Matanga1-2 committed Jan 31, 2025
1 parent 057b019 commit 00efc7a
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 47 deletions.
27 changes: 22 additions & 5 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from src.cli.input_handler import display_feed_stats, get_valid_url
from src.db.database import Database
from src.db.saved_items_repository import SavedItemsRepository
from src.mail_sender.init_credentials import init_gmail_credentials
from src.processor.feed_processor import categorize_feed_items, process_feed_items
from src.utils.console import prompt_yes_no
from src.utils.text_formatter import format_hebrew
Expand Down Expand Up @@ -47,9 +48,10 @@ def _process_menu_choice(self) -> bool:
print("3. Process current feed")
print("4. Store saved items")
print("5. Go to all URLs")
print("6. Exit")
print("6. Refresh Gmail credentials")
print("7. Exit")

choice = input("\nEnter your choice (1-6): ").strip()
choice = input("\nEnter your choice (1-7): ").strip()

if choice == '1':
self._handle_new_url()
Expand All @@ -62,10 +64,12 @@ def _process_menu_choice(self) -> bool:
elif choice == '5':
self._handle_go_to_all_urls()
elif choice == '6':
self._handle_refresh_credentials()
elif choice == '7':
print("Goodbye!")
return False
else:
print("Invalid choice. Please enter a number between 1 and 5.")
print("Invalid choice. Please enter a number between 1 and 7.")

return True

Expand Down Expand Up @@ -135,6 +139,19 @@ def _handle_process_feed(self) -> None:
return

items_to_process = [item for item in self.feed_items if not item.is_saved]
process_feed_items(items_to_process, self.address_matcher, self.client, self.saved_items_repo)

def _handle_refresh_credentials(self) -> None:
"""Handle refreshing Gmail credentials."""
print("\nRefreshing Gmail credentials...")
force_new = prompt_yes_no("Do you want to force creation of new credentials?")

if prompt_yes_no("\nProceed with processing these items?"):
process_feed_items(items_to_process, self.address_matcher, self.client, self.saved_items_repo)
try:
success = init_gmail_credentials(force_new=force_new)
if success:
print("Successfully refreshed Gmail credentials!")
else:
print("Failed to refresh Gmail credentials. Check the logs for more details.")
except Exception as e:
logging.error(f"Failed to refresh credentials: {str(e)}", exc_info=True)
print(f"Failed to refresh credentials: {str(e)}")
64 changes: 34 additions & 30 deletions src/mail_sender/init_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,47 +7,46 @@
from src.utils.console import prompt_yes_no


def _setup_logging():
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

if not logger.handlers:
# Get the directory where this script is located
current_dir = os.path.dirname(os.path.abspath(__file__))
log_path = os.path.join(current_dir, 'gmail_auth.log')

handlers = [
logging.FileHandler(log_path),
logging.StreamHandler()
]
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

for handler in handlers:
handler.setFormatter(formatter)
handler.setLevel(logging.INFO)
logger.addHandler(handler)

return logger

logger = _setup_logging()
def get_credentials_dir():
"""Get the directory where credentials should be stored"""
home = os.path.expanduser('~')
credentials_dir = os.path.join(home, '.Yad2Scraper', 'credentials')
os.makedirs(credentials_dir, exist_ok=True)
return credentials_dir

def init_gmail_credentials():
def init_gmail_credentials(force_new=False):
"""
Initialize Gmail API credentials. This script should be run once before using the main application.
It will:
1. Check for existing token.pickle and client_secret.json
2. Start the OAuth2 flow if needed
3. Save the credentials to token.pickle
Args:
force_new (bool): If True, force creation of new credentials regardless of existing ones
"""
logger = logging.getLogger(__name__)
SCOPES = ['https://www.googleapis.com/auth/gmail.send']

# Get the directory where this script is located
current_dir = os.path.dirname(os.path.abspath(__file__))
client_secret_path = os.path.join(current_dir, 'client_secret.json')
token_path = os.path.join(current_dir, 'token.pickle')
# Get the credentials directory in user's home
credentials_dir = get_credentials_dir()

# For client_secret.json, first check in the credentials dir, then fall back to package dir
client_secret_name = 'client_secret.json'
token_path = os.path.join(credentials_dir, 'token.pickle')
client_secret_path = os.path.join(credentials_dir, client_secret_name)

if not os.path.exists(client_secret_path):
# Fall back to checking in the package directory
package_dir = os.path.dirname(os.path.abspath(__file__))
package_client_secret = os.path.join(package_dir, client_secret_name)
if os.path.exists(package_client_secret):
# Copy to user directory
import shutil
shutil.copy2(package_client_secret, client_secret_path)

# Check if token.pickle already exists
if os.path.exists(token_path):
if os.path.exists(token_path) and not force_new:
logger.info(f"Existing credentials found at {token_path}")
if not prompt_yes_no("Credentials already exist. Do you want to create new ones?"):
logger.info("Using existing credentials")
Expand All @@ -61,7 +60,7 @@ def init_gmail_credentials():
logger.info("2. Create a project or select existing one")
logger.info("3. Enable Gmail API")
logger.info("4. Create OAuth 2.0 credentials")
logger.info("5. Download client configuration and save as 'client_secret.json'")
logger.info(f"5. Download client configuration and save as '{client_secret_name}' in {credentials_dir}")
return False

try:
Expand All @@ -81,4 +80,9 @@ def init_gmail_credentials():
return False

if __name__ == "__main__":
# Setup basic logging for when script is run directly
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
init_gmail_credentials()
16 changes: 10 additions & 6 deletions src/mail_sender/sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from googleapiclient.discovery import build

from src.mail_sender.init_credentials import init_gmail_credentials
from src.mail_sender.init_credentials import get_credentials_dir, init_gmail_credentials


class EmailSender:
Expand All @@ -26,9 +26,9 @@ def __init__(self):

def initialize_credentials(self):
"""Initialize or load credentials for Gmail API using init_credentials logic."""
# Get the directory where this script is located
current_dir = os.path.dirname(os.path.abspath(__file__))
token_path = os.path.join(current_dir, 'token.pickle')
# Get the credentials directory
credentials_dir = get_credentials_dir()
token_path = os.path.join(credentials_dir, 'token.pickle')

# Initialize credentials using the init_credentials function
if not os.path.exists(token_path):
Expand All @@ -38,8 +38,12 @@ def initialize_credentials(self):
raise RuntimeError("Failed to initialize Gmail credentials.")

# Load the credentials from token.pickle
with open(token_path, 'rb') as token:
self.creds = pickle.load(token)
try:
with open(token_path, 'rb') as token:
self.creds = pickle.load(token)
except Exception as err:
logging.error(f"Failed to load credentials from {token_path}: {str(err)}")
raise RuntimeError(f"Failed to load Gmail credentials: {str(err)}") from err

def create_message(self, subject: str, body: str) -> dict:
"""Create a message for an email."""
Expand Down
8 changes: 4 additions & 4 deletions src/yad2/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ def wait_for_element(self, by: By, value: str, timeout: int = 10):
)
self.logger.debug("Element found through wait_for_element")
return element
except Exception as e:
self.logger.error(f"Element not found: {by}={value}, {str(e)}")
self.logger.error(f"Current URL: {self.driver.current_url}")
self.logger.error(f"Page source snippet: {self.driver.page_source[:500]}")
except Exception:
self.logger.error(f"Element not found: {by}={value}")
# self.logger.error(f"Current URL: {self.driver.current_url}")
# self.logger.error(f"Page source snippet: {self.driver.page_source[:500]}")
raise

@staticmethod
Expand Down
15 changes: 15 additions & 0 deletions tests/email/test_sender.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import pytest
from google.auth.exceptions import RefreshError

from src.mail_sender.init_credentials import init_gmail_credentials
from src.mail_sender.sender import EmailSender


Expand All @@ -25,5 +27,18 @@ def test_send_email(setup_env_vars):
try:
sender.send_email(subject, body)
print("Test email sent successfully.")
except RefreshError:
# If token is expired, try to refresh credentials and retry
print("Token expired, attempting to refresh credentials...")
if init_gmail_credentials(force_new=True):
# Retry with new credentials
try:
sender = EmailSender() # Create new sender with fresh credentials
sender.send_email(subject, body)
print("Test email sent successfully after refreshing credentials.")
except Exception as e:
pytest.fail(f"Sending test email failed even after refreshing credentials: {e}")
else:
pytest.fail("Failed to refresh Gmail credentials")
except Exception as e:
pytest.fail(f"Sending test email failed: {e}")
3 changes: 1 addition & 2 deletions tests/test_app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from unittest.mock import MagicMock, patch
import json
from unittest.mock import MagicMock

import pytest

Expand Down

0 comments on commit 00efc7a

Please sign in to comment.