Use guerrilla mail

This commit is contained in:
7x11x13
2025-03-12 16:12:24 -04:00
parent ac8e841e02
commit 8a67ec79e4
3 changed files with 50 additions and 163 deletions
+33 -32
View File
@@ -59,15 +59,15 @@ from configparser import ConfigParser
from dataclasses import dataclass from dataclasses import dataclass
from http.cookiejar import MozillaCookieJar from http.cookiejar import MozillaCookieJar
from typing import Dict, Optional, Set from typing import Dict, Optional, Set
from urllib.parse import urljoin, urlsplit from urllib.parse import urljoin
import mutagen import mutagen
import pyrfc6266 import pyrfc6266
import requests import requests
import secmail
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from docopt import docopt from docopt import docopt
from tqdm import tqdm from tqdm import tqdm
from guerrillamail import GuerrillaMailSession
from free_bandcamp_downloader import __version__, logger from free_bandcamp_downloader import __version__, logger
from free_bandcamp_downloader.bandcamp_http_adapter import BandcampHTTPAdapter from free_bandcamp_downloader.bandcamp_http_adapter import BandcampHTTPAdapter
@@ -75,20 +75,20 @@ from free_bandcamp_downloader.bandcamp_http_adapter import BandcampHTTPAdapter
@dataclass @dataclass
class BCFreeDownloaderOptions: class BCFreeDownloaderOptions:
country: str = None country: str = "United States"
zipcode: str = None zipcode: str = "00000"
email: str = None email: str = "auto"
format: str = None format: str = "FLAC"
dir: str = None dir: str = "."
@dataclass @dataclass
class BCFreeDownloaderAlbumData: class BCFreeDownloaderAlbumData:
about: str = None about: Optional[str]
credits: str = None credits: Optional[str]
tags: str = None tags: Optional[str]
id: str = None id: str
title: str = None title: Optional[str]
class BCFreeDownloadError(Exception): class BCFreeDownloadError(Exception):
@@ -126,13 +126,12 @@ class BCFreeDownloader:
self.mail_session = None self.mail_session = None
self.mail_album_data: Dict[str, BCFreeDownloaderAlbumData] = {} self.mail_album_data: Dict[str, BCFreeDownloaderAlbumData] = {}
self.unzip = unzip self.unzip = unzip
self.session = None
self._init_downloaded() self._init_downloaded()
self._init_session(cookies_file, identity) self._init_session(cookies_file, identity)
def _init_email(self): def _init_email(self):
self.mail_session = secmail.Client(self.config_dir) self.mail_session = GuerrillaMailSession()
self.options.email = self.mail_session.random_email(1, "1secmail.com")[0] self.options.email = self.mail_session.get_session_state()["email_address"]
def _init_downloaded(self): def _init_downloaded(self):
if self.download_history_file: if self.download_history_file:
@@ -240,7 +239,6 @@ class BCFreeDownloader:
@staticmethod @staticmethod
def _get_album_data_from_soup(soup: BeautifulSoup) -> BCFreeDownloaderAlbumData: def _get_album_data_from_soup(soup: BeautifulSoup) -> BCFreeDownloaderAlbumData:
album_data = BCFreeDownloaderAlbumData()
about = soup.find("div", class_="tralbum-about") about = soup.find("div", class_="tralbum-about")
credits = soup.find("div", class_="tralbum-credits") credits = soup.find("div", class_="tralbum-credits")
tags = [tag.get_text() for tag in soup.find_all("a", class_="tag")] 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']}" id = f"{properties['item_type']}:{properties['item_id']}"
album_data.about = about.get_text("\n") if about else None return BCFreeDownloaderAlbumData(
album_data.credits = credits.get_text("\n") if credits else None about=about.get_text("\n") if about else None,
album_data.tags = ",".join(sorted(tags)) credits=credits.get_text("\n") if credits else None,
album_data.id = id tags=",".join(sorted(tags)),
id=id,
return album_data title=None,
)
def _download_purchased_album( def _download_purchased_album(
self, user_id: int, album_data: BCFreeDownloaderAlbumData self, user_id: int, album_data: BCFreeDownloaderAlbumData
@@ -330,7 +329,6 @@ class BCFreeDownloader:
if head_data["offers"]["price"] == 0.0: if head_data["offers"]["price"] == 0.0:
if tralbum_data["current"]["require_email"]: if tralbum_data["current"]["require_email"]:
raise BCFreeDownloadError(f"{url} requires email. Skipping...")
if not self.options.email or self.options.email == "auto": if not self.options.email or self.options.email == "auto":
self._init_email() self._init_email()
logger.info(f"{url} requires email") logger.info(f"{url} requires email")
@@ -414,7 +412,7 @@ class BCFreeDownloader:
try: try:
url_type = soup.head.find("meta", attrs={"property": "og:type"})["content"] 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.") raise BCFreeDownloadError(f"{url} does not have an og:type property.")
if url_type == "album" or url_type == "song": if url_type == "album" or url_type == "song":
@@ -429,24 +427,27 @@ class BCFreeDownloader:
while (expected_emails := len(self.mail_album_data)) > 0: while (expected_emails := len(self.mail_album_data)) > 0:
logger.info(f"Waiting for {expected_emails} emails from Bandcamp...") logger.info(f"Waiting for {expected_emails} emails from Bandcamp...")
time.sleep(5) time.sleep(5)
for email in self.mail_session.get_inbox(self.options.email): for email in self.mail_session.get_email_list():
if email.id not in checked_ids: email_id = email.guid
checked_ids.add(email.id) if email_id not in checked_ids:
checked_ids.add(email_id)
if ( if (
email.from_address.endswith("@email.bandcamp.com") email.sender == "noreply@bandcamp.com"
and "download" in email.subject and "download" in email.subject
): ):
logger.info(f'Received email "{email.subject}"') logger.info(f'Received email "{email.subject}"')
email = self.mail_session.get_message( content = self.mail_session.get_email(email_id).body
self.options.email, email.id match = self.LINK_REGEX.search(content)
)
match = self.LINK_REGEX.search(email.html_body)
if match: if match:
download_url = match.group("url") download_url = match.group("url")
album_id = self._download_file( album_id = self._download_file(
download_url, self.options.format download_url, self.options.format
) )
self.mail_album_data.pop(album_id) self.mail_album_data.pop(album_id)
else:
logger.error(
f"Could not find download URL in body: {content}"
)
class BCFreeDownloaderConfig: class BCFreeDownloaderConfig:
Generated
+15 -129
View File
@@ -1,41 +1,5 @@
# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. # 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]] [[package]]
name = "beautifulsoup4" name = "beautifulsoup4"
version = "4.12.3" version = "4.12.3"
@@ -189,76 +153,6 @@ files = [
{file = "docopt_ng-0.9.0.tar.gz", hash = "sha256:91c6da10b5bb6f2e9e25345829fb8278c78af019f6fc40887ad49b060483b1d7"}, {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]] [[package]]
name = "idna" name = "idna"
version = "3.8" version = "3.8"
@@ -309,6 +203,20 @@ files = [
[package.dependencies] [package.dependencies]
pyparsing = ">=3.0.7,<3.1.0" 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]] [[package]]
name = "requests" name = "requests"
version = "2.32.3" version = "2.32.3"
@@ -330,17 +238,6 @@ urllib3 = ">=1.21.1,<3"
socks = ["PySocks (>=1.5.6,!=1.5.7)"] socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 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]] [[package]]
name = "soupsieve" name = "soupsieve"
version = "2.6" version = "2.6"
@@ -372,17 +269,6 @@ notebook = ["ipywidgets (>=6)"]
slack = ["slack-sdk"] slack = ["slack-sdk"]
telegram = ["requests"] 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]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.2.2" version = "2.2.2"
@@ -403,4 +289,4 @@ zstd = ["zstandard (>=0.18.0)"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "b167eea04fb32fb22688a58b4a4cab72d2ad7bd468f82b19fb06bb5c20271dd9" content-hash = "85da27e91df1a1842bad13ae922fb9d9f63d82795c8665a3aeb91a7be43a0d8c"
+2 -2
View File
@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "free-bandcamp-downloader" 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" description = "Download free and name-your-price albums from Bandcamp in lossless quality"
authors = ["7x11x13 <x7x11x13@gmail.com>"] authors = ["7x11x13 <x7x11x13@gmail.com>"]
license = "MIT" license = "MIT"
@@ -16,8 +16,8 @@ beautifulsoup4 = "^4.12.2"
tqdm = "^4.66.1" tqdm = "^4.66.1"
pyrfc6266 = "^1.0.2" pyrfc6266 = "^1.0.2"
mutagen = "^1.47.0" mutagen = "^1.47.0"
1secmail = "^1.1.0"
docopt-ng = "^0.9.0" docopt-ng = "^0.9.0"
python-guerrillamail = "^0.2.0"
[build-system] [build-system]