kindleify 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kindleify-0.1.0/PKG-INFO +73 -0
- kindleify-0.1.0/README.md +50 -0
- kindleify-0.1.0/pyproject.toml +35 -0
- kindleify-0.1.0/setup.cfg +4 -0
- kindleify-0.1.0/src/kindleify/cli.py +96 -0
- kindleify-0.1.0/src/kindleify/config.py +61 -0
- kindleify-0.1.0/src/kindleify/converter/epub_builder.py +33 -0
- kindleify-0.1.0/src/kindleify/converter/pdf.py +51 -0
- kindleify-0.1.0/src/kindleify/converter/url.py +55 -0
- kindleify-0.1.0/src/kindleify/delivery/kindle.py +42 -0
- kindleify-0.1.0/src/kindleify/utils.py +15 -0
- kindleify-0.1.0/src/kindleify.egg-info/PKG-INFO +73 -0
- kindleify-0.1.0/src/kindleify.egg-info/SOURCES.txt +15 -0
- kindleify-0.1.0/src/kindleify.egg-info/dependency_links.txt +1 -0
- kindleify-0.1.0/src/kindleify.egg-info/entry_points.txt +2 -0
- kindleify-0.1.0/src/kindleify.egg-info/requires.txt +8 -0
- kindleify-0.1.0/src/kindleify.egg-info/top_level.txt +1 -0
kindleify-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kindleify
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convert URLs and PDFs to EPUB for Kindle
|
|
5
|
+
Author-email: Your Name <your@email.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: kindle,epub,pdf,converter,ebook
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Python: >=3.14
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: click>=8.3.2
|
|
16
|
+
Requires-Dist: ebooklib>=0.20
|
|
17
|
+
Requires-Dist: pypdf>=4.0.0
|
|
18
|
+
Requires-Dist: pillow>=12.0.0
|
|
19
|
+
Requires-Dist: platformdirs>=4.0.0
|
|
20
|
+
Requires-Dist: requests
|
|
21
|
+
Requires-Dist: trafilatura
|
|
22
|
+
Requires-Dist: twine>=6.2.0
|
|
23
|
+
|
|
24
|
+
# Kindleify
|
|
25
|
+
|
|
26
|
+
Convert URLs and PDFs to EPUB format for Kindle.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install kindleify
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Convert URL to EPUB
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
kindleify url "https://example.com/article" -o article.epub
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Convert PDF to EPUB
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
kindleify pdf document.pdf -o book.epub
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Send to Kindle
|
|
49
|
+
|
|
50
|
+
First, configure your Kindle email:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
kindleify config set
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Then send EPUB to your Kindle:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
kindleify send book.epub
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Commands
|
|
63
|
+
|
|
64
|
+
- `kindleify url` - Convert URL to EPUB
|
|
65
|
+
- `kindleify pdf` - Convert PDF to EPUB
|
|
66
|
+
- `kindleify send` - Send EPUB to Kindle
|
|
67
|
+
- `kindleify config set` - Configure Kindle credentials
|
|
68
|
+
- `kindleify config show` - Show current configuration
|
|
69
|
+
- `kindleify config clear` - Clear stored credentials
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Kindleify
|
|
2
|
+
|
|
3
|
+
Convert URLs and PDFs to EPUB format for Kindle.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install kindleify
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Convert URL to EPUB
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
kindleify url "https://example.com/article" -o article.epub
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Convert PDF to EPUB
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
kindleify pdf document.pdf -o book.epub
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Send to Kindle
|
|
26
|
+
|
|
27
|
+
First, configure your Kindle email:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
kindleify config set
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then send EPUB to your Kindle:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
kindleify send book.epub
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Commands
|
|
40
|
+
|
|
41
|
+
- `kindleify url` - Convert URL to EPUB
|
|
42
|
+
- `kindleify pdf` - Convert PDF to EPUB
|
|
43
|
+
- `kindleify send` - Send EPUB to Kindle
|
|
44
|
+
- `kindleify config set` - Configure Kindle credentials
|
|
45
|
+
- `kindleify config show` - Show current configuration
|
|
46
|
+
- `kindleify config clear` - Clear stored credentials
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
MIT
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kindleify"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Convert URLs and PDFs to EPUB for Kindle"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.14"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Your Name", email = "your@email.com"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["kindle", "epub", "pdf", "converter", "ebook"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: End Users/Desktop",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"click>=8.3.2",
|
|
25
|
+
"ebooklib>=0.20",
|
|
26
|
+
"pypdf>=4.0.0",
|
|
27
|
+
"pillow>=12.0.0",
|
|
28
|
+
"platformdirs>=4.0.0",
|
|
29
|
+
"requests",
|
|
30
|
+
"trafilatura",
|
|
31
|
+
"twine>=6.2.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
kindleify = "kindleify.cli:cli"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from kindleify import config as cfg
|
|
4
|
+
from kindleify.converter.url import url_to_epub
|
|
5
|
+
from kindleify.converter.pdf import pdf_to_epub
|
|
6
|
+
from kindleify.delivery.kindle import send_to_kindle
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
def cli():
|
|
11
|
+
"""Kindleify CLI"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@cli.command()
|
|
16
|
+
@click.argument("url")
|
|
17
|
+
@click.option("-o", "--output", default=None, help="Output EPUB file")
|
|
18
|
+
def url(url, output):
|
|
19
|
+
"""Convert URL to EPUB"""
|
|
20
|
+
path = url_to_epub(url, output)
|
|
21
|
+
click.echo(f"Saved EPUB: {path}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@cli.command()
|
|
25
|
+
@click.argument("pdf_path")
|
|
26
|
+
@click.option("-o", "--output", default="output.epub", help="Output EPUB file")
|
|
27
|
+
def pdf(pdf_path, output):
|
|
28
|
+
"""Convert PDF to EPUB"""
|
|
29
|
+
pdf_to_epub(pdf_path, output)
|
|
30
|
+
click.echo(f"Saved EPUB: {output}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@cli.command()
|
|
34
|
+
@click.argument("file")
|
|
35
|
+
@click.option("--to", default=None, help="Kindle email address")
|
|
36
|
+
@click.option("--from-email", default=None, help="Sender email")
|
|
37
|
+
@click.option("--password", default=None, help="App password")
|
|
38
|
+
def send(file, to, from_email, password):
|
|
39
|
+
"""Send EPUB to Kindle"""
|
|
40
|
+
try:
|
|
41
|
+
send_to_kindle(file, to, from_email, password)
|
|
42
|
+
click.echo(f"Sent {file}")
|
|
43
|
+
except ValueError as e:
|
|
44
|
+
raise click.ClickException(str(e))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@cli.group()
|
|
48
|
+
def config():
|
|
49
|
+
"""Manage Kindleify configuration"""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@config.command("set")
|
|
54
|
+
def config_set():
|
|
55
|
+
"""Set Kindle email credentials"""
|
|
56
|
+
click.echo("Configure your Kindle email settings:")
|
|
57
|
+
|
|
58
|
+
to_email = click.prompt("Kindle email address", type=str)
|
|
59
|
+
from_email = click.prompt("Sender email (Gmail)", type=str)
|
|
60
|
+
password = click.prompt("Gmail app password", type=str, hide_input=True)
|
|
61
|
+
|
|
62
|
+
cfg.set_kindle_config(to_email, from_email, password)
|
|
63
|
+
click.echo("Configuration saved!")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@config.command("show")
|
|
67
|
+
def config_show():
|
|
68
|
+
"""Show current configuration"""
|
|
69
|
+
kindle_cfg = cfg.get_kindle_config()
|
|
70
|
+
|
|
71
|
+
if not kindle_cfg:
|
|
72
|
+
click.echo("No configuration found. Run: kindleify config set")
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
to_email = kindle_cfg.get("to_email", "")
|
|
76
|
+
from_email = kindle_cfg.get("from_email", "")
|
|
77
|
+
password = kindle_cfg.get("password", "")
|
|
78
|
+
masked = "•" * 8 if password else ""
|
|
79
|
+
|
|
80
|
+
click.echo(f"Kindle email: {to_email}")
|
|
81
|
+
click.echo(f"Sender email: {from_email}")
|
|
82
|
+
click.echo(f"Password: {masked}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@config.command("clear")
|
|
86
|
+
def config_clear():
|
|
87
|
+
"""Clear stored configuration"""
|
|
88
|
+
if not cfg.has_kindle_config():
|
|
89
|
+
click.echo("No configuration to clear.")
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
if click.confirm("Clear all stored credentials?"):
|
|
93
|
+
cfg.clear_kindle_config()
|
|
94
|
+
click.echo("Configuration cleared.")
|
|
95
|
+
else:
|
|
96
|
+
click.echo("Cancelled.")
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tomllib
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import platformdirs
|
|
6
|
+
|
|
7
|
+
CONFIG_DIR = Path(platformdirs.user_config_dir("kindleify"))
|
|
8
|
+
CONFIG_FILE = CONFIG_DIR / "config.toml"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_config() -> dict:
|
|
12
|
+
"""Load config from file, return empty dict if not found."""
|
|
13
|
+
if not CONFIG_FILE.exists():
|
|
14
|
+
return {}
|
|
15
|
+
with open(CONFIG_FILE, "rb") as f:
|
|
16
|
+
return tomllib.load(f)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def save_config(config: dict):
|
|
20
|
+
"""Save config to file."""
|
|
21
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
lines = []
|
|
23
|
+
for section, values in config.items():
|
|
24
|
+
if isinstance(values, dict):
|
|
25
|
+
lines.append(f"[{section}]")
|
|
26
|
+
for key, value in values.items():
|
|
27
|
+
lines.append(f'{key} = "{value}"')
|
|
28
|
+
lines.append("")
|
|
29
|
+
with open(CONFIG_FILE, "w") as f:
|
|
30
|
+
f.write("\n".join(lines))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_kindle_config() -> dict:
|
|
34
|
+
"""Get Kindle-specific config (kindle section)."""
|
|
35
|
+
config = load_config()
|
|
36
|
+
return config.get("kindle", {})
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def set_kindle_config(to_email: str, from_email: str, password: str):
|
|
40
|
+
"""Save Kindle credentials to config."""
|
|
41
|
+
config = load_config()
|
|
42
|
+
config["kindle"] = {
|
|
43
|
+
"to_email": to_email,
|
|
44
|
+
"from_email": from_email,
|
|
45
|
+
"password": password,
|
|
46
|
+
}
|
|
47
|
+
save_config(config)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def clear_kindle_config():
|
|
51
|
+
"""Remove Kindle credentials from config."""
|
|
52
|
+
config = load_config()
|
|
53
|
+
if "kindle" in config:
|
|
54
|
+
del config["kindle"]
|
|
55
|
+
save_config(config)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def has_kindle_config() -> bool:
|
|
59
|
+
"""Check if Kindle credentials are configured."""
|
|
60
|
+
cfg = get_kindle_config()
|
|
61
|
+
return bool(cfg.get("to_email") and cfg.get("from_email") and cfg.get("password"))
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from ebooklib import epub
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EpubBuilder:
|
|
5
|
+
def __init__(self, title: str):
|
|
6
|
+
self.book = epub.EpubBook()
|
|
7
|
+
self.book.set_identifier("kindleify")
|
|
8
|
+
self.book.set_title(title)
|
|
9
|
+
self.book.set_language("en")
|
|
10
|
+
self.chapters = []
|
|
11
|
+
|
|
12
|
+
def add_chapter(self, title: str, content: str):
|
|
13
|
+
chapter = epub.EpubHtml(title=title, file_name=f"{title}.xhtml", lang="en")
|
|
14
|
+
chapter.set_content(content)
|
|
15
|
+
|
|
16
|
+
self.book.add_item(chapter)
|
|
17
|
+
self.chapters.append(chapter)
|
|
18
|
+
|
|
19
|
+
def build(self, output_path: str):
|
|
20
|
+
# Create nav first
|
|
21
|
+
nav = epub.EpubNav(file_name="nav.xhtml")
|
|
22
|
+
self.book.add_item(nav)
|
|
23
|
+
|
|
24
|
+
# Set TOC
|
|
25
|
+
self.book.toc = tuple(self.chapters)
|
|
26
|
+
|
|
27
|
+
# Set spine - reading order (nav first, then chapters)
|
|
28
|
+
self.book.spine = [nav] + self.chapters
|
|
29
|
+
|
|
30
|
+
# Add NCX for backward compatibility
|
|
31
|
+
self.book.add_item(epub.EpubNcx())
|
|
32
|
+
|
|
33
|
+
epub.write_epub(output_path, self.book)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from pypdf import PdfReader
|
|
2
|
+
from ebooklib import epub
|
|
3
|
+
|
|
4
|
+
from kindleify.converter.epub_builder import EpubBuilder
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def pdf_to_epub(input_path: str, output_path: str) -> str:
|
|
8
|
+
"""
|
|
9
|
+
Convert PDF to EPUB using pypdf.
|
|
10
|
+
"""
|
|
11
|
+
reader = PdfReader(input_path)
|
|
12
|
+
|
|
13
|
+
title = reader.metadata.get("/Title", "") if reader.metadata else ""
|
|
14
|
+
if not title:
|
|
15
|
+
title = "Untitled"
|
|
16
|
+
|
|
17
|
+
book = epub.EpubBook()
|
|
18
|
+
book.set_identifier("kindleify")
|
|
19
|
+
book.set_title(title)
|
|
20
|
+
book.set_language("en")
|
|
21
|
+
|
|
22
|
+
chapters = []
|
|
23
|
+
for i, page in enumerate(reader.pages):
|
|
24
|
+
text = page.extract_text()
|
|
25
|
+
if not text.strip():
|
|
26
|
+
continue
|
|
27
|
+
|
|
28
|
+
chapter = epub.EpubHtml(
|
|
29
|
+
title=f"Page {i + 1}", file_name=f"page_{i + 1}.xhtml", lang="en"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
html_content = f"""<html>
|
|
33
|
+
<head><title>Page {i + 1}</title></head>
|
|
34
|
+
<body>
|
|
35
|
+
<pre style="font-family: Georgia, serif; font-size: 1em; line-height: 1.6;">{text}</pre>
|
|
36
|
+
</body>
|
|
37
|
+
</html>"""
|
|
38
|
+
chapter.set_content(html_content)
|
|
39
|
+
|
|
40
|
+
book.add_item(chapter)
|
|
41
|
+
chapters.append(chapter)
|
|
42
|
+
|
|
43
|
+
nav = epub.EpubNav(file_name="nav.xhtml")
|
|
44
|
+
book.add_item(nav)
|
|
45
|
+
|
|
46
|
+
book.toc = tuple(chapters)
|
|
47
|
+
book.spine = [nav] + chapters
|
|
48
|
+
book.add_item(epub.EpubNcx())
|
|
49
|
+
|
|
50
|
+
epub.write_epub(output_path, book)
|
|
51
|
+
return output_path
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import trafilatura
|
|
2
|
+
from trafilatura.metadata import extract_metadata
|
|
3
|
+
|
|
4
|
+
from kindleify.converter.epub_builder import EpubBuilder
|
|
5
|
+
from kindleify.utils import sanitize_filename
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def url_to_epub(url: str, output_path: str | None = None) -> str:
|
|
9
|
+
"""
|
|
10
|
+
Convert a URL into an EPUB file.
|
|
11
|
+
Returns the output file path.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
downloaded = trafilatura.fetch_url(url)
|
|
15
|
+
if not downloaded:
|
|
16
|
+
raise ValueError("Failed to fetch URL content")
|
|
17
|
+
|
|
18
|
+
text = trafilatura.extract(downloaded, output_format="html", include_comments=False)
|
|
19
|
+
if not text:
|
|
20
|
+
raise ValueError("Failed to extract readable content")
|
|
21
|
+
|
|
22
|
+
body_content = (
|
|
23
|
+
text.replace("<html>", "")
|
|
24
|
+
.replace("</html>", "")
|
|
25
|
+
.replace("<body>", "")
|
|
26
|
+
.replace("</body>", "")
|
|
27
|
+
.strip()
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
metadata = extract_metadata(downloaded)
|
|
31
|
+
title = metadata.title if metadata and metadata.title else "Untitled"
|
|
32
|
+
|
|
33
|
+
html_content = f"""<html>
|
|
34
|
+
<head>
|
|
35
|
+
<title>{title}</title>
|
|
36
|
+
<style>
|
|
37
|
+
body {{ font-family: Georgia, serif; font-size: 1em; line-height: 1.6; padding: 1em; }}
|
|
38
|
+
p {{ margin-bottom: 1em; }}
|
|
39
|
+
li {{ margin-bottom: 0.5em; }}
|
|
40
|
+
</style>
|
|
41
|
+
</head>
|
|
42
|
+
<body>
|
|
43
|
+
{body_content}
|
|
44
|
+
</body>
|
|
45
|
+
</html>"""
|
|
46
|
+
|
|
47
|
+
if not output_path:
|
|
48
|
+
filename = sanitize_filename(title) or "book"
|
|
49
|
+
output_path = f"{filename}.epub"
|
|
50
|
+
|
|
51
|
+
builder = EpubBuilder(title)
|
|
52
|
+
builder.add_chapter("Content", html_content)
|
|
53
|
+
builder.build(output_path)
|
|
54
|
+
|
|
55
|
+
return output_path
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import smtplib
|
|
2
|
+
from email.message import EmailMessage
|
|
3
|
+
|
|
4
|
+
from kindleify import config as cfg
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def send_to_kindle(
|
|
8
|
+
file_path: str,
|
|
9
|
+
to_email: str | None = None,
|
|
10
|
+
from_email: str | None = None,
|
|
11
|
+
app_password: str | None = None,
|
|
12
|
+
):
|
|
13
|
+
"""
|
|
14
|
+
Send EPUB file to Kindle via email.
|
|
15
|
+
Uses stored credentials if not provided as arguments.
|
|
16
|
+
"""
|
|
17
|
+
stored = cfg.get_kindle_config()
|
|
18
|
+
|
|
19
|
+
to_email = to_email or stored.get("to_email")
|
|
20
|
+
from_email = from_email or stored.get("from_email")
|
|
21
|
+
app_password = app_password or stored.get("password")
|
|
22
|
+
|
|
23
|
+
if not to_email:
|
|
24
|
+
raise ValueError("Kindle email not configured. Run: kindleify config")
|
|
25
|
+
if not from_email or not app_password:
|
|
26
|
+
raise ValueError("Sender credentials not configured. Run: kindleify config")
|
|
27
|
+
|
|
28
|
+
msg = EmailMessage()
|
|
29
|
+
msg["Subject"] = "Kindleify Book"
|
|
30
|
+
msg["From"] = from_email
|
|
31
|
+
msg["To"] = to_email
|
|
32
|
+
|
|
33
|
+
msg.set_content("Sent via Kindleify")
|
|
34
|
+
|
|
35
|
+
with open(file_path, "rb") as f:
|
|
36
|
+
msg.add_attachment(
|
|
37
|
+
f.read(), maintype="application", subtype="epub+zip", filename="book.epub"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp:
|
|
41
|
+
smtp.login(from_email, app_password)
|
|
42
|
+
smtp.send_message(msg)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
def text_to_html(text: str) -> str:
|
|
4
|
+
"""
|
|
5
|
+
Convert plain text into basic HTML paragraphs.
|
|
6
|
+
"""
|
|
7
|
+
paragraphs = text.split("\n\n")
|
|
8
|
+
html = "".join(f"<p>{p.strip()}</p>" for p in paragraphs if p.strip())
|
|
9
|
+
return html
|
|
10
|
+
|
|
11
|
+
def sanitize_filename(name: str) -> str:
|
|
12
|
+
"""
|
|
13
|
+
Remove invalid filename characters.
|
|
14
|
+
"""
|
|
15
|
+
return re.sub(r'[<>:"/\\|?*]+', "", name).strip().replace(" ", "_")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kindleify
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convert URLs and PDFs to EPUB for Kindle
|
|
5
|
+
Author-email: Your Name <your@email.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: kindle,epub,pdf,converter,ebook
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Python: >=3.14
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: click>=8.3.2
|
|
16
|
+
Requires-Dist: ebooklib>=0.20
|
|
17
|
+
Requires-Dist: pypdf>=4.0.0
|
|
18
|
+
Requires-Dist: pillow>=12.0.0
|
|
19
|
+
Requires-Dist: platformdirs>=4.0.0
|
|
20
|
+
Requires-Dist: requests
|
|
21
|
+
Requires-Dist: trafilatura
|
|
22
|
+
Requires-Dist: twine>=6.2.0
|
|
23
|
+
|
|
24
|
+
# Kindleify
|
|
25
|
+
|
|
26
|
+
Convert URLs and PDFs to EPUB format for Kindle.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install kindleify
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Convert URL to EPUB
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
kindleify url "https://example.com/article" -o article.epub
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Convert PDF to EPUB
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
kindleify pdf document.pdf -o book.epub
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Send to Kindle
|
|
49
|
+
|
|
50
|
+
First, configure your Kindle email:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
kindleify config set
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Then send EPUB to your Kindle:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
kindleify send book.epub
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Commands
|
|
63
|
+
|
|
64
|
+
- `kindleify url` - Convert URL to EPUB
|
|
65
|
+
- `kindleify pdf` - Convert PDF to EPUB
|
|
66
|
+
- `kindleify send` - Send EPUB to Kindle
|
|
67
|
+
- `kindleify config set` - Configure Kindle credentials
|
|
68
|
+
- `kindleify config show` - Show current configuration
|
|
69
|
+
- `kindleify config clear` - Clear stored credentials
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/kindleify/cli.py
|
|
4
|
+
src/kindleify/config.py
|
|
5
|
+
src/kindleify/utils.py
|
|
6
|
+
src/kindleify.egg-info/PKG-INFO
|
|
7
|
+
src/kindleify.egg-info/SOURCES.txt
|
|
8
|
+
src/kindleify.egg-info/dependency_links.txt
|
|
9
|
+
src/kindleify.egg-info/entry_points.txt
|
|
10
|
+
src/kindleify.egg-info/requires.txt
|
|
11
|
+
src/kindleify.egg-info/top_level.txt
|
|
12
|
+
src/kindleify/converter/epub_builder.py
|
|
13
|
+
src/kindleify/converter/pdf.py
|
|
14
|
+
src/kindleify/converter/url.py
|
|
15
|
+
src/kindleify/delivery/kindle.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
kindleify
|