Compare commits

..

No commits in common. "791330e41559dfb6b1e5706d29940b747017c3d2" and "b0577624e1bf45c8c2d1a1df7409d435fb1180b9" have entirely different histories.

9 changed files with 49 additions and 166 deletions

1
.gitignore vendored
View File

@ -1,7 +1,6 @@
media/ media/
.secrets .secrets
*.db *.db
imagebot.cfg
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files

View File

@ -1,41 +1,26 @@
# RatBot # RatBot
Create database ```
```zsh alembic init alembic
ibot migrate
``` ```
Run server In alembic.ini, configure the database URL:
```zsh ```ini
ibot runserver sqlalchemy.url = sqlite:///./test.db
``` ```
Put images in `media/consume/` `albemic/env.py`
```python
from models import Base
target_metadata = Base.metadata
```
**Make migrations**
```zsh
alembic revision --autogenerate -m "Create model XYZ"
```
Add `ìmagebot.cfg` with **Migrate**
```zsh
```cfg alembic upgrade head
[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"
```

View File

@ -1,100 +0,0 @@
import logging
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
try:
response = client.post_status_with_image(status_text, image_path, alt_text)
logging.info(response)
image.set_image_posted()
except requests.exceptions.ConnectionError as e:
logging.error(f"Could not post image: {e}")
if __name__ == "__main__":
post()

View File

@ -1,13 +0,0 @@
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())

View File

@ -7,8 +7,7 @@ from django.db import models
from django.urls import reverse from django.urls import reverse
from django.core.files import File from django.core.files import File
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.db.models import Q from datetime import datetime
from django.utils import timezone
from imagebot import settings from imagebot import settings
@ -45,8 +44,7 @@ class Image(models.Model):
def set_image_posted(self): def set_image_posted(self):
self.number_times_posted += 1 self.number_times_posted += 1
self.last_posted = timezone.now() self.last_posted = datetime.now()
self.save()
@staticmethod @staticmethod
def consume(): def consume():
@ -63,13 +61,3 @@ class Image(models.Model):
file_hash=file_hash file_hash=file_hash
) )
os.remove(path) os.remove(path)
@staticmethod
def get_image_for_descriptor():
image = Image.objects.filter(Q(alt_text=None) | Q(title=None)).first()
return image
@staticmethod
def get_image_to_post():
image = Image.objects.filter(Q(alt_text__isnull=False) & Q(title__isnull=False)).order_by('last_posted').first()
return image

View File

@ -5,8 +5,13 @@ from django.urls import reverse
from idescriptor.forms import ImageForm from idescriptor.forms import ImageForm
from idescriptor.models import Image from idescriptor.models import Image
from django.db.models import Q
def get_image_for_descriptor():
image = Image.objects.filter(Q(alt_text=None) | Q(title=None)).first()
return image
def list_images(request): def list_images(request):
images = Image.objects.all() images = Image.objects.all()
context = {"images": images} context = {"images": images}
@ -24,7 +29,7 @@ class ImageFormView(UpdateView):
def index(request): def index(request):
"""View function for home page of site.""" """View function for home page of site."""
image_to_describe = Image.get_image_for_descriptor() image_to_describe = get_image_for_descriptor()
if request.method == "POST": if request.method == "POST":
action = request.POST.get("action") action = request.POST.get("action")

23
imagebot.cfg Normal file
View File

@ -0,0 +1,23 @@
# DEBUG CONFIGURATION
# DO NOT USE IN PRODUCTION
[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

View File

@ -83,9 +83,6 @@ MEDIA_PROCESSED_DIR_RELATIVE = Path("processed") # Relative to MEDIA_ROOT
CRISPY_TEMPLATE_PACK = 'bootstrap4' CRISPY_TEMPLATE_PACK = 'bootstrap4'
FEDIVERSE_SERVER = config.get('fediverse', 'server')
FEDIVERSE_TOKEN = config.get('fediverse', 'token')
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [

View File

@ -22,7 +22,6 @@ crispy-bootstrap4 = "*"
[tool.poetry.scripts] [tool.poetry.scripts]
ibot = 'imagebot.manage:main' ibot = 'imagebot.manage:main'
ibotpost = 'idescriptor.bot:main'
[tool.poetry.group.test.dependencies] [tool.poetry.group.test.dependencies]