|
|
|
@ -0,0 +1,106 @@ |
|
|
|
|
import traceback |
|
|
|
|
from misskey.exceptions import MisskeyAPIException |
|
|
|
|
|
|
|
|
|
from config import NOTIFICATION_BATCH_SIZE |
|
|
|
|
from parsing import parse_notification |
|
|
|
|
from db_utils import get_config, set_config |
|
|
|
|
from response import generate_response |
|
|
|
|
|
|
|
|
|
# Define your whitelist |
|
|
|
|
# TODO: move to config |
|
|
|
|
WHITELISTED_INSTANCES: list[str] = [] |
|
|
|
|
|
|
|
|
|
def process_notification(client, notification): |
|
|
|
|
'''Processes an individual notification''' |
|
|
|
|
notif_id = notification.get('id') |
|
|
|
|
|
|
|
|
|
user = notification.get('user', {}) |
|
|
|
|
username = user.get('username', 'unknown') |
|
|
|
|
host = user.get('host') # None if local user |
|
|
|
|
instance = host if host else 'local' |
|
|
|
|
|
|
|
|
|
if not (instance in WHITELISTED_INSTANCES or instance == 'local'): |
|
|
|
|
print(f'⚠️ Blocked notification from untrusted instance: {instance}') |
|
|
|
|
|
|
|
|
|
# Copy visibility of the post that was received when replying (so if people |
|
|
|
|
# don't want to dump a bunch of notes on home they don't have to) |
|
|
|
|
visibility = notification['note']['visibility'] |
|
|
|
|
if visibility != 'specified': |
|
|
|
|
visibility = 'home' |
|
|
|
|
|
|
|
|
|
notif_type = notification.get('type', 'unknown') |
|
|
|
|
print(f'📨 [{notif_type}] from @{username}@{instance}') |
|
|
|
|
|
|
|
|
|
# 🧠 Send to the parser |
|
|
|
|
parsed_command = parse_notification(notification, client) |
|
|
|
|
|
|
|
|
|
# Get the note Id to reply to |
|
|
|
|
note_id = notification.get('note', {}).get('id') |
|
|
|
|
|
|
|
|
|
# Get the response |
|
|
|
|
# TODO: Formalize exactly *what* is returned by this. Ideally just want to |
|
|
|
|
# handle two cases here: either we have a response, or we don't. |
|
|
|
|
# TODO: Return dictionaries instead of tuples. They handle multiple |
|
|
|
|
# elements a lot better as they're not position dependent |
|
|
|
|
response = generate_response(parsed_command) |
|
|
|
|
if isinstance(response, str): |
|
|
|
|
client.notes_create( |
|
|
|
|
text=response, |
|
|
|
|
reply_id=note_id, |
|
|
|
|
visibility=visibility |
|
|
|
|
) |
|
|
|
|
elif response: |
|
|
|
|
client.notes_create( |
|
|
|
|
text=response[0], |
|
|
|
|
reply_id=note_id, |
|
|
|
|
visibility=visibility, |
|
|
|
|
file_ids=response[1] |
|
|
|
|
#visible_user_ids=[] #todo: write actual visible users ids so pleromers can use the bot privately |
|
|
|
|
) |
|
|
|
|
return notif_id |
|
|
|
|
|
|
|
|
|
def process_notifications(client): |
|
|
|
|
'''Processes a batch of unread notifications. Returns False if there are |
|
|
|
|
no more notifications to process.''' |
|
|
|
|
|
|
|
|
|
last_seen_id = get_config('last_seen_notif_id') |
|
|
|
|
# process_notification writes to last_seen_id, so make a copy |
|
|
|
|
new_last_seen_id = last_seen_id |
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
notifications = client.i_notifications( |
|
|
|
|
# Fetch notifications we haven't seen yet |
|
|
|
|
since_id=last_seen_id, |
|
|
|
|
# Let misskey handle the filtering |
|
|
|
|
include_types=['mention', 'reply'], |
|
|
|
|
# And handle the batch size while we're at it |
|
|
|
|
limit=NOTIFICATION_BATCH_SIZE |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
# No notifications. Wait the poll period. |
|
|
|
|
if not notifications: |
|
|
|
|
return False |
|
|
|
|
|
|
|
|
|
# Iterate oldest to newest |
|
|
|
|
for notification in notifications: |
|
|
|
|
try: |
|
|
|
|
# Process notification and update new last seen id |
|
|
|
|
new_last_seen_id = process_notification(client, notification) |
|
|
|
|
except Exception as e: |
|
|
|
|
print(f'An exception has occured while processing a notification: {e}') |
|
|
|
|
print(traceback.format_exc()) |
|
|
|
|
|
|
|
|
|
# If we got as many notifications as we requested, there are probably |
|
|
|
|
# more in the queue |
|
|
|
|
return len(notifications) == NOTIFICATION_BATCH_SIZE |
|
|
|
|
|
|
|
|
|
except MisskeyAPIException as e: |
|
|
|
|
print(f'An exception has occured while reading notifications: {e}\n') |
|
|
|
|
print(traceback.format_exc()) |
|
|
|
|
finally: |
|
|
|
|
# Quality jank right here, but finally lets us update the last_seen_id |
|
|
|
|
# even if we hit an exception or return early |
|
|
|
|
if new_last_seen_id != last_seen_id: |
|
|
|
|
set_config('last_seen_notif_id', new_last_seen_id) |
|
|
|
|
|
|
|
|
|
return False |