Compare commits
5 Commits
28cc8b2ed1
...
d8f3d1bdb8
| Author | SHA1 | Date | |
|---|---|---|---|
| d8f3d1bdb8 | |||
| 328de3c766 | |||
| 2748a5a75b | |||
| b5d6fc2152 | |||
| 72bc286601 |
@@ -1,2 +1,2 @@
|
|||||||
python 3.13.0
|
python 3.14.3
|
||||||
nodejs 24.4.1
|
nodejs 25.8.0
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.11.5-alpine
|
FROM python:3.14.3-alpine
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN pip install uv
|
RUN pip install uv
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ screen](https://gitea.bubbletea.dev/shibao/szuru-eink-bot/raw/branch/master/eink
|
|||||||
- Fetches random images from a configurable Szurubooru instance.
|
- Fetches random images from a configurable Szurubooru instance.
|
||||||
- Resizes and dithered images for e-ink display compatibility.
|
- Resizes and dithered images for e-ink display compatibility.
|
||||||
- Supports monochrome and color dithering (for compatible displays).
|
- Supports monochrome and color dithering (for compatible displays).
|
||||||
|
- Filters images by an optional allowed username.
|
||||||
- Uploads processed images to a Waveshare e-ink screen.
|
- Uploads processed images to a Waveshare e-ink screen.
|
||||||
- Configurable via environment variables.
|
- Configurable via environment variables.
|
||||||
- Designed to run as a Docker container on a cron job.
|
- Designed to run as a Docker container on a cron job.
|
||||||
@@ -41,7 +42,10 @@ display.
|
|||||||
|
|
||||||
**Note:** For `EPD_TYPE`, refer to the `epd/epd_config.py` file for a full list
|
**Note:** For `EPD_TYPE`, refer to the `epd/epd_config.py` file for a full list
|
||||||
of supported display types and their corresponding string labels. For color
|
of supported display types and their corresponding string labels. For color
|
||||||
displays, ensure `DITHERING_MODE` is set to `color`.
|
displays, ensure `DITHERING_MODE` is set to `color`. `ALLOWED_USER` is optional;
|
||||||
|
if set, the bot will only fetch images uploaded by that specific user. The bot
|
||||||
|
will retry a maximum of 100 times to find a valid image (matching the user filter,
|
||||||
|
if provided) before giving up.
|
||||||
|
|
||||||
### 3. Build and Run with Docker Compose
|
### 3. Build and Run with Docker Compose
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ services:
|
|||||||
build: .
|
build: .
|
||||||
container_name: szuru-eink-bot
|
container_name: szuru-eink-bot
|
||||||
environment:
|
environment:
|
||||||
|
- ALLOWED_USER=YOUR_ALLOWED_USER # optional: only fetch images from this user
|
||||||
|
- DITHERING_MODE=YOUR_DITHERING_MODE # e.g., mono, color
|
||||||
|
- EINK_IP=YOUR_EINK_IP
|
||||||
|
- EPD_TYPE=YOUR_EPD_TYPE # e.g., '7.5 V2', 5.65f
|
||||||
|
- SZURU_TOKEN=YOUR_SZURU_TOKEN
|
||||||
- SZURU_URL=YOUR_SZURU_URL
|
- SZURU_URL=YOUR_SZURU_URL
|
||||||
- SZURU_USER=YOUR_SZURU_USER
|
- SZURU_USER=YOUR_SZURU_USER
|
||||||
- SZURU_TOKEN=YOUR_SZURU_TOKEN
|
|
||||||
- EPD_TYPE=YOUR_EPD_TYPE # e.g., '7.5 V2', 5.65f
|
|
||||||
- EINK_IP=YOUR_EINK_IP
|
|
||||||
- DITHERING_MODE=YOUR_DITHERING_MODE # e.g., mono, color
|
|
||||||
|
|||||||
@@ -2,14 +2,18 @@
|
|||||||
name = "szuru-eink"
|
name = "szuru-eink"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = ""
|
description = ""
|
||||||
authors = []
|
authors = [
|
||||||
requires-python = "==3.13.5"
|
{ name = "shibao" },
|
||||||
|
]
|
||||||
|
requires-python = ">=3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aioipfs==0.6.5",
|
"aioipfs==0.7.1",
|
||||||
"pyszuru==0.3.1",
|
"pyszuru==0.4.0",
|
||||||
"Requests==2.31.0",
|
"requests==2.32.5",
|
||||||
"Pillow==11.3.0",
|
"pillow==12.1.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = ["black==24.4.2"]
|
dev = [
|
||||||
|
"black==26.1.0",
|
||||||
|
]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import random
|
|||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
import pyszuru
|
import pyszuru
|
||||||
|
from pyszuru.api import SzurubooruHTTPError
|
||||||
from PIL import Image, ImageSequence, UnidentifiedImageError
|
from PIL import Image, ImageSequence, UnidentifiedImageError
|
||||||
|
|
||||||
|
|
||||||
@@ -13,12 +14,12 @@ from epd.epd_config import palArr, epdArr, EPD_TYPES
|
|||||||
from epd.image_processor import getErr, getNear, addVal, procImg
|
from epd.image_processor import getErr, getNear, addVal, procImg
|
||||||
from epd.epd_uploader import upload_image
|
from epd.epd_uploader import upload_image
|
||||||
|
|
||||||
|
|
||||||
# get truly random post
|
# get truly random post
|
||||||
try:
|
try:
|
||||||
szuru_url = os.environ["SZURU_URL"]
|
szuru_url = os.environ["SZURU_URL"]
|
||||||
szuru_user = os.environ["SZURU_USER"]
|
szuru_user = os.environ["SZURU_USER"]
|
||||||
szuru_token = os.environ["SZURU_TOKEN"]
|
szuru_token = os.environ["SZURU_TOKEN"]
|
||||||
|
allowed_user = os.getenv("ALLOWED_USER")
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise ValueError(f"Missing required environment variable: {e}")
|
raise ValueError(f"Missing required environment variable: {e}")
|
||||||
|
|
||||||
@@ -26,16 +27,42 @@ booru = pyszuru.API(szuru_url, username=szuru_user, token=szuru_token)
|
|||||||
highest_post = next(booru.search_post("sort:id type:image", page_size=1))
|
highest_post = next(booru.search_post("sort:id type:image", page_size=1))
|
||||||
|
|
||||||
post = None
|
post = None
|
||||||
while post is None:
|
retries = 0
|
||||||
|
MAX_RETRIES = 100
|
||||||
|
|
||||||
|
while post is None and retries < MAX_RETRIES:
|
||||||
random_id = random.randint(0, highest_post.id_)
|
random_id = random.randint(0, highest_post.id_)
|
||||||
|
try:
|
||||||
temp_post = booru.getPost(random_id)
|
temp_post = booru.getPost(random_id)
|
||||||
|
except SzurubooruHTTPError:
|
||||||
|
print(
|
||||||
|
f"[DEBUG] Skipping post ID: {random_id} - Post not found. (Retry {retries + 1}/{MAX_RETRIES})"
|
||||||
|
)
|
||||||
|
retries += 1
|
||||||
|
continue
|
||||||
|
|
||||||
if temp_post and temp_post.mime.startswith("image/") and temp_post.content:
|
if temp_post and temp_post.mime.startswith("image/") and temp_post.content:
|
||||||
|
# User info is in _json['user']['name']
|
||||||
|
post_user = temp_post._json.get("user", {}).get("name", "Unknown")
|
||||||
|
|
||||||
|
if allowed_user and post_user != allowed_user:
|
||||||
|
print(
|
||||||
|
f"[DEBUG] Skipping post ID: {random_id} - uploaded by {post_user}, but only {allowed_user} is allowed. (Retry {retries + 1}/{MAX_RETRIES})"
|
||||||
|
)
|
||||||
|
retries += 1
|
||||||
|
continue
|
||||||
post = temp_post
|
post = temp_post
|
||||||
print(f"[DEBUG] Found image post with ID: {post.id_}, MIME: {post.mime}")
|
print(
|
||||||
|
f"[DEBUG] Found image post with ID: {post.id_}, MIME: {post.mime} (User: {post_user})"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
f"[DEBUG] Skipping post ID: {random_id} (MIME: {temp_post.mime if temp_post else 'None'}, Content: {bool(temp_post.content) if temp_post else 'None'}) - not a valid image."
|
f"[DEBUG] Skipping post ID: {random_id} (MIME: {temp_post.mime if temp_post else 'None'}, Content: {bool(temp_post.content) if temp_post else 'None'}) - not a valid image. (Retry {retries + 1}/{MAX_RETRIES})"
|
||||||
)
|
)
|
||||||
|
retries += 1
|
||||||
|
|
||||||
|
if post is None:
|
||||||
|
raise RuntimeError(f"Failed to find a valid post after {MAX_RETRIES} retries.")
|
||||||
|
|
||||||
# download image
|
# download image
|
||||||
image_downloaded = False
|
image_downloaded = False
|
||||||
@@ -64,17 +91,39 @@ while not image_downloaded:
|
|||||||
os.remove("image.jpg")
|
os.remove("image.jpg")
|
||||||
# Find a new post
|
# Find a new post
|
||||||
post = None
|
post = None
|
||||||
while post is None:
|
while post is None and retries < MAX_RETRIES:
|
||||||
random_id = random.randint(0, highest_post.id_)
|
random_id = random.randint(0, highest_post.id_)
|
||||||
|
try:
|
||||||
temp_post = booru.getPost(random_id)
|
temp_post = booru.getPost(random_id)
|
||||||
|
except SzurubooruHTTPError:
|
||||||
|
print(
|
||||||
|
f"[DEBUG] Skipping post ID: {random_id} - Post not found. (Retry {retries + 1}/{MAX_RETRIES})"
|
||||||
|
)
|
||||||
|
retries += 1
|
||||||
|
continue
|
||||||
|
|
||||||
if temp_post and temp_post.mime.startswith("image/") and temp_post.content:
|
if temp_post and temp_post.mime.startswith("image/") and temp_post.content:
|
||||||
|
post_user = temp_post._json.get("user", {}).get("name", "Unknown")
|
||||||
|
|
||||||
|
if allowed_user and post_user != allowed_user:
|
||||||
|
print(
|
||||||
|
f"[DEBUG] Skipping post ID: {random_id} - uploaded by {post_user}, but only {allowed_user} is allowed. (Retry {retries + 1}/{MAX_RETRIES})"
|
||||||
|
)
|
||||||
|
retries += 1
|
||||||
|
continue
|
||||||
post = temp_post
|
post = temp_post
|
||||||
print(
|
print(
|
||||||
f"[DEBUG] Found new image post with ID: {post.id_}, MIME: {post.mime}"
|
f"[DEBUG] Found new image post with ID: {post.id_}, MIME: {post.mime} (User: {post_user})"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
f"[DEBUG] Skipping post ID: {random_id} (MIME: {temp_post.mime if temp_post else 'None'}, Content: {bool(temp_post.content) if temp_post else 'None'}) - not a valid image."
|
f"[DEBUG] Skipping post ID: {random_id} (MIME: {temp_post.mime if temp_post else 'None'}, Content: {bool(temp_post.content) if temp_post else 'None'}) - not a valid image. (Retry {retries + 1}/{MAX_RETRIES})"
|
||||||
|
)
|
||||||
|
retries += 1
|
||||||
|
|
||||||
|
if post is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Failed to find a valid post after {MAX_RETRIES} retries."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Process and upload
|
# Process and upload
|
||||||
|
|||||||
Reference in New Issue
Block a user