diff --git a/polybot/bot.py b/polybot/bot.py index aa59150..7d43b52 100644 --- a/polybot/bot.py +++ b/polybot/bot.py @@ -1,15 +1,16 @@ +import argparse import configparser -import pickle import logging -import argparse +import pickle import signal import sys -from typing import List, Union, Optional, Any -from .service import Service, PostError, ALL_SERVICES +from typing import Any, Optional, Union + from .image import Image +from .service import ALL_SERVICES, PostError, Service -class Bot(object): +class Bot: path = "" def __init__(self, name: str) -> None: @@ -41,7 +42,7 @@ def __init__(self, name: str) -> None: self.log = logging.getLogger(__name__) self.name = name - self.services: List[Service] = [] + self.services: list[Service] = [] self.state: Any = {} def run(self) -> None: @@ -51,9 +52,10 @@ def run(self) -> None: profile = "" if len(self.args.profile): - profile = "-%s" % self.args.profile - self.config_path = "%s%s%s.conf" % (self.path, self.name, profile) - self.state_path = "%s%s%s.state" % (self.path, self.name, profile) + profile = "-" + self.args.profile + prefix = f"{self.path}{self.name}{profile}" + self.config_path = prefix + ".conf" + self.state_path = prefix + ".state" self.read_config() if self.args.setup: @@ -101,21 +103,20 @@ def setup(self) -> None: print("=" * 80) for Svc in ALL_SERVICES: if Svc.name not in self.config: - result = input("Configure %s (y/n)? " % Svc.name) + result = input(f"Configure {Svc.name} (y/n)? ") if result[0] == "y": if Svc(self.config, False).setup(): - print("Configuring %s succeeded, writing config" % Svc.name) + print(f"Configuring {Svc.name} succeeded, writing config") self.write_config() else: - print("Configuring %s failed." % Svc.name) + print(f"Configuring {Svc.name} failed.") else: print("OK, skipping.") else: - print("Service %s is already configured" % Svc.name) + print(f"Service {Svc.name} is already configured") print("-" * 80) print( - "Setup complete. To reconfigure, remove the service details from %s" - % self.config_path + f"Setup complete. To reconfigure, remove the service details from {self.config_path}" ) def main(self) -> None: @@ -125,10 +126,11 @@ def load_state(self) -> None: try: with open(self.state_path, "rb") as f: self.state = pickle.load(f) - except IOError: + except OSError: self.log.info("No state file found") def save_state(self) -> None: + """Save the bot's state to disk.""" if len(self.state) != 0: self.log.info("Saving state...") with open(self.state_path, "wb") as f: @@ -136,9 +138,9 @@ def save_state(self) -> None: def post( self, - status: Union[str, List[str]], + status: Union[str, list[str]], wrap: bool = False, - images: List[Image] = [], + images: list[Image] = [], in_reply_to_id=None, lat: Optional[float] = None, lon: Optional[float] = None, @@ -163,7 +165,7 @@ def post( if not len(status): raise ValueError("Cannot supply an empty list") - if not isinstance(images, List) or not all( + if not isinstance(images, list) or not all( isinstance(i, Image) for i in images ): raise ValueError("The images argument must be a list of Image objects") diff --git a/polybot/image.py b/polybot/image.py index 3b8b57b..540a203 100644 --- a/polybot/image.py +++ b/polybot/image.py @@ -1,8 +1,9 @@ -from typing import BinaryIO, Optional +import logging from io import BytesIO from pathlib import Path +from typing import BinaryIO, Optional + from PIL import Image as PILImage -import logging log = logging.getLogger(__name__) @@ -45,8 +46,8 @@ def __init__( def resize_to_target( self, target_bytes: int, target_pixels: Optional[int] = None ) -> "Image": - """Resize the image to a target maximum size in bytes and (optionally) pixels. Returns a new Image object. - This is required for Bluesky's silly image size limit. + """Resize the image to a target maximum size in bytes and (optionally) pixels. + Returns a new Image object. """ original_bytes = len(self.data) @@ -63,9 +64,10 @@ def resize_to_target( target_pixels = original_pixels while new_bytes > target_bytes or new_pixels > target_pixels: - new_pixels = int(original_pixels * (target_bytes * margin / original_bytes)) - if target_pixels is not None: - new_pixels = min(new_pixels, target_pixels) + new_pixels = min( + int(original_pixels * (target_bytes * margin / original_bytes)), + target_pixels, + ) ratio = (new_pixels / original_pixels) ** 0.5 new_size = (int(img.width * ratio), int(img.height * ratio)) diff --git a/polybot/service.py b/polybot/service.py index 40eed9a..385b6c3 100644 --- a/polybot/service.py +++ b/polybot/service.py @@ -1,14 +1,15 @@ -from io import BytesIO import logging -import textwrap import mimetypes +import textwrap +from importlib.metadata import PackageNotFoundError, version +from io import BytesIO from time import time -from typing import List, Union, Optional, Type +from typing import Optional, Union + +import httpx from atproto import Client, models # type: ignore from atproto_client.exceptions import RequestException # type: ignore from mastodon import Mastodon as MastodonClient # type: ignore -from importlib.metadata import PackageNotFoundError, version -import httpx from .image import Image @@ -24,7 +25,7 @@ class PostError(Exception): pass -class Service(object): +class Service: name = None # type: str ellipsis_length = 1 max_length = None # type: int @@ -47,7 +48,7 @@ def auth(self) -> None: def setup(self) -> bool: raise NotImplementedError() - def longest_allowed(self, status: list, images: List[Image]) -> str: + def longest_allowed(self, status: list, images: list[Image]) -> str: max_len = self.max_length_image if images else self.max_length picked = status[0] for s in sorted(status, key=len): @@ -57,9 +58,9 @@ def longest_allowed(self, status: list, images: List[Image]) -> str: def post( self, - status: Union[str, List[str]], + status: Union[str, list[str]], wrap=False, - images: List[Image] = [], + images: list[Image] = [], lat: Optional[float] = None, lon: Optional[float] = None, in_reply_to_id=None, @@ -78,7 +79,7 @@ def post( def do_post( self, status: str, - images: List[Image] = [], + images: list[Image] = [], lat: Optional[float] = None, lon: Optional[float] = None, in_reply_to_id=None, @@ -88,7 +89,7 @@ def do_post( def do_wrapped( self, status, - images: List[Image] = [], + images: list[Image] = [], lat=None, lon=None, in_reply_to_id=None, @@ -101,9 +102,9 @@ def do_wrapped( first = True for line in wrapped: if first and len(wrapped) > 1: - line = "%s\u2026" % line + line = line + "\u2026" if not first: - line = "\u2026%s" % line + line = "\u2026" + line if images and first: out = self.do_post(line, images, lat, lon, in_reply_to_id) @@ -184,7 +185,7 @@ def setup(self): def do_post( self, status, - images: List[Image] = [], + images: list[Image] = [], lat=None, lon=None, in_reply_to_id=None, @@ -397,7 +398,7 @@ def setup(self): def do_post( self, status, - images: List[Image] = [], + images: list[Image] = [], lat=None, lon=None, in_reply_to_id=None, @@ -476,7 +477,7 @@ def setup(self): def do_post( self, status, - images: List[Image] = [], + images: list[Image] = [], lat=None, lon=None, in_reply_to_id=None, @@ -511,4 +512,4 @@ def do_post( raise PostError(e) -ALL_SERVICES: List[Type[Service]] = [Twitter, Mastodon, Bluesky] +ALL_SERVICES: list[type[Service]] = [Twitter, Mastodon, Bluesky] diff --git a/pyproject.toml b/pyproject.toml index d67289d..d33bb86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,3 +27,14 @@ dev = ["mypy>=1.13.0", "ruff>=0.8.1", "pytest>=8.3.4"] [tool.pytest.ini_options] pythonpath = ["."] + +[tool.ruff] +line-length = 110 + +[tool.ruff.lint] +select = ["E", "F", "UP", "I"] + +[tool.ruff.format] +quote-style = "single" +indent-style = "tab" +docstring-code-format = true diff --git a/tests/test_image.py b/tests/test_image.py index 298a056..bb72a83 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -1,8 +1,10 @@ -from polybot.image import Image from io import BytesIO -from PIL import Image as PILImage from pathlib import Path +from PIL import Image as PILImage + +from polybot.image import Image + def count_pixels(image): img = PILImage.open(BytesIO(image.data)) diff --git a/tests/test_mastodon.py b/tests/test_mastodon.py index 2ec2b2a..2d81474 100644 --- a/tests/test_mastodon.py +++ b/tests/test_mastodon.py @@ -1,4 +1,5 @@ import configparser + from polybot.service import Mastodon diff --git a/uv.lock b/uv.lock index d966c88..5d9cc47 100644 --- a/uv.lock +++ b/uv.lock @@ -557,7 +557,7 @@ wheels = [ [[package]] name = "polybot" -version = "0.9.4" +version = "0.9.5" source = { virtual = "." } dependencies = [ { name = "atproto" },