From f3467d812802e8cb46699b672137f86663233fbc Mon Sep 17 00:00:00 2001 From: dendogg Date: 2025年5月19日 13:56:40 -0700 Subject: [PATCH 1/3] Update README.md --- Attachment_Downloader/README.md | 98 +++++++++++++++++++++++++++------ 1 file changed, 82 insertions(+), 16 deletions(-) diff --git a/Attachment_Downloader/README.md b/Attachment_Downloader/README.md index 4545a209d9..34c749bff1 100644 --- a/Attachment_Downloader/README.md +++ b/Attachment_Downloader/README.md @@ -1,38 +1,104 @@ -# Attachment Downloader +# Gmail Attachment Downloader -The script downloads gmail atttachment(s) in no time! +A Python script to easily search and download Gmail attachments based on search queries. +## Features -# Setup Instructions +- Search Gmail using Google's powerful search operators +- View email search results with dates and subjects +- Download attachments from search results to a specified directory +- Progress tracking during downloads +- User-friendly command-line interface -The script uses ezgmail module (to know more visit https://pypi.org/project/EZGmail/). To install please type the following command in your bash. +## Requirements +- Python 3.6+ +- ezgmail library +- Google API credentials + +## Setup Instructions + +### 1. Install Required Packages + +```bash +pip install ezgmail +``` + +### 2. Get Google API Credentials + +1. Visit the [Google API Console](https://developers.google.com/gmail/api/quickstart/python) +2. Click the "Enable the Gmail API" button +3. Select "Desktop app" as the OAuth Client type when prompted +4. Download the `credentials.json` file and place it in the same directory as this script + +### 3. First-Time Authentication + +The first time you run the script, it will: +1. Open a browser window asking you to log in to your Gmail account +2. Request permission for the app to access your Gmail +3. Generate a `token.json` file in the script directory to save your authentication + +## Usage + +1. Run the script: ```bash -pip install EZGmail +python attachment_downloader.py ``` -Once the module is installed you will need to download credentials.json file by going to [developers.google.com](https://developers.google.com/gmail/api/quickstart/python) and clicking the Enable the Gmail API button (select "Desktop app" as OAuth Client in second step). +2. Enter your search query when prompted. The script supports all Gmail search operators, for example: + - `from:example@gmail.com` - Emails from a specific address + - `subject:report` - Emails with "report" in the subject + - `after:2023年01月01日 before:2023年12月31日` - Emails within a date range + - `label:work` - Emails with a specific label + - `has:pdf` - Emails with PDF attachments + - Combine operators: `from:example@gmail.com subject:report has:pdf` + +3. Review the search results + +4. Confirm if you want to download the attachments + +5. Specify a download directory (optional) -Once you have credentials.json (in root directory of project folder), the first time you run the script it will open up a browser window asking you to log in to your Gmail account and allow "Quickstart" app to access it. A token.json file will be generated (in root directory of project folder) which your script can use to access your account. +## Search Query Examples -# Outputs +- `from:payroll@company.com has:spreadsheet` - Payroll spreadsheets +- `subject:(invoice OR receipt) after:2023年05月01日` - Recent invoices or receipts +- `from:newsletter@example.com label:newsletters` - Newsletter emails with a specific label +- `filename:pdf larger:5M` - PDF attachments larger than 5MB +- `from:myteam@company.com has:attachment -has:document` - Team emails with attachments that aren't documents -## Output 1 +## Troubleshooting -![image](https://user-images.githubusercontent.com/75886245/111307477-82e5d100-867f-11eb-8461-58abd04fa56a.png) +### Authentication Issues -## Output 2 +- Make sure you have the `credentials.json` file in the same directory as the script +- If you change Google accounts, delete the `token.json` file and re-authenticate +- If you see permission errors, check that you've enabled the Gmail API for your Google account -![image](https://user-images.githubusercontent.com/75886245/111307625-ba547d80-867f-11eb-8f3b-77ded9e72416.png) +### Search Problems -## Output 3 +- Gmail search can be case-sensitive for some operators +- Ensure your search syntax is correct (see [Gmail search operators](https://support.google.com/mail/answer/7190?hl=en)) +- Very large inboxes may take longer to search or time out -![image](https://user-images.githubusercontent.com/75886245/111307771-e66ffe80-867f-11eb-8bbe-457675fcb963.png) +### Download Issues -## Output 4 +- Check if you have write permissions for the download directory +- Very large attachments might take time to download +- Gmail API has usage limits that might affect bulk downloads -![image](https://user-images.githubusercontent.com/75886245/111307864-0273a000-8680-11eb-8400-3009dff8d7db.png) +## Advanced Usage + +- To automate downloads, you can modify the script to accept command-line arguments +- For recurring downloads, consider setting up as a scheduled task +- To filter by attachment types, use search operators like `has:pdf`, `has:spreadsheet`, etc. + +## License + +This project is open source and available under the MIT License. # Author Contributed by Kirtan Bhatt + +Updated: Dennis 'dnoice' Smaltz May 19, 2025 From 8ad9ee071efbe767f63d4faad92ab9a6efdb9385 Mon Sep 17 00:00:00 2001 From: dendogg Date: 2025年5月19日 13:58:51 -0700 Subject: [PATCH 2/3] Update and rename attachment.py to attachment_downloader.py --- Attachment_Downloader/attachment.py | 48 ------ .../attachment_downloader.py | 139 ++++++++++++++++++ 2 files changed, 139 insertions(+), 48 deletions(-) delete mode 100644 Attachment_Downloader/attachment.py create mode 100644 Attachment_Downloader/attachment_downloader.py diff --git a/Attachment_Downloader/attachment.py b/Attachment_Downloader/attachment.py deleted file mode 100644 index 80e865cf84..0000000000 --- a/Attachment_Downloader/attachment.py +++ /dev/null @@ -1,48 +0,0 @@ -import ezgmail - - -def attachmentdownload(resulthreads): - # Two Objects used in code are GmailThread and GmailMessage - # 1. GmailThread - Represents conversation threads - # 2. GmailMessage - Represents individual emails within Threads - countofresults = len(resulthreads) - try: - for i in range(countofresults): - # checks whether the count of messages in threads is greater than 1 - if len(resulthreads[i].messages)> 1: - for j in range(len(resulthreads[i].messages)): - resulthreads[i].messages[ - j].downloadAllAttachments() # downloads attachment(s) for individual messages - else: - # downloads attachment(s) for single message - resulthreads[i].messages[0].downloadAllAttachments() - print("Download compelete. Please check your root directory.") - except: - raise Exception("Error occured while downloading attachment(s).") - - -if __name__ == '__main__': - query = input("Enter search query: ") - # appending to make sure the result threads always has an attachment - newquery = query + " + has:attachment" - # search functions accepts all the operators described at https://support.google.com/mail/answer/7190?hl=en - resulthreads = ezgmail.search(newquery) - - if len(resulthreads) == 0: - # Executed if results don't have attachment - print("Result has no attachments:") - else: - print("Result(s) with attachments:") - for threads in resulthreads: - # prints the subject line of email thread in results - print(f"Email Subject: {threads.messages[0].subject}") - try: - ask = input( - "Do you want to download attachment(s) in result(s) (Yes/No)? ") # Allows user to decide whether they want to download attachment(s) or not - if ask == "Yes": - # calls the function that downloads attachment(s) - attachmentdownload(resulthreads) - else: - print("Program exited") - except: - print("Something went wrong") diff --git a/Attachment_Downloader/attachment_downloader.py b/Attachment_Downloader/attachment_downloader.py new file mode 100644 index 0000000000..eca0d9001e --- /dev/null +++ b/Attachment_Downloader/attachment_downloader.py @@ -0,0 +1,139 @@ +""" +Gmail Attachment Downloader +A script to easily search and download Gmail attachments based on search queries. +""" +import os +import ezgmail +import datetime +from typing import List, Optional + + +def download_attachments(result_threads: List, download_dir: Optional[str] = None) -> None: + """ + Download all attachments from Gmail threads that match search criteria. + + Args: + result_threads: List of GmailThread objects from search results + download_dir: Directory to save attachments (defaults to current directory) + + Raises: + Exception: If there's an error during download + """ + # Create download directory if it doesn't exist + if download_dir and not os.path.exists(download_dir): + os.makedirs(download_dir) + print(f"Created directory: {download_dir}") + + # Store original working directory + original_dir = os.getcwd() + + # Change to download directory if specified + if download_dir: + os.chdir(download_dir) + + count_of_results = len(result_threads) + downloaded_files = 0 + + try: + print(f"Starting download of attachments from {count_of_results} thread(s)...") + + for i, thread in enumerate(result_threads): + thread_count = i + 1 + print(f"Processing thread {thread_count}/{count_of_results}: {thread.messages[0].subject}") + + # Check if thread has multiple messages + if len(thread.messages)> 1: + for j, message in enumerate(thread.messages): + msg_count = j + 1 + print(f" - Downloading attachments from message {msg_count}/{len(thread.messages)}") + downloaded = message.downloadAllAttachments() + downloaded_files += len(downloaded) + if downloaded: + print(f" Downloaded: {', '.join(downloaded)}") + else: + # Thread has only one message + downloaded = thread.messages[0].downloadAllAttachments() + downloaded_files += len(downloaded) + if downloaded: + print(f" - Downloaded: {', '.join(downloaded)}") + + print(f"\nDownload complete! {downloaded_files} file(s) downloaded.") + if download_dir: + print(f"Files saved to: {os.path.abspath(download_dir)}") + else: + print(f"Files saved to: {os.getcwd()}") + + except Exception as e: + print(f"Error occurred while downloading attachment(s): {str(e)}") + raise + finally: + # Return to original directory if we changed it + if download_dir: + os.chdir(original_dir) + + +def main(): + """Main function to run when script is executed directly.""" + print("=" * 50) + print("Gmail Attachment Downloader") + print("=" * 50) + + # Check if ezgmail is authenticated + try: + ezgmail.init() + print(f"Logged in as: {ezgmail.EMAIL_ADDRESS}\n") + except Exception as e: + print("Authentication error. Please follow setup instructions in README.md") + print(f"Error details: {str(e)}") + return + + # Get search query from user + query = input("Enter search query (e.g., 'from:example@gmail.com'): ").strip() + if not query: + print("Search query cannot be empty. Exiting...") + return + + # Always include attachment filter in the search + search_query = f"{query} has:attachment" + + print(f"\nSearching for: {search_query}") + print("This may take a moment depending on your inbox size...") + + try: + # Perform the search + result_threads = ezgmail.search(search_query) + + if not result_threads: + print("\nNo results found with attachments matching your query.") + return + + # Display results + print(f"\nFound {len(result_threads)} result(s) with attachments:") + for i, thread in enumerate(result_threads): + subject = thread.messages[0].subject + date = datetime.datetime.fromtimestamp(thread.messages[0].timestamp) + formatted_date = date.strftime("%Y-%m-%d %H:%M") + print(f"{i+1}. [{formatted_date}] {subject}") + + # Ask user if they want to download + while True: + download_confirmation = input("\nDo you want to download the attachment(s)? (y/n): ").strip().lower() + if download_confirmation in ['y', 'yes']: + # Ask for download directory + custom_dir = input("Enter download directory (leave empty for current directory): ").strip() + download_attachments(result_threads, custom_dir if custom_dir else None) + break + elif download_confirmation in ['n', 'no']: + print("Download canceled. Exiting...") + break + else: + print("Please enter 'y' or 'n'.") + + except KeyboardInterrupt: + print("\nOperation canceled by user.") + except Exception as e: + print(f"\nAn error occurred: {str(e)}") + + +if __name__ == '__main__': + main() From e67cae32fcba6bca20002115c9b44edfe5bd8e75 Mon Sep 17 00:00:00 2001 From: dendogg Date: 2025年5月19日 16:42:03 -0700 Subject: [PATCH 3/3] Update Attachment_Downloader/attachment_downloader.py Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- Attachment_Downloader/attachment_downloader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Attachment_Downloader/attachment_downloader.py b/Attachment_Downloader/attachment_downloader.py index eca0d9001e..65473ad442 100644 --- a/Attachment_Downloader/attachment_downloader.py +++ b/Attachment_Downloader/attachment_downloader.py @@ -19,9 +19,9 @@ def download_attachments(result_threads: List, download_dir: Optional[str] = Non Raises: Exception: If there's an error during download """ - # Create download directory if it doesn't exist - if download_dir and not os.path.exists(download_dir): - os.makedirs(download_dir) + # Ensure download directory exists (atomically avoiding race conditions) + if download_dir: + os.makedirs(download_dir, exist_ok=True) print(f"Created directory: {download_dir}") # Store original working directory

AltStyle によって変換されたページ (->オリジナル) /