[mastodon] Fix locking issue with synchronous Mastodon.py and replace last_seen with notification_dismiss

This commit is contained in:
ogdbd3h5qze42igcv8wcrqk3 2023-03-19 01:44:30 +01:00 committed by missytake
parent 66fff6fd7d
commit dfd17aa27c
3 changed files with 58 additions and 33 deletions

View file

@ -4,15 +4,15 @@
#
# SPDX-License-Identifier: 0BSD
from asyncio import get_event_loop, sleep
from kibicara.platformapi import Censor, Spawner, Message
from kibicara.platforms.mastodon.model import MastodonAccount
from logging import getLogger
from mastodon import Mastodon, MastodonError
from asyncio import gather
import re
from logging import getLogger
logger = getLogger(__name__)
@ -21,35 +21,49 @@ class MastodonBot(Censor):
super().__init__(mastodon_account_model.hood)
self.model = mastodon_account_model
self.enabled = self.model.enabled
self.polling_interval_sec = 60
@classmethod
async def destroy_hood(cls, hood):
"""Removes all its database entries."""
for mastodon in await Mastodon.objects.filter(hood=hood).all():
await mastodon.delete()
async def run(self):
await self.model.instance.load()
self.account = Mastodon(
client_id=self.model.instance.client_id,
client_secret=self.model.instance.client_secret,
api_base_url=self.model.instance.name,
access_token=self.model.access_token,
)
await gather(self.poll(), self.push())
try:
await self.model.instance.load()
self.account = Mastodon(
client_id=self.model.instance.client_id,
client_secret=self.model.instance.client_secret,
api_base_url=self.model.instance.name,
access_token=self.model.access_token,
)
account_details = await get_event_loop().run_in_executor(
None, self.account.account_verify_credentials
)
if username := account_details.get("username"):
await self.model.update(username=username)
await gather(self.poll(), self.push())
except Exception as e:
logger.debug("Bot {0} threw an Error: {1}".format(self.model.hood.name, e))
finally:
logger.debug("Bot {0} stopped.".format(self.model.hood.name))
async def poll(self):
"""Get new mentions and DMs from Mastodon"""
while True:
try:
notifications = self.account.notifications()
notifications = await get_event_loop().run_in_executor(
None, self.account.notifications
)
except MastodonError as e:
logger.warning("%s in hood %s" % (e, self.model.hood.name))
continue
last_seen = int(self.model.last_seen)
for status in notifications:
try:
status_id = int(status["status"]["id"])
except KeyError:
continue # ignore notifications which don't have a status
if status_id <= last_seen:
continue # toot was already processed in the past
if status_id > int(self.model.last_seen):
await self.model.update(last_seen=str(status_id))
text = re.sub(r"<[^>]*>", "", status["status"]["content"])
text = re.sub(
"(?<=^|(?<=[^a-zA-Z0-9-_.]))@([A-Za-z]+[A-Za-z0-9-_]+)", "", text
@ -62,6 +76,10 @@ class MastodonBot(Censor):
await self.publish(Message(text, toot_id=status_id))
else:
await self.publish(Message(text))
await get_event_loop().run_in_executor(
None, self.account.notifications_dismiss, status["id"]
)
await sleep(self.polling_interval_sec)
async def push(self):
"""Push new Ticketfrei reports to Mastodon; if source is mastodon, boost it."""
@ -69,10 +87,14 @@ class MastodonBot(Censor):
message = await self.receive()
if hasattr(message, "tood_id"):
logger.debug("Boosting post %s: %s" % (message.tood_id, message.text))
self.account.status_reblog(message.tood_id)
await get_event_loop().run_in_executor(
None, self.account.status_reblog, message.tood_id
)
else:
logger.debug("Posting message: %s" % (message.text,))
self.account.status_post(message.text)
await get_event_loop().run_in_executor(
None, self.account.status_post, message.text
)
spawner = Spawner(MastodonAccount, MastodonBot)

View file

@ -23,8 +23,8 @@ class MastodonAccount(Model):
hood: ForeignKey(Hood)
instance: ForeignKey(MastodonInstance)
access_token: Text()
username: Text(allow_null=True) = None
enabled: Boolean() = False
last_seen: Text()
class Mapping(Mapping):
table_name = "mastodonaccounts"

View file

@ -3,9 +3,11 @@
#
# SPDX-License-Identifier: 0BSD
from asyncio import get_event_loop
from fastapi import APIRouter, Depends, HTTPException, Response, status
from ormantic.exceptions import NoMatch
from pydantic import BaseModel, validate_email, validator
from sqlite3 import IntegrityError
from kibicara.config import config
from kibicara.platforms.mastodon.bot import spawner
@ -13,6 +15,7 @@ from kibicara.platforms.mastodon.model import MastodonAccount, MastodonInstance
from kibicara.webapi.hoods import get_hood, get_hood_unauthorized
from mastodon import Mastodon, MastodonError, MastodonNetworkError
from mastodon.errors import MastodonIllegalArgumentError
from logging import getLogger
@ -149,10 +152,7 @@ async def mastodon_stop(mastodon=Depends(get_mastodon)):
@router.post(
"/",
status_code=status.HTTP_201_CREATED,
responses={
201: {"model": MastodonAccount},
422: {"model": HTTPError, "description": "Invalid Input"},
},
# TODO response_model
operation_id="create_mastodon",
)
async def mastodon_create(values: BodyMastodonAccount, hood=Depends(get_hood)):
@ -172,14 +172,17 @@ async def mastodon_create(values: BodyMastodonAccount, hood=Depends(get_hood)):
instance.client_id, instance.client_secret, api_base_url=values.instance_url
)
try:
access_token = account.log_in(values.email, values.password)
except MastodonError:
access_token = await get_event_loop().run_in_executor(
None, account.log_in, username, password
)
logger.debug(f"{access_token}")
mastodon = await MastodonAccount.objects.create(
hood=hood, instance=instance, access_token=access_token, enabled=True
)
spawner.start(mastodon)
return mastodon
except MastodonIllegalArgumentError:
logger.warning("Login to Mastodon failed.", exc_info=True)
return HTTPException(422, "Login to Mastodon failed")
return await MastodonAccount.objects.create(
hood=hood,
instance=instance,
access_token=access_token,
enabled=True,
last_seen="0",
)
raise HTTPException(status_code=status.HTTP_422_INVALID_INPUT)
except IntegrityError:
raise HTTPException(status_code=status.HTTP_409_CONFLICT)