This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

update script and readme

+67 -38
+42 -13
README.md
··· 2 2 3 3 ## Overview 4 4 5 - This repository contains a Python script intended to update your Bluesky avatar automatically based on the current hour. The script utilises environment variables for configuration and reads a JSON file of blob CIDs to determine the appropriate avatar for each hour. Please note that the implementation is not yet fully operational, as several issues remain to be resolved. This script was inspired by [@dame.is](https://bsky.app/profile/dame.is)'s blog post ['How I made an automated dynamic avatar for my Bluesky profile'](https://dame.is/blog/how-i-made-an-automated-dynamic-avatar-for-my-bluesky-profile). 5 + This repository contains a Python script designed to automatically update your Bluesky avatar based on the current hour. The script leverages environment variables for configuration and reads a JSON file of blob CIDs to determine the appropriate avatar. This script was inspired by [@dame.is](https://bsky.app/profile/dame.is)'s blog post ['How I made an automated dynamic avatar for my Bluesky profile'](https://dame.is/blog/how-i-made-an-automated-dynamic-avatar-for-my-bluesky-profile). 6 + 7 + The script has been tested and is fully functional. It was developed on macOS but is intended for deployment on Linux. 6 8 7 9 ## Prerequisites 8 10 9 11 Before running the script, ensure you have the following: 10 12 11 13 - Python 3.6 or later installed. 12 - 13 - - The required Python packages: 14 + - The required Python packages (automatically installed if missing): 14 15 - `python-dotenv` 15 16 - `atproto` 16 - - Standard libraries such as `os`, `json`, `logging`, and `datetime` 17 + - `requests` 18 + - `python-magic` 17 19 - A valid Bluesky account with the necessary API credentials. 18 20 19 21 ## Installation ··· 41 43 ENDPOINT=your_endpoint 42 44 HANDLE=your_handle 43 45 PASSWORD=your_password 46 + DID=your_did 44 47 ``` 45 48 46 49 4. **Prepare the JSON file:** 47 - - Ensure that a `cids.json` file is located in the `../assets` directory. This file should map each hour (in two-digit format) to a corresponding blob CID. 50 + - Ensure that a `cids.json` file is located in the `../assets` directory. This file should map each hour (in two-digit format) to a corresponding blob CID. Example: 51 + 52 + ```json 53 + { 54 + "00": "cid_for_midnight", 55 + "01": "cid_for_1am", 56 + "02": "cid_for_2am" 57 + } 58 + ``` 48 59 49 60 ## Usage 50 61 ··· 56 67 57 68 The script will: 58 69 59 - - Load the environment configuration from `./assets/.env`. 60 - - Read the blob CIDs from `./assets/cids.json`. 70 + - Load the environment configuration from `../assets/.env`. 71 + - Read the blob CIDs from `../assets/cids.json`. 61 72 - Determine the current hour and select the appropriate blob CID. 62 - - Attempt to authenticate and update the avatar using the AT Protocol. 73 + - Authenticate using the AT Protocol. 74 + - Update the Bluesky avatar accordingly. 75 + 76 + Execution logs will be recorded in `logs/avatar_update.log` for your review. 77 + 78 + ## Automating with Cron (Linux) 63 79 64 - Execution logs will be recorded in `avatar_update.log` for your review. 80 + To run the script automatically every hour, a cron job is set up within the script. If you need to manually verify it, run: 65 81 66 - ## Known Issues 82 + ```bash 83 + crontab -l 84 + ``` 85 + 86 + If you need to remove or modify the cron job, use: 87 + 88 + ```bash 89 + crontab -e 90 + ``` 67 91 68 - At present, the script isn’t fully working. We’ve noticed an error when updating the profile—specifically, the `put_record()` method is missing a required parameter. There have also been occasional authentication hiccoughs, which might be due to configuration issues or [API](https://atproto.blue) quirks. 92 + ## Troubleshooting 69 93 70 - If you’re keen to help sort these out or have ideas for improvements, please open a Pull Request. Your contributions are very welcome! 94 + - **Environment variables not loading?** Ensure the `.env` file is correctly placed in `../assets/`. 95 + - **Script exits with missing dependencies?** The script will attempt to install missing packages, but you can manually install them using: 96 + 97 + ```bash 98 + pip install -r requirements.txt 99 + ``` 71 100 72 - --- 101 + - **Endpoint not responding?** Verify that the Bluesky API endpoint is correct and accessible. 73 102 74 103 ## Licence 75 104
+25 -25
src/main.py
··· 1 + #!/usr/bin/env python3 1 2 import os 2 3 import sys 3 4 import subprocess ··· 7 8 import magic 8 9 from datetime import datetime 9 10 from dotenv import load_dotenv 11 + from logging.handlers import RotatingFileHandler 10 12 from atproto import Client, models 11 13 from atproto.exceptions import BadRequestError 12 14 ··· 23 25 ASSETS_DIR = os.path.join(BASE_DIR, "../assets") 24 26 ENV_PATH = os.path.join(ASSETS_DIR, ".env") 25 27 JSON_PATH = os.path.join(ASSETS_DIR, "cids.json") 26 - LOG_PATH = os.path.join(BASE_DIR, "avatar_update.log") 28 + LOG_PATH = os.path.join(BASE_DIR, "logs", "avatar_update.log") 29 + 30 + # Ensure necessary directories exist 31 + os.makedirs(os.path.dirname(LOG_PATH), exist_ok=True) 32 + os.makedirs(ASSETS_DIR, exist_ok=True) 33 + 34 + # Configure logging with log rotation (5MB per file, keeps last 5 logs) 35 + log_handler = RotatingFileHandler(LOG_PATH, maxBytes=5 * 1024 * 1024, backupCount=5) 36 + log_handler.setLevel(logging.DEBUG) 37 + log_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") 38 + log_handler.setFormatter(log_formatter) 27 39 28 - # Configure logging 29 - logging.basicConfig( 30 - filename=LOG_PATH, 31 - level=logging.DEBUG, 32 - format="%(asctime)s - %(levelname)s - %(message)s", 33 - ) 34 40 console_handler = logging.StreamHandler() 35 41 console_handler.setLevel(logging.INFO) 36 - formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") 37 - console_handler.setFormatter(formatter) 38 - logging.getLogger().addHandler(console_handler) 42 + console_handler.setFormatter(log_formatter) 39 43 44 + logging.basicConfig(level=logging.DEBUG, handlers=[log_handler, console_handler]) 45 + logging.info("Starting script with log rotation enabled.") 40 46 41 47 def install_and_rerun(): 42 48 """Install missing packages from requirements.txt and re-run the script.""" ··· 53 59 logging.error(f"requirements.txt not found at {REQ_PATH}, cannot install missing packages.") 54 60 sys.exit(1) 55 61 56 - 57 - # Try to import external packages; if any are missing, install them. 62 + # Check for required packages and install if missing 58 63 try: 59 64 import requests 60 65 import magic ··· 65 70 logging.error(f"Missing package(s): {e}") 66 71 install_and_rerun() 67 72 68 - 69 73 def ensure_https(url): 70 74 """Ensure the URL starts with https://""" 71 75 if not url.startswith("http://") and not url.startswith("https://"): ··· 73 77 if url.startswith("http://"): 74 78 return "https://" + url[7:] 75 79 return url 76 - 77 80 78 81 def is_endpoint_alive(url): 79 82 """Check if the endpoint is alive by making a health check request.""" ··· 90 93 except requests.RequestException as e: 91 94 logging.error(f"Health check failed for {health_url}: {e}") 92 95 return False 93 - 94 96 95 97 def fetch_blob(did, cid, endpoint): 96 98 """Fetch blob data from the given endpoint.""" ··· 105 107 logging.error(f"Failed to fetch blob {cid} for DID {did}: {e}") 106 108 return None 107 109 108 - 109 110 def get_blob_metadata(cid, did, endpoint): 110 111 """Retrieve metadata for a given blob CID.""" 111 112 blob_data = fetch_blob(did, cid, endpoint) ··· 125 126 "mimeType": mime_type, 126 127 "size": size, 127 128 } 128 - 129 129 130 130 def setup_cron_job(): 131 131 """Set up a cron job to run the script hourly.""" ··· 145 145 logging.info("Cron job added successfully.") 146 146 except Exception as e: 147 147 logging.error(f"Failed to set up cron job: {e}") 148 - 149 148 150 149 def main(): 151 150 logging.info("Starting avatar update script...") 152 151 153 - if os.path.exists(ENV_PATH): 154 - load_dotenv(ENV_PATH) 155 - logging.info("Loaded .env file successfully.") 156 - else: 152 + if not os.path.exists(ENV_PATH): 157 153 logging.error(f"Missing .env file at {ENV_PATH}") 158 154 return 155 + load_dotenv(ENV_PATH) 156 + logging.info("Loaded .env file successfully.") 159 157 160 158 endpoint = os.getenv("ENDPOINT") 161 159 handle = os.getenv("HANDLE") ··· 169 167 endpoint = ensure_https(endpoint) 170 168 if not is_endpoint_alive(endpoint): 171 169 logging.error(f"Endpoint {endpoint} is not responding.") 170 + return 171 + 172 + if not os.path.exists(JSON_PATH) or os.path.getsize(JSON_PATH) == 0: 173 + logging.error(f"Error: {JSON_PATH} is missing or empty.") 172 174 return 173 175 174 176 try: ··· 216 218 except Exception as e: 217 219 logging.error(f"Failed to update profile record: {e}") 218 220 219 - 220 221 if __name__ == "__main__": 221 222 setup_cron_job() 222 - main() 223 - 223 + main()