diff --git a/.gitignore b/.gitignore index 8fa916a..008c16e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ media/ .secrets *.db +imagebot.cfg # Byte-compiled / optimized / DLL files diff --git a/README.md b/README.md index 8011438..81f9f2e 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,31 @@ ibot runserver ``` Put images in `media/consume/` + + +Add `ìmagebot.cfg` with + +```cfg +[imagebot] +instance_name=ImageBot +host=localhost + +[django] +secret=CHANGE-ME +debug=True + +[database] +backend=sqlite3 +name=imagebot.sqlite3 + +[locations] +media_root=./media/ + +[logging] +app_log_level=INFO +django_log_level=INFO + +[fediverse] +server = "example.org" +token = "VERYSECRETTOKEN" +``` \ No newline at end of file diff --git a/idescriptor/bot.py b/idescriptor/bot.py new file mode 100644 index 0000000..7357d53 --- /dev/null +++ b/idescriptor/bot.py @@ -0,0 +1,94 @@ +import requests + +from imagebot import settings +from idescriptor.models import Image + + +class FediClient: + def __init__(self, access_token, api_base_url): + """ + :param access_token: Your server API access token. + :param api_base_url: The base URL of the Fediverse instance (e.g., 'https://gay-pirate-assassins.de'). + """ + self.access_token = access_token + self.api_base_url = api_base_url.rstrip('/') + self.headers = { + 'Authorization': f'Bearer {self.access_token}', + } + + def upload_media(self, image_path, alt_text): + """ + Uploads media (image) to the server and returns the media ID. + :param image_path: Path to the image file to upload. + :param alt_text: Description (alt text) for the image. + :return: The media ID of the uploaded image. + """ + + media_endpoint = f'{self.api_base_url}/api/v2/media' + + with open(image_path, 'rb') as image_file: + files = { + 'file': image_file, + 'description': (None, alt_text) + } + response = requests.post(media_endpoint, headers=self.headers, files=files) + + # Raise exception if upload fails + response.raise_for_status() + + # Parse and return the media ID from the response + media_id = response.json().get('id') + return media_id + + def post_status(self, status, media_ids=None): + """ + Posts a status to Mastodon with optional media. + :param status: The text of the status to post. + :param media_ids: A list of media IDs to attach to the status (optional). + :return: The response from the Mastodon API. + """ + status_endpoint = f'{self.api_base_url}/api/v1/statuses' + + payload = { + 'status': status, + 'media_ids[]': media_ids if media_ids else [] + } + response = requests.post(status_endpoint, headers=self.headers, data=payload) + + # Raise exception if posting fails + response.raise_for_status() + + return response.json() + + def post_status_with_image(self, status, image_path, alt_text): + """ + Uploads an image, then posts a status with that image and alt text. + :param status: The text of the status. + :param image_path: The path to the image file. + :param alt_text: The alt text for the image. + :return: The response from the Mastodon API. + """ + # Upload the image and get the media ID + media_id = self.upload_media(image_path, alt_text) + + # Post the status with the uploaded image's media ID + return self.post_status(status, media_ids=[media_id]) + + +def post(): + ACCESS_TOKEN = settings.FEDIVERSE_TOKEN + API_BASE_URL = f"https://{settings.FEDIVERSE_SERVER}" + client = FediClient(ACCESS_TOKEN, API_BASE_URL) + + image = Image.get_image_to_post() + + status_text = image.title + image_path = f"{settings.MEDIA_ROOT}/{image.image}" + alt_text = image.alt_text + + response = client.post_status_with_image(status_text, image_path, alt_text) + print(response) + + +if __name__ == "__main__": + post() diff --git a/idescriptor/management/commands/post.py b/idescriptor/management/commands/post.py new file mode 100644 index 0000000..dd290d2 --- /dev/null +++ b/idescriptor/management/commands/post.py @@ -0,0 +1,13 @@ +from django.core.management import BaseCommand +from idescriptor.bot import post + + +class Command(BaseCommand): + help = 'Post an image' + + def add_arguments(self, parser): + # Named (optional) arguments + pass + + def handle(self, *args, **options): + print(post()) diff --git a/imagebot/settings.py b/imagebot/settings.py index 43aedb2..5eefc05 100644 --- a/imagebot/settings.py +++ b/imagebot/settings.py @@ -83,6 +83,9 @@ MEDIA_PROCESSED_DIR_RELATIVE = Path("processed") # Relative to MEDIA_ROOT CRISPY_TEMPLATE_PACK = 'bootstrap4' +FEDIVERSE_SERVER = config.get('fediverse', 'server') +FEDIVERSE_TOKEN = config.get('fediverse', 'token') + # Application definition INSTALLED_APPS = [ diff --git a/pyproject.toml b/pyproject.toml index b7085f6..418889f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ crispy-bootstrap4 = "*" [tool.poetry.scripts] ibot = 'imagebot.manage:main' +ibotpost = 'idescriptor.bot:main' [tool.poetry.group.test.dependencies]