diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bdf64c6..7082538 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -51,9 +51,10 @@ jobs: set -e # pip install from requirements.txt python -m pip install -r pip_requirements.txt - python -m pip install . --upgrade - coverage run --source=iembot setup.py test - coverage xml + python -m pip install . --upgrade --no-deps + python -m pytest --cov=iembot + python -m coverage xml + - name: Code coverage if: ${{ matrix.PYTHON_VERSION == '3.12' }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f99ea94..4041f0d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,9 +2,13 @@ ci: autoupdate_schedule: quarterly repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.6.8" + rev: "v0.7.2" hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format + - repo: https://github.com/tox-dev/pyproject-fmt + rev: '2.4.3' + hooks: + - id: pyproject-fmt diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..36b15cc --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +recursive-include iembot *.py +include src/iembot/data/startrek diff --git a/environment.yml b/environment.yml index 35a89c8..1af4fde 100644 --- a/environment.yml +++ b/environment.yml @@ -1,13 +1,17 @@ dependencies: + - atproto - codecov + - httpx - twisted>=18.4.0 - psycopg + - pyiem - pytest - pytest-cov - pytest-runner # bot non-async twitter - python-twitter - service_identity + - setuptools_scm # cython is a lame requirement from rabbit hole of cartopy - cython - cartopy diff --git a/pip_requirements.txt b/pip_requirements.txt index 81e39f0..ca260d5 100644 --- a/pip_requirements.txt +++ b/pip_requirements.txt @@ -1,6 +1,4 @@ # generates RSS feedgen -# get pyiem from github -git+https://github.com/akrherz/pyIEM.git # twisted memcached txyam2 diff --git a/pyproject.toml b/pyproject.toml index 15e608b..ebf0805 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,39 @@ +[build-system] +build-backend = "setuptools.build_meta" + +requires = [ "setuptools" ] + +[project] +name = "iembot" +description = "A poorly written XMPP bot that does other things" +readme = "README.md" +license = { "text" = "Apache" } +authors = [ + { name = "daryl herzmann", email = "akrherz@gmail.com" }, +] +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] +dynamic = [ + "version", +] + +urls."Homepage" = "https://github.com/akrherz/iembot" + +[tool.setuptools_scm] +# can be empty if no extra settings are needed, presence enables setuptools_scm [tool.ruff] -line-length = 79 target-version = "py39" -[tool.ruff.lint] -select = [ - "E", - "F", - "I", +line-length = 79 +lint.select = [ + "E", + "F", + "I", ] diff --git a/setup.cfg b/setup.cfg index 00c8fa3..342e9fb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,14 +1,3 @@ -[metadata] -name = iembot -author = daryl herzmann -author_email = akrherz@gmail.com -url = https://github.com/akrherz/iembot -keywords = meteorology -license = Apache -license-file = LICENSE -description = An XMPP Message Router, sort of. -description-file = README.md - [aliases] test = pytest @@ -21,7 +10,7 @@ include_package_data = True package_dir = = src packages = find: -setup_requires = setuptools_scm + [options.packages.find] where = src diff --git a/src/iembot/basicbot.py b/src/iembot/basicbot.py index 632c109..b803afc 100644 --- a/src/iembot/basicbot.py +++ b/src/iembot/basicbot.py @@ -363,6 +363,18 @@ def tweet(self, user_id, twttxt, **kwargs): Tweet a message """ twttxt = botutil.safe_twitter_text(twttxt) + adf = threads.deferToThread( + botutil.at_send_message, + self, + user_id, + twttxt, + **kwargs, + ) + adf.addErrback( + botutil.email_error, + self, + f"User: {user_id}, Text: {twttxt} Hit double exception", + ) df = threads.deferToThread( botutil.tweet, self, diff --git a/src/iembot/util.py b/src/iembot/util.py index f41e6ae..71c1d05 100644 --- a/src/iembot/util.py +++ b/src/iembot/util.py @@ -17,6 +17,8 @@ from io import BytesIO from zoneinfo import ZoneInfo +import atproto +import httpx import mastodon import requests import twitter @@ -29,7 +31,6 @@ from twisted.words.xish import domish from twitter.error import TwitterError -# local import iembot TWEET_API = "https://api.twitter.com/2/tweets" @@ -41,6 +42,45 @@ DISABLE_TWITTER_CODES = [89, 185, 226, 326, 64] +def at_send_message(bot, user_id, msg: str, **kwargs): + """Send a message to the ATmosphere.""" + if bot.tw_users.get(user_id, {}).get("at_handle") is None: + return None + media = kwargs.get("twitter_media") + img = None + if media is not None: + try: + resp = httpx.get(media, timeout=30) + resp.raise_for_status() + img = resp.content + # AT has a size limit of 976.56KB + if len(img) > 1_000_000: + img = None + except Exception as exp: + log.err(exp) + + client = atproto.Client() + client.login( + bot.tw_users[user_id]["at_handle"], + bot.tw_users[user_id]["at_app_pass"], + ) + if msg.find("http") > -1: + parts = msg.split("http") + msg = ( + atproto.client_utils.TextBuilder() + .text(parts[0]) + .link("link", f"http{parts[1]}") + ) + + if img: + res = client.send_image(msg, image=img, image_alt="IEMBot Image TBD") + else: + res = client.send_post(msg) + # for now + log.msg(repr(res)) + return res + + def tweet(bot, user_id, twttxt, **kwargs): """Blocking tweet method.""" if user_id not in bot.tw_users: @@ -662,8 +702,8 @@ def load_twitter_from_db(txn, bot): twusers = {} txn.execute( "SELECT user_id, access_token, access_token_secret, screen_name, " - "iem_owned from " - f"{bot.name}_twitter_oauth WHERE access_token is not null and " + "iem_owned, at_handle, at_app_pass from " + "iembot_twitter_oauth WHERE access_token is not null and " "access_token_secret is not null and user_id is not null and " "screen_name is not null and not disabled" ) @@ -674,6 +714,8 @@ def load_twitter_from_db(txn, bot): "access_token": row["access_token"], "access_token_secret": row["access_token_secret"], "iem_owned": row["iem_owned"], + "at_handle": row["at_handle"], + "at_app_pass": row["at_app_pass"], } bot.tw_users = twusers log.msg(f"load_twitter_from_db(): {txn.rowcount} oauth tokens found")