Compare commits
6 Commits
b0577624e1
...
791330e415
Author | SHA1 | Date | |
---|---|---|---|
791330e415 | |||
e02ec77308 | |||
1c40d2e769 | |||
b84d0ba0af | |||
01050792d0 | |||
5dc6267cfc |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
|||||||
media/
|
media/
|
||||||
.secrets
|
.secrets
|
||||||
*.db
|
*.db
|
||||||
|
imagebot.cfg
|
||||||
|
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
|
53
README.md
53
README.md
@ -1,26 +1,41 @@
|
|||||||
# RatBot
|
# RatBot
|
||||||
|
|
||||||
```
|
Create database
|
||||||
alembic init alembic
|
|
||||||
```
|
|
||||||
|
|
||||||
In alembic.ini, configure the database URL:
|
|
||||||
```ini
|
|
||||||
sqlalchemy.url = sqlite:///./test.db
|
|
||||||
```
|
|
||||||
|
|
||||||
`albemic/env.py`
|
|
||||||
```python
|
|
||||||
from models import Base
|
|
||||||
target_metadata = Base.metadata
|
|
||||||
```
|
|
||||||
|
|
||||||
**Make migrations**
|
|
||||||
```zsh
|
```zsh
|
||||||
alembic revision --autogenerate -m "Create model XYZ"
|
ibot migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
**Migrate**
|
Run server
|
||||||
```zsh
|
```zsh
|
||||||
alembic upgrade head
|
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"
|
||||||
|
```
|
100
idescriptor/bot.py
Normal file
100
idescriptor/bot.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
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()
|
13
idescriptor/management/commands/post.py
Normal file
13
idescriptor/management/commands/post.py
Normal file
@ -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())
|
@ -7,7 +7,8 @@ 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 datetime import datetime
|
from django.db.models import Q
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from imagebot import settings
|
from imagebot import settings
|
||||||
|
|
||||||
@ -44,7 +45,8 @@ 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 = datetime.now()
|
self.last_posted = timezone.now()
|
||||||
|
self.save()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def consume():
|
def consume():
|
||||||
@ -61,3 +63,13 @@ 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
|
||||||
|
@ -5,13 +5,8 @@ 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}
|
||||||
@ -29,7 +24,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 = get_image_for_descriptor()
|
image_to_describe = Image.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
23
imagebot.cfg
@ -1,23 +0,0 @@
|
|||||||
# 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
|
|
||||||
|
|
||||||
|
|
@ -83,6 +83,9 @@ 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 = [
|
||||||
|
@ -22,6 +22,7 @@ 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]
|
||||||
|
Loading…
Reference in New Issue
Block a user