commit 9a27cc175caa0b806ed30afcfed77097372669ec Author: Florian Uhlig Date: Sun Oct 12 04:14:31 2025 +0200 first commit diff --git a/Changelog.md b/Changelog.md new file mode 100644 index 0000000..a2305f1 --- /dev/null +++ b/Changelog.md @@ -0,0 +1,21 @@ +### V1.0 +- First Version + +### V1.1 +- Cleanup code + +### V1.2 +- Redone the changelog: + - better structure +- Added env.example +- Chnaged Readme.md: + - Added how to get a API key + +### V2.0 +- Make more comments +- clearer structure +- added functions.py + +Planned Features: +To-Do: +- Add arguments when running the script \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..57a7a3a --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# Bookstack Backup +This Script works via the Bookstack API. +You can decide which file Type you want to download, you can decide from text, markdown, pdf and html. +## How it works +You should have an Python virtual Environment and the pip package *bookstack* installed. +If it's not installed, you can install it via: +``` +python3 -m venv [Name of your venv Folder] +source [Your Venv Folder]/bin/activate +pip install bookstack +``` +Now you can run the script: +``` +python3 main.py +``` +## How to get an API key +##### Step 1: +- Log in with an administrator account +##### Step 2: +- Click on settings in the top bar +##### Step 3: +- Go to the user tab +##### Step 4: +- Click on the user you want to create an API Token for +##### Step 5: +- Scroll down to API-Token +##### Step 6: +- Click on "Create Token" +##### Step 7: +- Give the token a name and expiry date +##### Step 8: +- Save your "Token ID" and "Token Secret" \ No newline at end of file diff --git a/env.example b/env.example new file mode 100644 index 0000000..e416ae5 --- /dev/null +++ b/env.example @@ -0,0 +1,5 @@ +BASE_URL=https://example.com ## Your full bookstack FQDN +TOKEN_ID= ## Your API token ID +TOKEN_SECRET= ## Your API token secret + +BACKUP_DIR="" ## Your Backup destination diff --git a/functions/__init__.py b/functions/__init__.py new file mode 100644 index 0000000..28a17ed --- /dev/null +++ b/functions/__init__.py @@ -0,0 +1,32 @@ +from . import get_set_Book +from . import set_Variables +from . import backupType +from . import backupLogic +from . import get_confirmation +import bookstack + +def main(version): + print(f"Functions module running version: {version}") + env = set_Variables.set_variables() + print(f"You are currently running on Version: {version}") + + api = bookstack.BookStack(env['base_url'], token_id=env['token_id'], token_secret=env['token_secret']) + api.generate_api_methods() + + try: + book_list = api.get_books_list()['data'] + except Exception as e: + print(f"Error loading books: {e}") + return + + get_set_Book.get_Books(book_list) + book_to_backup = get_set_Book.set_Book(book_list) + backup_extension = backupType.get_backup_extension() + backup_extension_fileType = backupType.set_backup_extension_filetype(backup_extension) + book_to_backup = next(book for book in book_list if book['id'] == book_to_backup) + + if get_confirmation.get_confirmation(backup_extension, book_to_backup): + backupLogic.backup_book(api, env, book_to_backup, backup_extension, backup_extension_fileType) + print("Backup completed.") + else: + return \ No newline at end of file diff --git a/functions/backupLogic.py b/functions/backupLogic.py new file mode 100644 index 0000000..2dd5fc2 --- /dev/null +++ b/functions/backupLogic.py @@ -0,0 +1,31 @@ +import os +import requests + + +def backup_book(api, env, selected_book, backup_extension, backup_extension_fileType): + base_url = env['base_url'] + backup_dir = env['backup_dir'] + headers = { + "Authorization": f"Token {env['token_id']}:{env['token_secret']}" + } + + pages_list = api.get_pages_list()['data'] + chapter_list = api.get_chapters_list()['data'] + + print(f"Backup type: {backup_extension}") + + for chapter in chapter_list: + for page in pages_list: + if page['book_id'] == selected_book['id']: + if chapter['id'] == page['chapter_id']: + folder = os.path.join(backup_dir, chapter['name']) + filename = os.path.join(folder, page['name'] + backup_extension_fileType) + print(f"Backing up: {filename}") + if not os.path.exists(folder): + os.makedirs(folder, exist_ok=True) + response = requests.get( + f'{base_url}/api/pages/{page["id"]}/export/{backup_extension}', + headers=headers + ) + with open(filename, 'wb') as file: + file.write(response.content) \ No newline at end of file diff --git a/functions/backupType.py b/functions/backupType.py new file mode 100644 index 0000000..8151271 --- /dev/null +++ b/functions/backupType.py @@ -0,0 +1,24 @@ +def get_backup_extension(): + backup_type = input("Enter backup type (md, pdf, html, text): ").strip().lower() + if backup_type in ["md", "markdown"]: + return "markdown" + elif backup_type == "pdf": + return "PDF" + elif backup_type == "html": + return "HTML" + elif backup_type in ["plain", "text", "txt", "plain_text"]: + return "Plaintext" + else: + raise ValueError("Unsupported backup type") + +def set_backup_extension_filetype(backup_type): + if backup_type == "markdown": + return ".md" + elif backup_type == "PDF": + return ".pdf" + elif backup_type == "HTML": + return ".html" + elif backup_type == "Plaintext": + return ".txt" + else: + print("Error file type not found") \ No newline at end of file diff --git a/functions/get_confirmation.py b/functions/get_confirmation.py new file mode 100644 index 0000000..01b2842 --- /dev/null +++ b/functions/get_confirmation.py @@ -0,0 +1,14 @@ +def get_confirmation(backup_extension, selected_book): + print(f"Backup type: {backup_extension}") + while True: + confirm = input("Are these settings correct? (y/n): ").strip().lower() + if confirm == "y": + print(f"Starting backup for book: {selected_book['name']} with filetype {backup_extension}.") + print("Starting now.......") + return True + elif confirm == "n": + print("Stopping now.....") + return False + else: + print("Please enter 'y' or 'n'.") + diff --git a/functions/get_set_Book.py b/functions/get_set_Book.py new file mode 100644 index 0000000..3b4b6a0 --- /dev/null +++ b/functions/get_set_Book.py @@ -0,0 +1,16 @@ +def get_Books(book_list): + print("Available books:") + print("Book_ID: ", "Book_Name") + for book in book_list: + print(f"{book['id']}: {book['name']}") + +def set_Book(book_list): + while True: + try: + book_to_backup = int(input("Enter the book ID to backup: ")) + if any(book['id'] == book_to_backup for book in book_list): + return book_to_backup + else: + print("Invalid book ID, please try again.") + except ValueError: + print("Please enter a valid number.") \ No newline at end of file diff --git a/functions/set_Variables.py b/functions/set_Variables.py new file mode 100644 index 0000000..3c17b6d --- /dev/null +++ b/functions/set_Variables.py @@ -0,0 +1,11 @@ +from dotenv import load_dotenv +import os + +def set_variables(): + load_dotenv() + return { + "base_url": os.getenv('BASE_URL'), + "token_id": os.getenv('TOKEN_ID'), + "token_secret": os.getenv('TOKEN_SECRET'), + "backup_dir": os.getenv('BACKUP_DIR') + } \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..c1f74e2 --- /dev/null +++ b/main.py @@ -0,0 +1,4 @@ +## Imports +import functions as func + +func.main(version="V2.1") \ No newline at end of file