From 8a67ec79e41e2d74ad6d1182c86bb8f47fa4b23e Mon Sep 17 00:00:00 2001 From: 7x11x13 Date: Wed, 12 Mar 2025 16:12:24 -0400 Subject: [PATCH] Use guerrilla mail --- free_bandcamp_downloader/__main__.py | 65 ++++++------ poetry.lock | 144 +++------------------------ pyproject.toml | 4 +- 3 files changed, 50 insertions(+), 163 deletions(-) diff --git a/free_bandcamp_downloader/__main__.py b/free_bandcamp_downloader/__main__.py index 2dcd19c..ce8602c 100644 --- a/free_bandcamp_downloader/__main__.py +++ b/free_bandcamp_downloader/__main__.py @@ -59,15 +59,15 @@ from configparser import ConfigParser from dataclasses import dataclass from http.cookiejar import MozillaCookieJar from typing import Dict, Optional, Set -from urllib.parse import urljoin, urlsplit +from urllib.parse import urljoin import mutagen import pyrfc6266 import requests -import secmail from bs4 import BeautifulSoup from docopt import docopt from tqdm import tqdm +from guerrillamail import GuerrillaMailSession from free_bandcamp_downloader import __version__, logger from free_bandcamp_downloader.bandcamp_http_adapter import BandcampHTTPAdapter @@ -75,20 +75,20 @@ from free_bandcamp_downloader.bandcamp_http_adapter import BandcampHTTPAdapter @dataclass class BCFreeDownloaderOptions: - country: str = None - zipcode: str = None - email: str = None - format: str = None - dir: str = None + country: str = "United States" + zipcode: str = "00000" + email: str = "auto" + format: str = "FLAC" + dir: str = "." @dataclass class BCFreeDownloaderAlbumData: - about: str = None - credits: str = None - tags: str = None - id: str = None - title: str = None + about: Optional[str] + credits: Optional[str] + tags: Optional[str] + id: str + title: Optional[str] class BCFreeDownloadError(Exception): @@ -126,13 +126,12 @@ class BCFreeDownloader: self.mail_session = None self.mail_album_data: Dict[str, BCFreeDownloaderAlbumData] = {} self.unzip = unzip - self.session = None self._init_downloaded() self._init_session(cookies_file, identity) def _init_email(self): - self.mail_session = secmail.Client(self.config_dir) - self.options.email = self.mail_session.random_email(1, "1secmail.com")[0] + self.mail_session = GuerrillaMailSession() + self.options.email = self.mail_session.get_session_state()["email_address"] def _init_downloaded(self): if self.download_history_file: @@ -240,7 +239,6 @@ class BCFreeDownloader: @staticmethod def _get_album_data_from_soup(soup: BeautifulSoup) -> BCFreeDownloaderAlbumData: - album_data = BCFreeDownloaderAlbumData() about = soup.find("div", class_="tralbum-about") credits = soup.find("div", class_="tralbum-credits") tags = [tag.get_text() for tag in soup.find_all("a", class_="tag")] @@ -249,12 +247,13 @@ class BCFreeDownloader: ) id = f"{properties['item_type']}:{properties['item_id']}" - album_data.about = about.get_text("\n") if about else None - album_data.credits = credits.get_text("\n") if credits else None - album_data.tags = ",".join(sorted(tags)) - album_data.id = id - - return album_data + return BCFreeDownloaderAlbumData( + about=about.get_text("\n") if about else None, + credits=credits.get_text("\n") if credits else None, + tags=",".join(sorted(tags)), + id=id, + title=None, + ) def _download_purchased_album( self, user_id: int, album_data: BCFreeDownloaderAlbumData @@ -330,7 +329,6 @@ class BCFreeDownloader: if head_data["offers"]["price"] == 0.0: if tralbum_data["current"]["require_email"]: - raise BCFreeDownloadError(f"{url} requires email. Skipping...") if not self.options.email or self.options.email == "auto": self._init_email() logger.info(f"{url} requires email") @@ -414,7 +412,7 @@ class BCFreeDownloader: try: url_type = soup.head.find("meta", attrs={"property": "og:type"})["content"] - except: + except Exception: raise BCFreeDownloadError(f"{url} does not have an og:type property.") if url_type == "album" or url_type == "song": @@ -429,24 +427,27 @@ class BCFreeDownloader: while (expected_emails := len(self.mail_album_data)) > 0: logger.info(f"Waiting for {expected_emails} emails from Bandcamp...") time.sleep(5) - for email in self.mail_session.get_inbox(self.options.email): - if email.id not in checked_ids: - checked_ids.add(email.id) + for email in self.mail_session.get_email_list(): + email_id = email.guid + if email_id not in checked_ids: + checked_ids.add(email_id) if ( - email.from_address.endswith("@email.bandcamp.com") + email.sender == "noreply@bandcamp.com" and "download" in email.subject ): logger.info(f'Received email "{email.subject}"') - email = self.mail_session.get_message( - self.options.email, email.id - ) - match = self.LINK_REGEX.search(email.html_body) + content = self.mail_session.get_email(email_id).body + match = self.LINK_REGEX.search(content) if match: download_url = match.group("url") album_id = self._download_file( download_url, self.options.format ) self.mail_album_data.pop(album_id) + else: + logger.error( + f"Could not find download URL in body: {content}" + ) class BCFreeDownloaderConfig: diff --git a/poetry.lock b/poetry.lock index 9d8ab34..3b2dacb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,41 +1,5 @@ # This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. -[[package]] -name = "1secmail" -version = "1.2.0" -description = "📧 Simple and intuitive, yet full featured API wrapper for www.1secmail.com, supporting both synchronous and asynchronous operations." -optional = false -python-versions = "*" -files = [ - {file = "1secMail-1.2.0-py3-none-any.whl", hash = "sha256:9e2e01fd8ed95abab90de2ec5dbf8f6e0687398ba90583d01b0d39c322aa008a"}, - {file = "1secMail-1.2.0.tar.gz", hash = "sha256:169955e6239aa2b3e039388dae0a4f6d26ef4f0a86806562c0b21be79d78450c"}, -] - -[package.dependencies] -httpx = ">=0.17.1" - -[[package]] -name = "anyio" -version = "4.4.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.8" -files = [ - {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, - {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, -] - -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} - -[package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] - [[package]] name = "beautifulsoup4" version = "4.12.3" @@ -189,76 +153,6 @@ files = [ {file = "docopt_ng-0.9.0.tar.gz", hash = "sha256:91c6da10b5bb6f2e9e25345829fb8278c78af019f6fc40887ad49b060483b1d7"}, ] -[[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "httpcore" -version = "1.0.5" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, - {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.13,<0.15" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.26.0)"] - -[[package]] -name = "httpx" -version = "0.27.0" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, - {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" -sniffio = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] - [[package]] name = "idna" version = "3.8" @@ -309,6 +203,20 @@ files = [ [package.dependencies] pyparsing = ">=3.0.7,<3.1.0" +[[package]] +name = "python-guerrillamail" +version = "0.2.0" +description = "Client for the Guerrillamail temporary email server" +optional = false +python-versions = "*" +files = [ + {file = "python-guerrillamail-0.2.0.tar.gz", hash = "sha256:f04c48c58ddc55f81c2fe63c43e5f8d806ea77695fc621fe8bdbc11413bf9e31"}, + {file = "python_guerrillamail-0.2.0-py2.py3-none-any.whl", hash = "sha256:993934d09cdba4c0f8a70b00eedf3563acabdec3e24bb82335121a0e022afff5"}, +] + +[package.dependencies] +requests = "*" + [[package]] name = "requests" version = "2.32.3" @@ -330,17 +238,6 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - [[package]] name = "soupsieve" version = "2.6" @@ -372,17 +269,6 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - [[package]] name = "urllib3" version = "2.2.2" @@ -403,4 +289,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "b167eea04fb32fb22688a58b4a4cab72d2ad7bd468f82b19fb06bb5c20271dd9" +content-hash = "85da27e91df1a1842bad13ae922fb9d9f63d82795c8665a3aeb91a7be43a0d8c" diff --git a/pyproject.toml b/pyproject.toml index 1f49343..e2e94ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "free-bandcamp-downloader" -version = "0.4.2" +version = "0.4.3" description = "Download free and name-your-price albums from Bandcamp in lossless quality" authors = ["7x11x13 "] license = "MIT" @@ -16,8 +16,8 @@ beautifulsoup4 = "^4.12.2" tqdm = "^4.66.1" pyrfc6266 = "^1.0.2" mutagen = "^1.47.0" -1secmail = "^1.1.0" docopt-ng = "^0.9.0" +python-guerrillamail = "^0.2.0" [build-system]