Last active
November 13, 2024 08:13
-
-
Save xyb/b2b950a2b0f1b522d94fd462a87b5df6 to your computer and use it in GitHub Desktop.
scripts to setup joplin web clipper api token and create new note
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Joplin Note Creator | |
A command line tool to create notes in Joplin. Features: | |
- Create notes from stdin or clipboard | |
- Specify note title | |
- Create notes in specific folders (supports partial folder name matching) | |
Usage: | |
pbpaste | python jnote.py --title "Note Title" | |
pbpaste | python jnote.py --title "Note Title" --folder "Folder Name" | |
python jnote.py --list-folders | |
Options: | |
--title Required. The title for the new note | |
--folder Optional. The folder to create the note in. Supports partial matching | |
--list-folders List all available folders | |
Requirements: | |
- Joplin desktop app must be running | |
- Web Clipper Service must be enabled | |
- Joplin API token must be configured (use setup_joplin_token.py) | |
Configuration: | |
Token file: ~/.config/joplinapi/token | |
""" | |
import os | |
import sys | |
import json | |
import requests | |
def check_joplin_service(): | |
"""Check if Joplin Web Clipper Service is running""" | |
try: | |
response = requests.get("http://127.0.0.1:41184/ping") | |
if response.status_code != 200 or response.text.strip() != "JoplinClipperServer": | |
print("Error: Joplin Web Clipper Service is not running.") | |
print("Please start Joplin and enable Web Clipper Service in:") | |
print("Tools > Options > Web Clipper > Enable Web Clipper Service") | |
sys.exit(1) | |
except requests.exceptions.ConnectionError: | |
print("Error: Cannot connect to Joplin Web Clipper Service.") | |
print("Please start Joplin and enable Web Clipper Service in:") | |
print("Tools > Options > Web Clipper > Enable Web Clipper service") | |
sys.exit(1) | |
def validate_token(token): | |
"""Validate token by making a test API call""" | |
url = f"http://127.0.0.1:41184/folders?token={token}" | |
try: | |
response = requests.get(url) | |
if response.status_code == 200: | |
return True | |
print("Error: Invalid Joplin API token.") | |
print("\nPlease run setup_joplin_token.py to configure your Joplin API token:") | |
print(" python setup_joplin_token.py") | |
return False | |
except requests.exceptions.RequestException as e: | |
print(f"Error validating token: {e}") | |
return False | |
def load_token(): | |
"""Load Joplin API token from config file and validate connection""" | |
# First check if Joplin service is running | |
check_joplin_service() | |
# Then load and validate token | |
token_file = os.path.expanduser("~/.config/joplinapi/token") | |
try: | |
with open(token_file, "r") as f: | |
token = f.read().strip() | |
except FileNotFoundError: | |
print("Error: Joplin token file not found.") | |
print(f"Token file path: {token_file}") | |
print("\nPlease run setup_joplin_token.py to configure your Joplin API token:") | |
print(" python setup_joplin_token.py") | |
sys.exit(1) | |
except Exception as e: | |
print(f"Error reading token file: {e}") | |
print(f"Token file path: {token_file}") | |
print("\nPlease run setup_joplin_token.py to reconfigure your Joplin API token:") | |
print(" python setup_joplin_token.py") | |
sys.exit(1) | |
# Validate token | |
if not validate_token(token): | |
sys.exit(1) | |
return token | |
def get_folder_id_by_name(folder_name, token): | |
"""Get folder ID by partial name match (case-insensitive) | |
Returns (folder_id, error_message). If no match or multiple matches found, | |
folder_id will be None and error_message will contain the error details.""" | |
url = f"http://localhost:41184/folders?token={token}" | |
response = requests.get(url) | |
if response.status_code == 200: | |
folders = response.json().get("items", []) | |
matches = [] | |
search_term = folder_name.lower() | |
for folder in folders: | |
if search_term in folder["title"].lower(): | |
matches.append(folder) | |
if len(matches) == 0: | |
return None, f"No folders found containing '{folder_name}'" | |
elif len(matches) > 1: | |
matching_names = ", ".join(f"'{f['title']}'" for f in matches) | |
return None, f"Multiple matching folders found: {matching_names}" | |
else: | |
return matches[0]["id"], None | |
return None, "Failed to retrieve folders" | |
def get_folder_name_by_id(folder_id, token): | |
"""Get folder name by ID""" | |
url = f"http://localhost:41184/folders/{folder_id}?token={token}" | |
response = requests.get(url) | |
if response.status_code == 200: | |
return response.json().get("title") | |
return None | |
def create_note(title, body, token, folder_id=None): | |
"""Create a new note in Joplin using the provided title, body, and optional folder_id""" | |
url = f"http://localhost:41184/notes?token={token}" | |
headers = {"Content-Type": "application/json"} | |
payload = { | |
"title": title, | |
"body": body | |
} | |
if folder_id: | |
payload["parent_id"] = folder_id | |
response = requests.post(url, headers=headers, json=payload) | |
if response.status_code == 200: | |
response_data = response.json() | |
if "id" in response_data: | |
folder_info = "" | |
parent_id = response_data.get("parent_id") | |
if parent_id: | |
folder_name = get_folder_name_by_id(parent_id, token) | |
if folder_name: | |
folder_info = f" in folder '{folder_name}'" | |
print(f"Note '{title}' created successfully{folder_info}.") | |
else: | |
print(f"Error creating note: {response_data}") | |
else: | |
print(f"Error creating note: {response.text}") | |
def list_folders(token): | |
"""List all folders in Joplin, sorted by name, and print their names and IDs""" | |
url = f"http://localhost:41184/folders?token={token}" | |
response = requests.get(url) | |
if response.status_code == 200: | |
folders = response.json().get("items", []) | |
# Sort folders by name | |
sorted_folders = sorted(folders, key=lambda folder: folder["title"].lower()) | |
if sorted_folders: | |
print("Folders (sorted by name):") | |
for folder in sorted_folders: | |
print(f"Name: {folder['title']}, ID: {folder['id']}") | |
else: | |
print("No folders found.") | |
else: | |
print(f"Error retrieving folders: {response.text}") | |
def main(): | |
if len(sys.argv) < 2: | |
print("Usage:") | |
print(" pbpaste | python jnote.py --title 'new note' [--folder 'folder name']") | |
print(" python jnote.py --list-folders") | |
sys.exit(1) | |
token = load_token() | |
if sys.argv[1] == "--list-folders": | |
list_folders(token) | |
elif sys.argv[1] == "--title" and len(sys.argv) > 2: | |
title = sys.argv[2] | |
folder_id = None | |
# Check for folder argument | |
if "--folder" in sys.argv: | |
folder_index = sys.argv.index("--folder") | |
if folder_index + 1 < len(sys.argv): | |
folder_name = sys.argv[folder_index + 1] | |
folder_id, error_msg = get_folder_id_by_name(folder_name, token) | |
if error_msg: | |
print(f"Error: {error_msg}") | |
sys.exit(1) | |
body = sys.stdin.read() | |
create_note(title, body, token, folder_id) | |
else: | |
print("Invalid arguments.") | |
print("Usage:") | |
print(" pbpaste | python jnote.py --title 'new note' [--folder 'folder name']") | |
print(" python jnote.py --list-folders") | |
sys.exit(1) | |
if __name__ == "__main__": | |
main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
This script automatically obtains a Joplin Web Clipper token and saves it to | |
~/.config/joplinapi/token. It first checks if a valid token already exists, and | |
if so, uses it directly. If no valid token is found, the script initiates the | |
authorization process, waits for the user to approve access in Joplin, and then | |
saves the new token once approved. | |
Steps: | |
1. Check if the Joplin Web Clipper Service is running and reachable. | |
2. Check for an existing token and validate it. | |
3. If no valid token exists, request an auth token and prompt user authorization. | |
4. Once authorized, save the new token for future use. | |
Requirements: | |
- Joplin Web Clipper Service must be enabled and running on localhost:41184. | |
""" | |
import requests | |
import time | |
import os | |
import sys | |
def check_server_status(): | |
"""Check if Joplin Web Clipper Service is running on localhost:41184""" | |
try: | |
response = requests.get("http://127.0.0.1:41184/ping") | |
if response.status_code == 200 and response.text == "JoplinClipperServer": | |
print("Joplin Web Clipper Service is running.") | |
else: | |
print(response) | |
print("The server on port 41184 is not Joplin. Please check your setup.") | |
sys.exit(1) | |
except requests.ConnectionError: | |
print("Joplin Web Clipper Service is not running. Please enable it and try again.") | |
sys.exit(1) | |
def load_existing_token(): | |
"""Load existing token if it exists, otherwise return None""" | |
token_path = os.path.expanduser("~/.config/joplinapi/token") | |
if os.path.exists(token_path): | |
with open(token_path, "r") as f: | |
return f.read().strip() | |
return None | |
def check_token_validity(token): | |
"""Check if the token is valid""" | |
response = requests.get(f"http://localhost:41184/notes?token={token}") | |
return response.status_code == 200 | |
def get_auth_token(): | |
"""Request an auth token from Joplin""" | |
response = requests.post("http://localhost:41184/auth") | |
if response.status_code == 200 and "auth_token" in response.json(): | |
return response.json()["auth_token"] | |
else: | |
raise Exception("Failed to obtain auth_token") | |
def check_auth_status(auth_token): | |
"""Continuously check authorization status until access token is received""" | |
url = f"http://localhost:41184/auth/check?auth_token={auth_token}" | |
while True: | |
response = requests.get(url) | |
if response.status_code == 200: | |
data = response.json() | |
if data["status"] == "accepted": | |
return data["token"] | |
elif data["status"] == "rejected": | |
print("Authorization rejected by the user in Joplin.") | |
sys.exit(1) | |
elif data["status"] == "waiting": | |
print("Waiting for authorization in Joplin...") | |
time.sleep(2) | |
else: | |
raise Exception("Unexpected status received") | |
else: | |
raise Exception("Failed to check auth status") | |
def save_token(token): | |
"""Save the token to the specified file""" | |
config_path = os.path.expanduser("~/.config/joplinapi") | |
os.makedirs(config_path, exist_ok=True) | |
token_path = os.path.join(config_path, "token") | |
with open(token_path, "w") as f: | |
f.write(token) | |
print(f"Token saved to {token_path}") | |
def main(): | |
try: | |
# Check if Joplin Web Clipper Service is running | |
check_server_status() | |
# Check if a valid token already exists | |
existing_token = load_existing_token() | |
if existing_token and check_token_validity(existing_token): | |
print("Existing token is valid.") | |
print(f"Token: {existing_token}") | |
else: | |
print("No valid token found. Starting authorization process.") | |
auth_token = get_auth_token() | |
print(f"Auth token received: {auth_token}") | |
token = check_auth_status(auth_token) | |
print(f"Access token received: {token}") | |
save_token(token) | |
except Exception as e: | |
print(f"Error: {e}") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment