streamlit-mini-toolkit 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.
- streamlit_mini_toolkit-0.1.0/.gitignore +21 -0
- streamlit_mini_toolkit-0.1.0/.streamlit/config.toml +16 -0
- streamlit_mini_toolkit-0.1.0/LICENSE +21 -0
- streamlit_mini_toolkit-0.1.0/PKG-INFO +151 -0
- streamlit_mini_toolkit-0.1.0/README.md +108 -0
- streamlit_mini_toolkit-0.1.0/app.py +111 -0
- streamlit_mini_toolkit-0.1.0/core/__init__.py +1 -0
- streamlit_mini_toolkit-0.1.0/core/net.py +56 -0
- streamlit_mini_toolkit-0.1.0/core/pdf.py +12 -0
- streamlit_mini_toolkit-0.1.0/core/registry.py +75 -0
- streamlit_mini_toolkit-0.1.0/core/ui.py +90 -0
- streamlit_mini_toolkit-0.1.0/mini_toolkit/__init__.py +3 -0
- streamlit_mini_toolkit-0.1.0/mini_toolkit/__main__.py +3 -0
- streamlit_mini_toolkit-0.1.0/mini_toolkit/cli.py +40 -0
- streamlit_mini_toolkit-0.1.0/pyproject.toml +65 -0
- streamlit_mini_toolkit-0.1.0/requirements.txt +26 -0
- streamlit_mini_toolkit-0.1.0/style/style.css +171 -0
- streamlit_mini_toolkit-0.1.0/tools/__init__.py +1 -0
- streamlit_mini_toolkit-0.1.0/tools/background_remover.py +65 -0
- streamlit_mini_toolkit-0.1.0/tools/change_tracker.py +90 -0
- streamlit_mini_toolkit-0.1.0/tools/chat_alert.py +42 -0
- streamlit_mini_toolkit-0.1.0/tools/color_picker.py +40 -0
- streamlit_mini_toolkit-0.1.0/tools/document_converter.py +121 -0
- streamlit_mini_toolkit-0.1.0/tools/file_compressor.py +78 -0
- streamlit_mini_toolkit-0.1.0/tools/github_trending.py +53 -0
- streamlit_mini_toolkit-0.1.0/tools/hash_generator.py +43 -0
- streamlit_mini_toolkit-0.1.0/tools/image_converter.py +63 -0
- streamlit_mini_toolkit-0.1.0/tools/image_downloader.py +67 -0
- streamlit_mini_toolkit-0.1.0/tools/image_enhancer.py +54 -0
- streamlit_mini_toolkit-0.1.0/tools/image_watermarker.py +63 -0
- streamlit_mini_toolkit-0.1.0/tools/ocr.py +93 -0
- streamlit_mini_toolkit-0.1.0/tools/page_speed.py +45 -0
- streamlit_mini_toolkit-0.1.0/tools/password_checker.py +53 -0
- streamlit_mini_toolkit-0.1.0/tools/pdf_tools.py +83 -0
- streamlit_mini_toolkit-0.1.0/tools/qr_code.py +83 -0
- streamlit_mini_toolkit-0.1.0/tools/resume_builder.py +385 -0
- streamlit_mini_toolkit-0.1.0/tools/seo_health.py +64 -0
- streamlit_mini_toolkit-0.1.0/tools/signature.py +72 -0
- streamlit_mini_toolkit-0.1.0/tools/ssl_checker.py +68 -0
- streamlit_mini_toolkit-0.1.0/tools/text_to_speech.py +34 -0
- streamlit_mini_toolkit-0.1.0/tools/text_utilities.py +36 -0
- streamlit_mini_toolkit-0.1.0/tools/url_shortener.py +29 -0
- streamlit_mini_toolkit-0.1.0/tools/website_status.py +39 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[theme]
|
|
2
|
+
base = "dark"
|
|
3
|
+
primaryColor = "#5B8CFF"
|
|
4
|
+
backgroundColor = "#0D1017"
|
|
5
|
+
secondaryBackgroundColor = "#161A23"
|
|
6
|
+
textColor = "#E7E9EE"
|
|
7
|
+
font = "sans serif"
|
|
8
|
+
|
|
9
|
+
[server]
|
|
10
|
+
maxUploadSize = 50
|
|
11
|
+
|
|
12
|
+
[browser]
|
|
13
|
+
gatherUsageStats = false
|
|
14
|
+
|
|
15
|
+
[client]
|
|
16
|
+
toolbarMode = "minimal"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sarif Mia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: streamlit-mini-toolkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 25 everyday utilities (image, document, text and web tools) in one Streamlit app.
|
|
5
|
+
Project-URL: Homepage, https://github.com/sarif-mia/mini-toolkit
|
|
6
|
+
Project-URL: Source, https://github.com/sarif-mia/mini-toolkit
|
|
7
|
+
Project-URL: Tracker, https://github.com/sarif-mia/mini-toolkit/issues
|
|
8
|
+
Author: Sarif Mia
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: image,pdf,qr-code,seo,streamlit,toolkit,utilities
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Utilities
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Requires-Dist: beautifulsoup4>=4.12
|
|
17
|
+
Requires-Dist: fpdf2>=2.7.8
|
|
18
|
+
Requires-Dist: gtts>=2.5
|
|
19
|
+
Requires-Dist: pillow>=10.1
|
|
20
|
+
Requires-Dist: pypdf>=4.0
|
|
21
|
+
Requires-Dist: python-docx>=1.1
|
|
22
|
+
Requires-Dist: qrcode>=7.4
|
|
23
|
+
Requires-Dist: requests>=2.31
|
|
24
|
+
Requires-Dist: streamlit-image-coordinates>=0.1.9
|
|
25
|
+
Requires-Dist: streamlit>=1.50
|
|
26
|
+
Provides-Extra: all
|
|
27
|
+
Requires-Dist: docx2pdf>=0.1.8; (sys_platform != 'linux') and extra == 'all'
|
|
28
|
+
Requires-Dist: opencv-python-headless>=4.8; extra == 'all'
|
|
29
|
+
Requires-Dist: pdf2docx>=0.5.8; extra == 'all'
|
|
30
|
+
Requires-Dist: pytesseract>=0.3.10; extra == 'all'
|
|
31
|
+
Requires-Dist: pywin32; (sys_platform == 'win32') and extra == 'all'
|
|
32
|
+
Requires-Dist: rembg[cpu]>=2.0.50; extra == 'all'
|
|
33
|
+
Provides-Extra: bg
|
|
34
|
+
Requires-Dist: rembg[cpu]>=2.0.50; extra == 'bg'
|
|
35
|
+
Provides-Extra: docs
|
|
36
|
+
Requires-Dist: docx2pdf>=0.1.8; (sys_platform != 'linux') and extra == 'docs'
|
|
37
|
+
Requires-Dist: pdf2docx>=0.5.8; extra == 'docs'
|
|
38
|
+
Requires-Dist: pywin32; (sys_platform == 'win32') and extra == 'docs'
|
|
39
|
+
Provides-Extra: ocr
|
|
40
|
+
Requires-Dist: opencv-python-headless>=4.8; extra == 'ocr'
|
|
41
|
+
Requires-Dist: pytesseract>=0.3.10; extra == 'ocr'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# ๐งฐ Mini Toolkit
|
|
45
|
+
|
|
46
|
+
A collection of 25 everyday utilities โ image editing, document conversion, text tools and website checks โ in one fast, clean Streamlit app. No sign-up, no clutter.
|
|
47
|
+
|
|
48
|
+
## Tools
|
|
49
|
+
|
|
50
|
+
| Category | Tools |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| **Image** | Background Remover ยท Image Converter ยท Image Enhancer ยท Image Watermarker ยท Image Color Picker ยท QR Code Generator ยท OCR Text Extractor |
|
|
53
|
+
| **Documents** | Document Converter (DOCX โ PDF โ TXT) ยท PDF Merge & Split ยท File Compressor ยท Resume Builder ยท Email Signature |
|
|
54
|
+
| **Text & Security** | Text Utilities ยท Text to Speech ยท Hash Generator ยท Password Checker & Generator |
|
|
55
|
+
| **Web & Developer** | Website Status ยท Page Speed Checker ยท SSL Certificate Checker ยท SEO Health Check ยท Website Image Downloader ยท URL Shortener ยท Website Change Tracker ยท GitHub Trending ยท Chat Alert Bot (Slack / Teams / Discord) |
|
|
56
|
+
|
|
57
|
+
Every tool has its own shareable URL, e.g. `/?tool=qr-code`.
|
|
58
|
+
|
|
59
|
+
## Install from PyPI
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
pip install streamlit-mini-toolkit # core tools
|
|
63
|
+
pip install "streamlit-mini-toolkit[all]" # + background remover, OCR, DOCX/PDF conversion
|
|
64
|
+
mini-toolkit # opens the app at http://localhost:8501
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Pick extras individually if you prefer: `[bg]` (Background Remover), `[ocr]` (OCR Text Extractor), `[docs]` (DOCX โ PDF). Streamlit flags pass through, e.g. `mini-toolkit --server.port 8080`.
|
|
68
|
+
|
|
69
|
+
## Quick start (from source)
|
|
70
|
+
|
|
71
|
+
Requires Python 3.9+.
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
git clone https://github.com/sarif-mia/mini-toolkit.git
|
|
75
|
+
cd mini-toolkit
|
|
76
|
+
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
77
|
+
pip install -r requirements.txt
|
|
78
|
+
streamlit run app.py
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The app opens at <http://localhost:8501>.
|
|
82
|
+
|
|
83
|
+
### System dependencies (optional)
|
|
84
|
+
|
|
85
|
+
A few tools call external programs. Everything else works without them.
|
|
86
|
+
|
|
87
|
+
| Feature | Needs |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| OCR Text Extractor | [Tesseract](https://tesseract-ocr.github.io/tessdoc/Installation.html) |
|
|
90
|
+
| DOCX โ PDF | [LibreOffice](https://www.libreoffice.org/) (or Microsoft Word on Windows/macOS) |
|
|
91
|
+
|
|
92
|
+
On Debian/Ubuntu: `sudo xargs apt-get install -y < packages.txt`
|
|
93
|
+
|
|
94
|
+
## Deployment
|
|
95
|
+
|
|
96
|
+
### Streamlit Community Cloud (recommended, free)
|
|
97
|
+
|
|
98
|
+
1. Go to [share.streamlit.io](https://share.streamlit.io) and sign in with GitHub.
|
|
99
|
+
2. **Create app** โ pick this repository, branch `master`, main file `app.py`.
|
|
100
|
+
3. Deploy. `requirements.txt` and `packages.txt` are picked up automatically.
|
|
101
|
+
|
|
102
|
+
### Docker (Render, Railway, Fly.io, Hugging Face Spaces, โฆ)
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
docker build -t mini-toolkit .
|
|
106
|
+
docker run -p 8501:8501 mini-toolkit
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
> **Why not Vercel?** Vercel runs short-lived serverless functions, while Streamlit needs a long-running server with a persistent WebSocket connection. Use one of the options above instead.
|
|
110
|
+
|
|
111
|
+
### Optional secrets
|
|
112
|
+
|
|
113
|
+
The Website Change Tracker can email change summaries. Add these to `.streamlit/secrets.toml` locally, or to your host's secrets settings:
|
|
114
|
+
|
|
115
|
+
```toml
|
|
116
|
+
smtp_server = "smtp.gmail.com"
|
|
117
|
+
smtp_port = 587
|
|
118
|
+
smtp_user = "you@example.com"
|
|
119
|
+
smtp_pass = "app-password"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Project structure
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
app.py Entry point: page config, routing, home page
|
|
126
|
+
core/
|
|
127
|
+
registry.py List of tools and categories (add new tools here)
|
|
128
|
+
ui.py Shared UI helpers (headers, uploads, ZIP export, footer)
|
|
129
|
+
net.py Safe HTTP fetching for the web tools
|
|
130
|
+
tools/ One module per tool, each exposing render()
|
|
131
|
+
style/style.css Custom styling
|
|
132
|
+
.streamlit/ Theme and server config
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Adding a tool
|
|
136
|
+
|
|
137
|
+
1. Create `tools/my_tool.py` with a `render()` function.
|
|
138
|
+
2. Register it in `core/registry.py`:
|
|
139
|
+
```python
|
|
140
|
+
Tool("my-tool", "My Tool", "๐งช", "One-line description.", TEXT, "my_tool"),
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Tools are imported lazily, so a missing optional dependency only affects that one tool.
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
Distributed under the MIT License. See [`LICENSE`](LICENSE).
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
Built by [Sarif](https://sarifmiia.netlify.app/) ยท [GitHub](https://github.com/sarif-mia) ยท [LinkedIn](https://www.linkedin.com/in/sarif-mia/)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# ๐งฐ Mini Toolkit
|
|
2
|
+
|
|
3
|
+
A collection of 25 everyday utilities โ image editing, document conversion, text tools and website checks โ in one fast, clean Streamlit app. No sign-up, no clutter.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
| Category | Tools |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| **Image** | Background Remover ยท Image Converter ยท Image Enhancer ยท Image Watermarker ยท Image Color Picker ยท QR Code Generator ยท OCR Text Extractor |
|
|
10
|
+
| **Documents** | Document Converter (DOCX โ PDF โ TXT) ยท PDF Merge & Split ยท File Compressor ยท Resume Builder ยท Email Signature |
|
|
11
|
+
| **Text & Security** | Text Utilities ยท Text to Speech ยท Hash Generator ยท Password Checker & Generator |
|
|
12
|
+
| **Web & Developer** | Website Status ยท Page Speed Checker ยท SSL Certificate Checker ยท SEO Health Check ยท Website Image Downloader ยท URL Shortener ยท Website Change Tracker ยท GitHub Trending ยท Chat Alert Bot (Slack / Teams / Discord) |
|
|
13
|
+
|
|
14
|
+
Every tool has its own shareable URL, e.g. `/?tool=qr-code`.
|
|
15
|
+
|
|
16
|
+
## Install from PyPI
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
pip install streamlit-mini-toolkit # core tools
|
|
20
|
+
pip install "streamlit-mini-toolkit[all]" # + background remover, OCR, DOCX/PDF conversion
|
|
21
|
+
mini-toolkit # opens the app at http://localhost:8501
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Pick extras individually if you prefer: `[bg]` (Background Remover), `[ocr]` (OCR Text Extractor), `[docs]` (DOCX โ PDF). Streamlit flags pass through, e.g. `mini-toolkit --server.port 8080`.
|
|
25
|
+
|
|
26
|
+
## Quick start (from source)
|
|
27
|
+
|
|
28
|
+
Requires Python 3.9+.
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
git clone https://github.com/sarif-mia/mini-toolkit.git
|
|
32
|
+
cd mini-toolkit
|
|
33
|
+
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
34
|
+
pip install -r requirements.txt
|
|
35
|
+
streamlit run app.py
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The app opens at <http://localhost:8501>.
|
|
39
|
+
|
|
40
|
+
### System dependencies (optional)
|
|
41
|
+
|
|
42
|
+
A few tools call external programs. Everything else works without them.
|
|
43
|
+
|
|
44
|
+
| Feature | Needs |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| OCR Text Extractor | [Tesseract](https://tesseract-ocr.github.io/tessdoc/Installation.html) |
|
|
47
|
+
| DOCX โ PDF | [LibreOffice](https://www.libreoffice.org/) (or Microsoft Word on Windows/macOS) |
|
|
48
|
+
|
|
49
|
+
On Debian/Ubuntu: `sudo xargs apt-get install -y < packages.txt`
|
|
50
|
+
|
|
51
|
+
## Deployment
|
|
52
|
+
|
|
53
|
+
### Streamlit Community Cloud (recommended, free)
|
|
54
|
+
|
|
55
|
+
1. Go to [share.streamlit.io](https://share.streamlit.io) and sign in with GitHub.
|
|
56
|
+
2. **Create app** โ pick this repository, branch `master`, main file `app.py`.
|
|
57
|
+
3. Deploy. `requirements.txt` and `packages.txt` are picked up automatically.
|
|
58
|
+
|
|
59
|
+
### Docker (Render, Railway, Fly.io, Hugging Face Spaces, โฆ)
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
docker build -t mini-toolkit .
|
|
63
|
+
docker run -p 8501:8501 mini-toolkit
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> **Why not Vercel?** Vercel runs short-lived serverless functions, while Streamlit needs a long-running server with a persistent WebSocket connection. Use one of the options above instead.
|
|
67
|
+
|
|
68
|
+
### Optional secrets
|
|
69
|
+
|
|
70
|
+
The Website Change Tracker can email change summaries. Add these to `.streamlit/secrets.toml` locally, or to your host's secrets settings:
|
|
71
|
+
|
|
72
|
+
```toml
|
|
73
|
+
smtp_server = "smtp.gmail.com"
|
|
74
|
+
smtp_port = 587
|
|
75
|
+
smtp_user = "you@example.com"
|
|
76
|
+
smtp_pass = "app-password"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Project structure
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
app.py Entry point: page config, routing, home page
|
|
83
|
+
core/
|
|
84
|
+
registry.py List of tools and categories (add new tools here)
|
|
85
|
+
ui.py Shared UI helpers (headers, uploads, ZIP export, footer)
|
|
86
|
+
net.py Safe HTTP fetching for the web tools
|
|
87
|
+
tools/ One module per tool, each exposing render()
|
|
88
|
+
style/style.css Custom styling
|
|
89
|
+
.streamlit/ Theme and server config
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Adding a tool
|
|
93
|
+
|
|
94
|
+
1. Create `tools/my_tool.py` with a `render()` function.
|
|
95
|
+
2. Register it in `core/registry.py`:
|
|
96
|
+
```python
|
|
97
|
+
Tool("my-tool", "My Tool", "๐งช", "One-line description.", TEXT, "my_tool"),
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Tools are imported lazily, so a missing optional dependency only affects that one tool.
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
Distributed under the MIT License. See [`LICENSE`](LICENSE).
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
Built by [Sarif](https://sarifmiia.netlify.app/) ยท [GitHub](https://github.com/sarif-mia) ยท [LinkedIn](https://www.linkedin.com/in/sarif-mia/)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Mini Toolkit โ a collection of everyday utilities in one Streamlit app.
|
|
2
|
+
|
|
3
|
+
Run locally with: streamlit run app.py
|
|
4
|
+
"""
|
|
5
|
+
import html
|
|
6
|
+
import traceback
|
|
7
|
+
|
|
8
|
+
import streamlit as st
|
|
9
|
+
|
|
10
|
+
from core.registry import CATEGORIES, TOOLS, TOOLS_BY_SLUG, Tool, tools_in
|
|
11
|
+
from core.ui import footer, load_css, tool_header
|
|
12
|
+
|
|
13
|
+
st.set_page_config(page_title="Mini Toolkit", page_icon="๐งฐ", layout="wide", initial_sidebar_state="collapsed")
|
|
14
|
+
load_css()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def open_tool(slug=None) -> None:
|
|
18
|
+
if slug:
|
|
19
|
+
st.query_params["tool"] = slug
|
|
20
|
+
else:
|
|
21
|
+
st.query_params.clear()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def render_sidebar(active=None) -> None:
|
|
25
|
+
with st.sidebar:
|
|
26
|
+
st.button("๐ All tools", on_click=open_tool, width="stretch", type="primary" if active is None else "secondary")
|
|
27
|
+
for category in CATEGORIES:
|
|
28
|
+
st.markdown(f"<div class='nav-heading'>{category}</div>", unsafe_allow_html=True)
|
|
29
|
+
for tool in tools_in(category):
|
|
30
|
+
st.button(
|
|
31
|
+
f"{tool.icon} {tool.name}",
|
|
32
|
+
key=f"nav_{tool.slug}",
|
|
33
|
+
on_click=open_tool,
|
|
34
|
+
args=(tool.slug,),
|
|
35
|
+
width="stretch",
|
|
36
|
+
type="primary" if active and active.slug == tool.slug else "tertiary",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def tool_card(tool: Tool) -> None:
|
|
41
|
+
with st.container(border=True):
|
|
42
|
+
st.markdown(
|
|
43
|
+
f"""
|
|
44
|
+
<div class="tool-card">
|
|
45
|
+
<div class="tool-card-icon">{tool.icon}</div>
|
|
46
|
+
<div class="tool-card-title">{html.escape(tool.name)}</div>
|
|
47
|
+
<div class="tool-card-desc">{html.escape(tool.description)}</div>
|
|
48
|
+
</div>
|
|
49
|
+
""",
|
|
50
|
+
unsafe_allow_html=True,
|
|
51
|
+
)
|
|
52
|
+
st.button("Open", key=f"open_{tool.slug}", on_click=open_tool, args=(tool.slug,), width="stretch")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def render_home() -> None:
|
|
56
|
+
st.markdown(
|
|
57
|
+
f"""
|
|
58
|
+
<section class="hero">
|
|
59
|
+
<span class="hero-badge">{len(TOOLS)} free tools ยท no sign-up</span>
|
|
60
|
+
<h1>Everyday tools, <span class="accent">one place.</span></h1>
|
|
61
|
+
<p>Edit images, convert documents, check websites and more โ right in your browser.</p>
|
|
62
|
+
</section>
|
|
63
|
+
""",
|
|
64
|
+
unsafe_allow_html=True,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
query = st.text_input("Search tools", placeholder="๐ Search tools โ e.g. pdf, qr, background", label_visibility="collapsed").strip().lower()
|
|
68
|
+
if query:
|
|
69
|
+
matches = [t for t in TOOLS if query in f"{t.name} {t.description} {t.category}".lower()]
|
|
70
|
+
if not matches:
|
|
71
|
+
st.info(f"No tools match โ{query}โ.")
|
|
72
|
+
return
|
|
73
|
+
sections = {"Search results": matches}
|
|
74
|
+
else:
|
|
75
|
+
sections = {category: tools_in(category) for category in CATEGORIES}
|
|
76
|
+
|
|
77
|
+
for title, tools in sections.items():
|
|
78
|
+
subtitle = CATEGORIES.get(title, f"{len(tools)} match{'es' if len(tools) != 1 else ''}")
|
|
79
|
+
st.markdown(f"<div class='category-heading'><div class='category-title'>{title}</div><span>{subtitle}</span></div>", unsafe_allow_html=True)
|
|
80
|
+
# Build row by row so cards keep their order when columns stack on mobile.
|
|
81
|
+
for row in range(0, len(tools), 4):
|
|
82
|
+
for col, tool in zip(st.columns(4), tools[row:row + 4]):
|
|
83
|
+
with col:
|
|
84
|
+
tool_card(tool)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def render_tool(tool: Tool) -> None:
|
|
88
|
+
st.button("โ All tools", on_click=open_tool, key="back_home", type="tertiary")
|
|
89
|
+
tool_header(tool.icon, tool.name, tool.description)
|
|
90
|
+
try:
|
|
91
|
+
tool.load()()
|
|
92
|
+
except Exception as exc: # keep one broken tool from blanking the whole page
|
|
93
|
+
st.error(f"Something went wrong in {tool.name}: {exc}")
|
|
94
|
+
with st.expander("Technical details"):
|
|
95
|
+
st.code(traceback.format_exc())
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def main() -> None:
|
|
99
|
+
slug = st.query_params.get("tool")
|
|
100
|
+
tool = TOOLS_BY_SLUG.get(slug) if slug else None
|
|
101
|
+
render_sidebar(tool)
|
|
102
|
+
if slug and not tool:
|
|
103
|
+
st.warning(f"Unknown tool โ{slug}โ. Showing all tools instead.")
|
|
104
|
+
if tool:
|
|
105
|
+
render_tool(tool)
|
|
106
|
+
else:
|
|
107
|
+
render_home()
|
|
108
|
+
footer()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Shared building blocks for the Mini Toolkit app."""
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""HTTP helpers for the web tools.
|
|
2
|
+
|
|
3
|
+
All outbound requests go through :func:`fetch`, which normalises the URL and
|
|
4
|
+
refuses to reach private or loopback addresses, so a publicly hosted copy of the
|
|
5
|
+
app can't be used to probe the host's internal network.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import ipaddress
|
|
10
|
+
import socket
|
|
11
|
+
from urllib.parse import urlparse
|
|
12
|
+
|
|
13
|
+
import requests
|
|
14
|
+
|
|
15
|
+
USER_AGENT = "Mozilla/5.0 (compatible; MiniToolkit/2.0; +https://github.com/sarif-mia/mini-toolkit)"
|
|
16
|
+
DEFAULT_TIMEOUT = 10
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UnsafeURLError(ValueError):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def normalize_url(url: str) -> str:
|
|
24
|
+
url = url.strip()
|
|
25
|
+
if url and not url.startswith(("http://", "https://")):
|
|
26
|
+
url = "https://" + url
|
|
27
|
+
return url
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def ensure_public_url(url: str) -> str:
|
|
31
|
+
"""Return the normalised URL, or raise if it is malformed or points at a private address."""
|
|
32
|
+
url = normalize_url(url)
|
|
33
|
+
host = urlparse(url).hostname
|
|
34
|
+
if not host:
|
|
35
|
+
raise UnsafeURLError("Please enter a valid URL, e.g. https://example.com")
|
|
36
|
+
try:
|
|
37
|
+
infos = socket.getaddrinfo(host, None)
|
|
38
|
+
except socket.gaierror:
|
|
39
|
+
raise UnsafeURLError(f"Could not resolve host '{host}'.")
|
|
40
|
+
for info in infos:
|
|
41
|
+
ip = ipaddress.ip_address(info[4][0])
|
|
42
|
+
if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved or ip.is_multicast:
|
|
43
|
+
raise UnsafeURLError(f"'{host}' resolves to a private address and can't be checked.")
|
|
44
|
+
return url
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def fetch(url: str, timeout: float = DEFAULT_TIMEOUT, max_redirects: int = 5, **kwargs) -> requests.Response:
|
|
48
|
+
"""GET a public URL, re-validating every redirect hop."""
|
|
49
|
+
headers = {"User-Agent": USER_AGENT, **kwargs.pop("headers", {})}
|
|
50
|
+
for _ in range(max_redirects + 1):
|
|
51
|
+
url = ensure_public_url(url)
|
|
52
|
+
response = requests.get(url, timeout=timeout, headers=headers, allow_redirects=False, **kwargs)
|
|
53
|
+
if not response.is_redirect:
|
|
54
|
+
return response
|
|
55
|
+
url = requests.compat.urljoin(url, response.headers["Location"])
|
|
56
|
+
raise requests.TooManyRedirects(f"Exceeded {max_redirects} redirects.")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Helpers for generating PDFs with fpdf2's built-in fonts."""
|
|
2
|
+
|
|
3
|
+
# Built-in PDF fonts only cover Latin-1, so map common typographic characters
|
|
4
|
+
# to ASCII equivalents and replace anything else instead of crashing.
|
|
5
|
+
_TYPOGRAPHIC = str.maketrans({
|
|
6
|
+
"โ": "-", "โ": "-", "โ": "'", "โ": "'",
|
|
7
|
+
"โ": '"', "โ": '"', "โข": "*", "โฆ": "...", "ย ": " ",
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def pdf_safe(text: str) -> str:
|
|
12
|
+
return text.translate(_TYPOGRAPHIC).encode("latin-1", errors="replace").decode("latin-1")
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Central registry of every tool in the toolkit.
|
|
2
|
+
|
|
3
|
+
Each tool points at a module in ``tools/`` that exposes a ``render()`` function.
|
|
4
|
+
Modules are imported lazily, so a missing optional dependency only affects the
|
|
5
|
+
tool that needs it instead of taking down the whole app.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import importlib
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Callable, Dict, List
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class Tool:
|
|
16
|
+
slug: str
|
|
17
|
+
name: str
|
|
18
|
+
icon: str
|
|
19
|
+
description: str
|
|
20
|
+
category: str
|
|
21
|
+
module: str
|
|
22
|
+
|
|
23
|
+
def load(self) -> Callable[[], None]:
|
|
24
|
+
return importlib.import_module(f"tools.{self.module}").render
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
IMAGE = "Image"
|
|
28
|
+
DOCUMENT = "Documents"
|
|
29
|
+
TEXT = "Text & Security"
|
|
30
|
+
WEB = "Web & Developer"
|
|
31
|
+
|
|
32
|
+
CATEGORIES: Dict[str, str] = {
|
|
33
|
+
IMAGE: "Edit, convert and analyse images.",
|
|
34
|
+
DOCUMENT: "Convert, merge and create documents.",
|
|
35
|
+
TEXT: "Transform text, hashes and passwords.",
|
|
36
|
+
WEB: "Inspect websites and automate small tasks.",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
TOOLS: List[Tool] = [
|
|
40
|
+
# Image
|
|
41
|
+
Tool("background-remover", "Background Remover", "๐ช", "Remove image backgrounds with AI models.", IMAGE, "background_remover"),
|
|
42
|
+
Tool("image-converter", "Image Converter", "๐", "Convert images between PNG, JPG, WEBP, BMP and TIFF.", IMAGE, "image_converter"),
|
|
43
|
+
Tool("image-enhancer", "Image Enhancer", "โจ", "Adjust brightness, contrast, colour and sharpness.", IMAGE, "image_enhancer"),
|
|
44
|
+
Tool("image-watermarker", "Image Watermarker", "๐ง", "Stamp a text watermark onto your images.", IMAGE, "image_watermarker"),
|
|
45
|
+
Tool("color-picker", "Image Color Picker", "๐จ", "Click an image to get exact HEX and RGB values.", IMAGE, "color_picker"),
|
|
46
|
+
Tool("qr-code", "QR Code Generator", "๐ณ", "Create QR codes for links, contacts and Wi-Fi.", IMAGE, "qr_code"),
|
|
47
|
+
Tool("ocr", "OCR Text Extractor", "๐", "Extract text from images and screenshots.", IMAGE, "ocr"),
|
|
48
|
+
# Documents
|
|
49
|
+
Tool("document-converter", "Document Converter", "๐", "Convert between DOCX, PDF and TXT.", DOCUMENT, "document_converter"),
|
|
50
|
+
Tool("pdf-tools", "PDF Merge & Split", "๐", "Combine PDFs or extract pages from one.", DOCUMENT, "pdf_tools"),
|
|
51
|
+
Tool("file-compressor", "File Compressor", "๐๏ธ", "Zip files or shrink image file sizes.", DOCUMENT, "file_compressor"),
|
|
52
|
+
Tool("resume-builder", "Resume Builder", "๐งพ", "Build a resume and cover letter as PDF or DOCX.", DOCUMENT, "resume_builder"),
|
|
53
|
+
Tool("email-signature", "Email Signature", "โ๏ธ", "Design an HTML email signature.", DOCUMENT, "signature"),
|
|
54
|
+
# Text & Security
|
|
55
|
+
Tool("text-utilities", "Text Utilities", "๐ค", "Count words, change case and clean up text.", TEXT, "text_utilities"),
|
|
56
|
+
Tool("text-to-speech", "Text to Speech", "๐", "Turn text into downloadable MP3 audio.", TEXT, "text_to_speech"),
|
|
57
|
+
Tool("hash-generator", "Hash Generator", "#๏ธโฃ", "Compute MD5 and SHA hashes of text or files.", TEXT, "hash_generator"),
|
|
58
|
+
Tool("password-checker", "Password Checker", "๐ก๏ธ", "Score password strength and get tips.", TEXT, "password_checker"),
|
|
59
|
+
# Web & Developer
|
|
60
|
+
Tool("website-status", "Website Status", "๐", "Check whether one or many sites are up.", WEB, "website_status"),
|
|
61
|
+
Tool("page-speed", "Page Speed Checker", "โก", "Measure server response and download time.", WEB, "page_speed"),
|
|
62
|
+
Tool("ssl-checker", "SSL Certificate Checker", "๐", "Inspect certificate validity and expiry.", WEB, "ssl_checker"),
|
|
63
|
+
Tool("seo-health", "SEO Health Check", "๐", "Audit titles, meta tags and headings.", WEB, "seo_health"),
|
|
64
|
+
Tool("image-downloader", "Website Image Downloader", "๐ผ๏ธ", "Collect every image on a page as a ZIP.", WEB, "image_downloader"),
|
|
65
|
+
Tool("url-shortener", "URL Shortener", "๐", "Shorten long links with TinyURL.", WEB, "url_shortener"),
|
|
66
|
+
Tool("change-tracker", "Website Change Tracker", "๐", "Diff a page against its last snapshot.", WEB, "change_tracker"),
|
|
67
|
+
Tool("github-trending", "GitHub Trending", "๐", "Discover fast-rising GitHub repositories.", WEB, "github_trending"),
|
|
68
|
+
Tool("chat-alert", "Chat Alert Bot", "๐ฌ", "Send messages to Slack or Teams webhooks.", WEB, "chat_alert"),
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
TOOLS_BY_SLUG: Dict[str, Tool] = {tool.slug: tool for tool in TOOLS}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def tools_in(category: str) -> List[Tool]:
|
|
75
|
+
return [tool for tool in TOOLS if tool.category == category]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Reusable Streamlit UI helpers shared across tools."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import html
|
|
5
|
+
import io
|
|
6
|
+
import zipfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Iterable, List, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
import streamlit as st
|
|
11
|
+
|
|
12
|
+
MAX_UPLOAD_MB = 10
|
|
13
|
+
STYLE_PATH = Path(__file__).resolve().parent.parent / "style" / "style.css"
|
|
14
|
+
GITHUB_URL = "https://github.com/sarif-mia/mini-toolkit"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_css() -> None:
|
|
18
|
+
st.markdown(f"<style>{STYLE_PATH.read_text(encoding='utf-8')}</style>", unsafe_allow_html=True)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def tool_header(icon: str, title: str, description: str) -> None:
|
|
22
|
+
st.markdown(
|
|
23
|
+
f"""
|
|
24
|
+
<div class="tool-header">
|
|
25
|
+
<div class="tool-header-icon">{icon}</div>
|
|
26
|
+
<div>
|
|
27
|
+
<h1>{html.escape(title)}</h1>
|
|
28
|
+
<p>{html.escape(description)}</p>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
""",
|
|
32
|
+
unsafe_allow_html=True,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def section(title: str) -> None:
|
|
37
|
+
st.markdown(f"<div class='section-title'>{html.escape(title)}</div>", unsafe_allow_html=True)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def footer() -> None:
|
|
41
|
+
st.markdown(
|
|
42
|
+
f"""
|
|
43
|
+
<footer class="app-footer">
|
|
44
|
+
<span>Mini Toolkit ยท built by <a href="https://sarifmiia.netlify.app/" target="_blank" rel="noopener">Sarif</a></span>
|
|
45
|
+
<span class="footer-links">
|
|
46
|
+
<a href="{GITHUB_URL}" target="_blank" rel="noopener">GitHub</a>
|
|
47
|
+
<a href="https://www.linkedin.com/in/sarif-mia/" target="_blank" rel="noopener">LinkedIn</a>
|
|
48
|
+
<a href="mailto:sarif@lovegenbd.com">Contact</a>
|
|
49
|
+
</span>
|
|
50
|
+
</footer>
|
|
51
|
+
""",
|
|
52
|
+
unsafe_allow_html=True,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def sanitize_filename(name: str) -> str:
|
|
57
|
+
stem = "".join(c if c.isalnum() or c in "-_" else "_" for c in Path(name).stem)
|
|
58
|
+
return stem or "file"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def image_uploader(key: str, types: Iterable[str] = ("png", "jpg", "jpeg", "webp", "bmp", "tiff")) -> List:
|
|
62
|
+
"""Multi-image uploader that drops files above the size limit with a warning."""
|
|
63
|
+
files = st.file_uploader(
|
|
64
|
+
f"Upload images (max {MAX_UPLOAD_MB} MB each)",
|
|
65
|
+
type=list(types),
|
|
66
|
+
accept_multiple_files=True,
|
|
67
|
+
key=key,
|
|
68
|
+
) or []
|
|
69
|
+
accepted = []
|
|
70
|
+
for f in files:
|
|
71
|
+
if f.size > MAX_UPLOAD_MB * 1024 ** 2:
|
|
72
|
+
st.warning(f"{f.name} is {f.size / 1024 ** 2:.1f} MB and was skipped (limit {MAX_UPLOAD_MB} MB).")
|
|
73
|
+
else:
|
|
74
|
+
accepted.append(f)
|
|
75
|
+
return accepted
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def build_zip(entries: Iterable[Tuple[str, bytes]]) -> bytes:
|
|
79
|
+
buffer = io.BytesIO()
|
|
80
|
+
with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
81
|
+
for name, data in entries:
|
|
82
|
+
zf.writestr(name, data)
|
|
83
|
+
return buffer.getvalue()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def missing_dependency(package: str, extra: Optional[str] = None) -> None:
|
|
87
|
+
message = f"This tool needs the `{package}` package, which isn't installed on this server."
|
|
88
|
+
if extra:
|
|
89
|
+
message += f" {extra}"
|
|
90
|
+
st.error(message)
|