Skip to content

Commit 02b4b8e

Browse files
committed
Merge branch 'master' of https://github.com/7pvd/ffpm
# Conflicts: # ffpm.py
2 parents 4c64bc3 + 74042c5 commit 02b4b8e

File tree

4 files changed

+128
-4
lines changed

4 files changed

+128
-4
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
__pycache__
2+
build
3+
dist

README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<a href="http://zuko.pro/">
2+
<img src="https://avatars0.githubusercontent.com/u/6666271?v=3&s=96" alt="Z-Logo"
3+
title="Halu Universe" align="right" />
4+
</a>
5+
# ffpm
6+
7+
# :fire: FFPM — Firefox Profile Manager :fire:
8+
9+
Simple Firefox Profile Manager, including Import/Export
10+
**Special Feature:** Monitor directory changes before/after actions.
11+
12+
## Description
13+
14+
**ffpm** is a Python-based tool for managing Firefox profiles. It allows you to import/export profiles and monitor directory changes before or after running actions—ideal for troubleshooting, backups, and tracking modifications.
15+
16+
## Features
17+
18+
- List Firefox profiles
19+
- Import and export profiles
20+
- Monitor directory changes before/after actions
21+
- Simple command-line interface
22+
23+
## Installation & Usage
24+
25+
### Recommended: Use Pre-Built Binaries
26+
27+
For most users, download the latest pre-built binary from the [Releases page](https://github.com/7pvd/ffpm/releases).
28+
No Python setup is required—just download and run the binary appropriate for your platform.
29+
Built binaries was tested and confirmed working on Windows 10 & Debian 11.
30+
31+
### Development: Run from Source
32+
33+
For development or testing only:
34+
35+
```bash
36+
git clone https://github.com/7pvd/ffpm.git
37+
cd ffpm
38+
pip install typer watchdog nuitka
39+
python ffpm.py [OPTIONS]
40+
```
41+
42+
## Example Commands
43+
44+
- List profiles:
45+
```bash
46+
ffpm list
47+
```
48+
- Export a profile:
49+
```bash
50+
ffpm export --profile <profile_name> --output <output_path>
51+
```
52+
- Import a profile:
53+
```bash
54+
ffpm import --input <input_path> --name <name>
55+
```
56+
- Monitor directory changes:
57+
```bash
58+
ffpm monitor --dir <directory_path>
59+
```
60+
61+
## Requirements (for development)
62+
63+
- Python 3.x
64+
- [See requirements.txt for dependencies]
65+
66+
## Contributing
67+
68+
Contributions are welcome! Please open an issue or submit a pull request.
69+
70+
## License
71+
72+
GPL v3
73+
74+
## Acknowledgements
75+
76+
Inspired by the need for easy Firefox profile management and monitoring.

ffpm.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
along with this program. If not, see <https://www.gnu.org/licenses/>.
1919
"""
2020

21-
2221
# M""""""""`M dP
2322
# Mmmmmm .M 88
2423
# MMMMP .MMM dP dP 88 .dP .d8888b.
@@ -34,6 +33,9 @@
3433
# * * * * * * * * * * * * * * * * * * * * *
3534
import subprocess
3635
import sys
36+
from functools import cache
37+
38+
from typing_extensions import Annotated
3739

3840
DEPS = ['typer[all]', None, None, 'watchdog'] # None cuz its built-in
3941
DEP_CHECK_NAMES = ['typer', 'zipfile', 'shutil', 'watchdog']
@@ -60,6 +62,7 @@ def ensure_deps():
6062

6163
import signal
6264
from datetime import datetime
65+
import typeguard
6366
from watchdog.observers import Observer
6467
from watchdog.events import FileSystemEventHandler
6568

@@ -68,7 +71,7 @@ def ensure_deps():
6871
FIREFOX_DIR = Path.home() / ".mozilla" / "firefox" # Linux/macOS
6972
PROFILES_INI = FIREFOX_DIR / "profiles.ini"
7073
BACKUP_DIR = Path.home() / "firefox-profile-backups"
71-
74+
typeguard.install_import_hook('ffpm')
7275

7376
def get_profile_path(profile_name_or_path: str) -> Path:
7477
path = Path(profile_name_or_path).expanduser().resolve()
@@ -333,18 +336,48 @@ def _ensureBakDir():
333336
typer.echo(f"❌ Backup directory '{BACKUP_DIR}' is not a directory.")
334337
raise typer.Exit(1)
335338

339+
340+
def _logo(_):
341+
print("""
342+
M\"\"\"\"\"\"\"\"`M dP
343+
Mmmmmm .M 88
344+
MMMMP .MMM dP dP 88 .dP .d8888b.
345+
MMP .MMMMM 88 88 88888" 88' `88
346+
M' .MMMMMMM 88. .88 88 `8b. 88. .88
347+
M M `88888P' dP `YP `88888P'
348+
MMMMMMMMMMM -*- Created by Zuko -*-
349+
350+
* * * * * * * * * * * * * * * * * * * * *
351+
* - - - F.R.E.E.M.I.N.D - - - *
352+
* - Copyright © 2025 (Z) Programing - *
353+
* - - All Rights Reserved - - *
354+
* * * * * * * * * * * * * * * * * * * * *
355+
""")
356+
336357
@app.command()
337358
def list():
359+
"""List available installed profiles"""
338360
profiles = get_profiles()
361+
from rich.table import Table
362+
from rich.console import Console
363+
table = Table("Name", "Path")
364+
console = Console()
339365
for name, path in profiles.items():
340-
typer.echo(f"{name}: {path}")
366+
table.add_row(name, str(path))
367+
console.print(table)
341368

342369

343370
@app.command(name="export")
344371
def export_profile(
345372
name: str,
346373
output: Path = typer.Option(None, "--output", "-o", help="Output zip file (optional)")
347374
):
375+
"""
376+
Export a Firefox profile to a zip file
377+
Args:
378+
name: profile name
379+
output: output path, if omitted. It will be {name}.zip, located in ~/firefox-profile-backups
380+
"""
348381
useDefaultDir = True
349382
if not output:
350383
output = name
@@ -359,6 +392,8 @@ def export_profile(
359392
if not path or not path.exists():
360393
typer.echo(f"❌ Profile '{name}' visible in 'profiles.ini' but not found in file system.")
361394
raise typer.Exit(1)
395+
if output.exists():
396+
typer.confirm(f"Output file {str(output)} exists. Overwrite?", abort=True)
362397
with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) as zipf:
363398
for root, _, files in os.walk(path):
364399
for file in files:
@@ -382,6 +417,12 @@ def import_profile(
382417
if not zip_path.exists() and not str(zip_path).endswith(".zip"):
383418
zip_path = zip_path.with_suffix(".zip")
384419
isRelative = not (str(zip_path).count('/') == 0) or (str(zip_path).count('\\') == 0)
420+
"""
421+
Import a zip file to a Firefox profile
422+
Args:
423+
zip_path: path to zip file
424+
name: profile name
425+
"""
385426
if not zip_path.exists():
386427
if isRelative:
387428
candidates = [
@@ -425,6 +466,7 @@ def clean(name: str):
425466

426467

427468
def main():
469+
# noinspection PyShadowingBuiltins
428470
exec = sys.executable
429471

430472
if len(sys.argv) == 1:

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ description = "Simple Firefox profile manager, including import/export. Speciall
44
license = {text = "GPLv3", file = "./LICENSE.txt"}
55
version = "1.0.1"
66
dependencies = [
7-
"nuitka>=2.7.5",
87
"typer[all]",
98
"watchdog",
9+
"typeguard"
10+
]
11+
optional-dependencies=[
12+
"nuitka>=2.7.5",
1013
]
1114
authors = [
1215
{name = "Zuko", email = "tansautn@gmail.com", github = "https://github.com/tansautn"}

0 commit comments

Comments
 (0)