e-dreyer / mastobot Goto Github PK
View Code? Open in Web Editor NEWA generic bot that allows anyone to create group bots on Mastodon.
License: GNU General Public License v3.0
A generic bot that allows anyone to create group bots on Mastodon.
License: GNU General Public License v3.0
Currently MastoBot
implements a wrapper and decorator
function which allows for the handling of exceptions in most functions making API calls through Mastodon.py
. This however is not the best solution. Exceptions are still able to slip through and there is actually no handling of exceptions and they are rather logged.
It should be investigated how a queue system for API calls can be created and operations then postponed until a connection error or rate limiting possibly sort itself out.
def handleMastodonExceptions(func) -> Callable:
def wrapper(self, *args, **kwargs):
try:
result = func(self, *args, **kwargs)
return result
except MastodonServerError as e:
logging.critical(f"MastodonServerError: {e}")
except MastodonIllegalArgumentError as e:
logging.critical(f"MastodonIllegalArgumentError: {e}")
except MastodonFileNotFoundError as e:
logging.critical(f"MastodonFileNotFoundError: {e}")
except MastodonNetworkError as e:
logging.critical(f"MastodonNetworkError: {e}")
except MastodonAPIError as e:
logging.critical(f"MastodonAPIError: {e}")
except MastodonMalformedEventError as e:
logging.critical(f"MastodonMalformedEventError: {e}")
except MastodonRatelimitError as e:
logging.critical(f"MastodonRatelimitError: {e}")
except MastodonVersionError as e:
logging.critical(f"MastodonVersionError: {e}")
except Exception as e:
logging.critical(f"Error in function {func.__name__}")
logging.critical(e)
raise e
return wrapper
MastoBot-3D
currently implements a reporting system, allowing users to mention
the bot as @3dprinting $report <some report message>
.
This allows for a custom response to reply to the report
with a templating system similar to the proposed one in #7 and also sends a message to the moderators.
This functionality can be extended to allow for more advanced commands to be sent to the bot and is generic enough to be part of MastoBot
A possinle solution would be to allow users to supply MastoBot
with a list of regex expressions and a list of callbacks. Thus allowing the user to implement actions similar to how abstract class methods are currently used to implement custom behavior.
Part of the simplification of creating a bot
is defining the profile of the bot
. Users should be able to create templates as mentioned in #7 to define the profile, bio and fields of the profile of the bot.
Currently there is a bug where entire status
objects are logged to the console when a new favorite is received.
2023-07-25 19:30:38,369 - [INFO] - mastoBot.favoriteStatus - โญ Status favorited: {'id': 110776380880090671, 'created_at': datetime.datetime(2023, 7, 25, 19, 30, 27, tzinfo=tzlocal()), 'in_reply_to_id': 110776325607636451, 'in_reply_to_account_id': 109538684057237209, 'sensitive': False, 'spoiler_text': '', 'visibility': 'public', 'language': 'en', 'uri': 'https://mstdn.social/users/botolo86/statuses/110776380783827192', 'url': 'https://mstdn.social/@botolo86/110776380783827192', 'replies_count': 0, 'reblogs_count': 0, 'favourites_count': 0, 'edited_at': None, 'favourited': False, 'reblogged': False, 'muted': False, 'bookmarked': False, 'content': '<p><span class="h-card"><a href="https://techhub.social/@Stark9837" class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@<span>Stark9837</span></a></span> <span class="h-card"><a href="https://tech.lgbt/@jsj" class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@<span>jsj</span></a></span> <span class="h-card"><a href="https://techhub.social/@3dprinting" class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@<span>3dprinting</span></a></span> It looks like the V6 color engine of AnkerMake will have zero purge.</p>', 'filtered': [], 'reblog': None, 'account': {'id': 109469707711538381, 'username': 'botolo86', 'acct': '[email protected]', 'display_name': 'Botolo', 'locked': False, 'bot': False, 'discoverable': False, 'group': False, 'created_at': datetime.datetime(2022, 12, 7, 0, 0, tzinfo=tzlocal()), 'note': '', 'url': 'https://mstdn.social/@botolo86', 'avatar': 'https://files.techhub.social/cache/accounts/avatars/109/469/707/711/538/381/original/18821310774daf64.jpg', 'avatar_static': 'https://files.techhub.social/cache/accounts/avatars/109/469/707/711/538/381/original/18821310774daf64.jpg', 'header': 'https://techhub.social/headers/original/missing.png', 'header_static': 'https://techhub.social/headers/original/missing.png', 'followers_count': 99, 'following_count': 72, 'statuses_count': 418, 'last_status_at': datetime.datetime(2023, 7, 25, 0, 0), 'emojis': [], 'fields': []}, 'media_attachments': [], 'mentions': [{'id': 109538684057237209, 'username': 'Stark9837', 'url': 'https://techhub.social/@Stark9837', 'acct': 'Stark9837'}, {'id': 110513374291535967, 'username': 'jsj', 'url': 'https://tech.lgbt/@jsj', 'acct': '[email protected]'}, {'id': 109897487128650085, 'username': '3dprinting', 'url': 'https://techhub.social/@3dprinting', 'acct': '3dprinting'}], 'tags': [], 'emojis': [], 'card': None, 'poll': None}
MastoBot
is intended to simplify the process of creating bots for Mastodon. This involves allowing the user to perform actions on notifications and filtering them, but often, users would like to respond to a notification with a post.
This can easily be done in code, but Jinja2
templates are by far the most sustainable and future-proof method to do this.
It would allow for better customization and better implementations, and templates could also be easily shared between users but still allow for a personalized touch.
Such a templating system is generic enough to be part of MastoBot
The Mastodon API had a great feature, which implements a push notification service. This not only allows apps to easily implement push notifications but also for ways of tracking new notifications in extended APIs.
It is this core functionality that is used in MastoBot
to allow for the detection of new mentions
, favorites
, follows
, etc.
This, however, comes at a cost. If this feature is used to track notifications. These notifications should either be dismissed on the API level or they should be written to some local database to allow for the tracking and persistence of the bot's state.
In early versions of MastoBot
, sqlite
, mysql
, firebase
and redis
was used to do this, but had since been removed in order to keep the overall design generic and Docker
setup minimalistic.
This, however, leads to a problem with mentions of the bot. Mentions can be used to simulate a group, similar to MastoBot-3D
. In doing so, notifications of mentions are dismissed in order to perform certain behaviors.
If users, however, mention the bot, the creators and owners of said bot will be unable to track notifications in most apps and in the Mastodon web UI.
A method needs to be implemented to either forward certain mentions which only mention the bot, thus it is directed at the bot and its team or other criteria, to the team or allow the teacking of such notifications.
As the usage of MastoBot-3D
has evolved from a simple boosting bot to an actual community and posts are being made by the MastoBot-3D
team, users are directing questions to the team and these posts are being missed. The only current solution is to implement this functionality in MastoBot-3D
or have users mention the admins directly.
This is a core functionality and is generic enough that it should be part of MastoBot
Currently, the notification
type update
isn't implemented. This notification was received for a user updating a type:poll
post, but will occur for any edited status
2023-07-26 07:14:14 2023-07-26 05:14:14,942 - [INFO] - main.processMention - ๐ฌ Mention processed: 2179152
2023-07-26 07:14:15 2023-07-26 05:14:15,257 - [INFO] - mastoBot.dismissNotification - ๐ญ Notification 2179152 dismissed
2023-07-26 07:14:15 2023-07-26 05:14:15,258 - [WARNING] - mastoBot._process_notifications - โ Invalid notification type: update
2023-07-26 07:14:25 2023-07-26 05:14:25,618 - [WARNING] - mastoBot._process_notifications - โ Invalid notification type: update
2023-07-26 07:14:36 2023-07-26 05:14:36,000 - [WARNING] - mastoBot._process_notifications - โ Invalid notification type: update
2023-07-26 07:14:46 2023-07-26 05:14:46,367 - [WARNING] - mastoBot._process_notifications - โ Invalid notification type: update
2023-07-26 07:14:56 2023-07-26 05:14:56,717 - [WARNING] - mastoBot._process_notifications - โ Invalid notification type: update
2023-07-26 07:15:07 2023-07-26 05:15:07,116 - [WARNING] - mastoBot._process_notifications - โ Invalid notification type: update
2023-07-26 07:15:17 2023-07-26 05:15:17,491 - [WARNING] - mastoBot._process_notifications - โ Invalid notification type: update
It simply isn't implemented due to the fact that Mastodon.py
documentation didn't include this type.
@handleMastodonExceptions
def _process_notifications(self, notifications: List[Dict[Any, Any]]) -> None:
for notification in notifications:
if notification.get("type") == "mention":
self.processMention(notification)
elif notification.get("type") == "reblog":
self.processReblog(notification)
elif notification.get("type") == "favourite":
self.processFavourite(notification)
elif notification.get("type") == "follow":
self.processFollow(notification)
elif notification.get("type") == "poll":
self.processPoll(notification)
elif notification.get("type") == "follow_request":
self.processFollowRequest(notification)
else:
logging.warning(f"โ \t Invalid notification type: {notification.get('type')}")
This can simply be fixed by adding a new abstract class method processUpdate
.
@handleMastodonExceptions
def _process_notifications(self, notifications: List[Dict[Any, Any]]) -> None:
for notification in notifications:
if notification.get("type") == "mention":
self.processMention(notification)
elif notification.get("type") == "reblog":
self.processReblog(notification)
elif notification.get("type") == "favourite":
self.processFavourite(notification)
elif notification.get("type") == "follow":
self.processFollow(notification)
elif notification.get("type") == "poll":
self.processPoll(notification)
elif notification.get("type") == "follow_request":
self.processFollowRequest(notification)
elif notification.get("type") == "update": # This should be added
self.processUpdate(notification)
else:
logging.warning(f"โ \t Invalid notification type: {notification.get('type')}")
The alignment for processMention
is wrong in the logging.
2023-07-26 09:33:53 2023-07-26 07:33:53,461 - [INFO] - mastoBot.refresh_rate - โ Refresh rate set to: 10
2023-07-26 09:33:53 2023-07-26 07:33:53,461 - [INFO] - mastoBot.__init__ - โ
Config and credentials initialized
2023-07-26 09:33:54 2023-07-26 07:33:54,346 - [INFO] - mastoBot.__init__ - โ
Mastodon.py initialized
2023-07-26 09:33:54 2023-07-26 07:33:54,348 - [INFO] - mastoBot.run - โ๏ธ Starting main loop
2023-07-26 09:34:48 2023-07-26 07:34:48,158 - [INFO] - mastoBot.reblogStatus - ๐ฃ๏ธ Status reblogged
2023-07-26 09:34:49 2023-07-26 07:34:49,570 - [INFO] - mastoBot.favoriteStatus - โญ Status favorited
2023-07-26 09:34:49 2023-07-26 07:34:49,573 - [INFO] - main.processMention - ๐ฌ Mention processed: 2179567
2023-07-26 09:34:49 2023-07-26 07:34:49,872 - [INFO] - mastoBot.dismissNotification - ๐ญ Notification 2179567 dismissed
There is an unhandled MastodonNetworkError exception in the decorator
function used for logging exceptions. This exception however is not exported by Mastodon.py
and further investigation should be done to see how it can be caught and handled.
Currently, I am unsure how to reproduce this bug and I am not sure what caused it. It happened while MastoBot-3D
was under light load with only a few pending mentions
and 1 follow
notification
2023-07-25 21:16:11 2023-07-25 19:16:11,061 - [CRITICAL] - mastoBot.wrapper - MastodonNetworkError: Could not complete request: ('Connection aborted.', ConnectionResetError(104, 'Connection reset by peer'))
2023-07-25 21:16:11 2023-07-25 19:16:11,064 - [CRITICAL] - mastoBot.wrapper - Error in function _process_notifications
2023-07-25 21:16:11 Traceback (most recent call last):
2023-07-25 21:16:11 File "/app/main.py", line 170, in <module>
2023-07-25 21:16:11 bot.run()
2023-07-25 21:16:11 File "/usr/local/lib/python3.9/site-packages/mastoBot/mastoBot.py", line 118, in run
2023-07-25 21:16:11 self._process_notifications(notifications)
2023-07-25 21:16:11 File "/usr/local/lib/python3.9/site-packages/mastoBot/mastoBot.py", line 43, in wrapper
2023-07-25 21:16:11 raise e
2023-07-25 21:16:11 File "/usr/local/lib/python3.9/site-packages/mastoBot/mastoBot.py", line 23, in wrapper
2023-07-25 21:16:11 result = func(self, *args, **kwargs)
2023-07-25 21:16:11 File "/usr/local/lib/python3.9/site-packages/mastoBot/mastoBot.py", line 129, in _process_notifications
2023-07-25 21:16:11 for notification in notifications:
2023-07-25 21:16:11 TypeError: 'NoneType' object is not iterable
The MastoBot package is intended to help simplify the process of developing a Mastodon bot. For this reason, certain commonly used requests and calculations should be included in the main package. These functions should stay true to the generic implementation of the MastoBot
class and should only serve as helper functions.
Furthermore, the API level provided by Mastodon.py
is abstracted and hidden away as a private variable. In Python, private variables can however still be externally accessed, but this is not good practice. Currently, users of MastoBot
are limited by this and are unable to implement the following functions.
As a user of the bot, it would be useful to have access to a simple function to determine whether a user is a follower of the bot account. Determinining whether a user is a follower, requires a call to the Mastodon.py
function account_relationships
. The output of this function should then further be processed to determine whether a user is a follower.
def isByFollower(self, status_id: int) -> bool:
api_mention = self.getStatus(status_id)
relationships = self._api.account_relationships(api_mention.get("account"))
return relationships[0].get("followed_by", False)
One common design decision when creating group bots, MastoBot-3D
being one, is to have responses and behviours being dependent on whether a post is a parent
status. A parent
status is one which is not a reply to any other status or notification and can be seen as a root
status. A child
status is one which has a parent
.
def isParentStatus(self, status_id: int) -> bool:
api_status = self.getStatus(status_id)
if api_status.get("in_reply_to_id"):
return False
else:
return True
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.