uncia/uncia/processing.py

146 lines
3.8 KiB
Python

import json, traceback
from izzylib import logging
from tldextract import extract
from urllib.parse import urlparse
from .database import db
from .functions import fetch, fetch_actor, get_inbox, push_message
from .messages import Message
relayed_objects = []
class ProcessData:
def __init__(self, request, response, data):
self.request = request
self.response = response
self.signature = request.signature
self.instance = request.instance
self.actor = request.actor
self.type = data.type.lower()
self.data = data
async def __call__(self):
return getattr(self, f'cmd_{self.type}')()
def valid_type(self):
if getattr(self, f'cmd_{self.type}', None):
return True
return False
def cmd_follow(self):
if self.actor.type.lower() != 'application':
#return self.response.json('No', status=403)
Message('reject', self.data.id, self.actor.id).send(self.actor.inbox)
logging.debug(f'Rejected non-application actor: {self.actor.id}')
return
with db.session as s:
req_app = s.get.config('require_approval')
if not (self.instance and not self.instance.followid):
data = [
get_inbox(self.actor),
self.actor.id
]
if req_app:
data.append(self.data.id)
self.instance = s.put.instance(*data)
if not self.instance:
logging.error(f'Something messed up when inserting "{self.signature.domain}" into the database')
return
message = Message('accept', self.data.id, self.instance.actor)
resp = message.send(self.instance.inbox)
if resp.status not in [200, 202]:
raise ValueError(f'Error when pushing to "{self.instance.inbox}"')
logging.verbose(f'Instance joined the relay: {self.instance.domain}')
def cmd_undo(self):
if self.actor.type.lower() != 'application':
return
object = fetch(self.data.object, sign=True) if isinstance(self.data.object, str) else self.data.object
if object.type != 'Follow':
return
with db.session as s:
s.delete.instance(self.signature.actor)
logging.debug(f'Removed instance from relay: {self.instance.domain}')
def cmd_announce(self):
if isinstance(self.data.object, dict):
object = self.data.object
object_id = self.data.object.id
else:
object = fetch(self.data.object, sign=True)
object_id = self.data.object
if not object:
obj_actor = None
logging.verbose('Failed to fetch object:', object_id)
else:
obj_actor = fetch(object.attributedTo, sign=True)
# I'm pretty sure object.attributedTo is a string, but leaving this here just in case
#obj_actor = fetch(object.attributedTo, sign=True) if isinstance(object.attributedTo, str) else object.attributedTo
if object.id in relayed_objects:
logging.verbose('Already relayed object:', object.id)
return
if obj_actor:
obj_handle = obj_actor.preferredUsername
obj_domain = urlparse(object.id).netloc
obj_domain_top = extract(obj_domain).registered_domain
with db.session as s:
if any(map(s.get.ban, [None], [obj_domain, obj_domain_top])) or s.get.ban(obj_handle, obj_domain):
logging.verbose('Refusing to relay object from banned domain or user:', object.id)
return
msg = Message('announce', object.id)
for instance in [row for row in s.get.instance_list() if row.domain not in [obj_domain, self.instance.domain]]:
try:
response = msg.send(instance.inbox)
except:
s.put.retry(instance.inbox, msg)
traceback.print_exc()
if not response or response.status not in [200, 202]:
logging.verbose(f'Failed to send announce object to {instance.domain}: {object.id}')
s.put.retry(instance.inbox, msg)
if response:
logging.debug(f'Server error {response.status}: {response.text}')
def cmd_create(self):
return self.cmd_announce()
def cmd_delete(self):
logging.verbose('Deletes not implemented yet')
def cmd_update(self):
logging.verbose('Updates not implemented yet')