def get_or_create_remote_server(domain): """ get info on a remote server """ try: return FederatedServer.objects.get(server_name=domain) except FederatedServer.DoesNotExist: pass try: data = get_data("https://%s/.well-known/nodeinfo" % domain) try: nodeinfo_url = data.get("links")[0].get("href") except (TypeError, KeyError): raise ConnectorException() data = get_data(nodeinfo_url) application_type = data.get("software", {}).get("name") application_version = data.get("software", {}).get("version") except ConnectorException: application_type = application_version = None server = FederatedServer.objects.create( server_name=domain, application_type=application_type, application_version=application_version, ) return server
def get_or_create_remote_server(domain, refresh=False): """get info on a remote server""" server = FederatedServer() try: server = FederatedServer.objects.get(server_name=domain) if not refresh: return server except FederatedServer.DoesNotExist: pass try: data = get_data(f"https://{domain}/.well-known/nodeinfo") try: nodeinfo_url = data.get("links")[0].get("href") except (TypeError, KeyError): raise ConnectorException() data = get_data(nodeinfo_url) application_type = data.get("software", {}).get("name") application_version = data.get("software", {}).get("version") except ConnectorException: if server.id: return server application_type = application_version = None server.server_name = domain server.application_type = application_type server.application_version = application_version server.save() return server
def get_or_create_remote_server(domain): ''' get info on a remote server ''' try: return FederatedServer.objects.get(server_name=domain) except FederatedServer.DoesNotExist: pass try: data = get_data('https://%s/.well-known/nodeinfo' % domain) try: nodeinfo_url = data.get('links')[0].get('href') except (TypeError, KeyError): raise ConnectorException() data = get_data(nodeinfo_url) application_type = data.get('software', {}).get('name') application_version = data.get('software', {}).get('version') except ConnectorException: application_type = application_version = None server = FederatedServer.objects.create( server_name=domain, application_type=application_type, application_version=application_version, ) return server
def handle_remote_webfinger(query): ''' webfingerin' other servers ''' user = None # usernames could be @user@domain or user@domain if not query: return None if query[0] == '@': query = query[1:] try: domain = query.split('@')[1] except IndexError: return None try: user = models.User.objects.get(username=query) except models.User.DoesNotExist: url = 'https://%s/.well-known/webfinger?resource=acct:%s' % \ (domain, query) try: data = get_data(url) except (ConnectorException, HTTPError): return None for link in data.get('links'): if link.get('rel') == 'self': try: user = activitypub.resolve_remote_id( models.User, link['href'] ) except KeyError: return None return user
def resolve_remote_id( remote_id, model=None, refresh=False, save=True, get_activity=False ): """take a remote_id and return an instance, creating if necessary""" if model: # a bonus check we can do if we already know the model if isinstance(model, str): model = apps.get_model(f"bookwyrm.{model}", require_ready=True) result = model.find_existing_by_remote_id(remote_id) if result and not refresh: return result if not get_activity else result.to_activity_dataclass() # load the data and create the object try: data = get_data(remote_id) except ConnectorException: raise ActivitySerializerError( f"Could not connect to host for remote_id: {remote_id}" ) # determine the model implicitly, if not provided # or if it's a model with subclasses like Status, check again if not model or hasattr(model.objects, "select_subclasses"): model = get_model_from_type(data.get("type")) # check for existing items with shared unique identifiers result = model.find_existing(data) if result and not refresh: return result if not get_activity else result.to_activity_dataclass() item = model.activity_serializer(**data) if get_activity: return item # if we're refreshing, "result" will be set and we'll update it return item.to_model(model=model, instance=result, save=save)
def set_related_field( model_name, origin_model_name, related_field_name, related_remote_id, data ): """load reverse related fields (editions, attachments) without blocking""" model = apps.get_model(f"bookwyrm.{model_name}", require_ready=True) origin_model = apps.get_model(f"bookwyrm.{origin_model_name}", require_ready=True) if isinstance(data, str): existing = model.find_existing_by_remote_id(data) if existing: data = existing.to_activity() else: data = get_data(data) activity = model.activity_serializer(**data) # this must exist because it's the object that triggered this function instance = origin_model.find_existing_by_remote_id(related_remote_id) if not instance: raise ValueError(f"Invalid related remote id: {related_remote_id}") # set the origin's remote id on the activity so it will be there when # the model instance is created # edition.parentWork = instance, for example model_field = getattr(model, related_field_name) if hasattr(model_field, "activitypub_field"): setattr(activity, getattr(model_field, "activitypub_field"), instance.remote_id) item = activity.to_model(model=model) # if the related field isn't serialized (attachments on Status), then # we have to set it post-creation if not hasattr(model_field, "activitypub_field"): setattr(item, related_field_name, instance) item.save()
def resolve_remote_id(remote_id, model=None, refresh=False, save=True): """ take a remote_id and return an instance, creating if necessary """ if model: # a bonus check we can do if we already know the model result = model.find_existing_by_remote_id(remote_id) if result and not refresh: return result # load the data and create the object try: data = get_data(remote_id) except ConnectorException: raise ActivitySerializerError( "Could not connect to host for remote_id in: %s" % (remote_id)) # determine the model implicitly, if not provided if not model: model = get_model_from_type(data.get("type")) # check for existing items with shared unique identifiers result = model.find_existing(data) if result and not refresh: return result item = model.activity_serializer(**data) # if we're refreshing, "result" will be set and we'll update it return item.to_model(model=model, instance=result, save=save)
def handle_remote_webfinger(query): """webfingerin' other servers""" user = None # usernames could be @user@domain or user@domain if not query: return None if query[0] == "@": query = query[1:] try: domain = query.split("@")[1] except IndexError: return None try: user = models.User.objects.get(username__iexact=query) except models.User.DoesNotExist: url = "https://%s/.well-known/webfinger?resource=acct:%s" % (domain, query) try: data = get_data(url) except (ConnectorException, HTTPError): return None for link in data.get("links"): if link.get("rel") == "self": try: user = activitypub.resolve_remote_id(link["href"], model=models.User) except (KeyError, activitypub.ActivitySerializerError): return None return user
def subscribe_remote_webfinger(query): """get subscribe template from other servers""" template = None # usernames could be @user@domain or user@domain if not query: return WebFingerError("invalid_username") if query[0] == "@": query = query[1:] try: domain = query.split("@")[1] except IndexError: return WebFingerError("invalid_username") url = f"https://{domain}/.well-known/webfinger?resource=acct:{query}" try: data = get_data(url) except (ConnectorException, HTTPError): return WebFingerError("user_not_found") for link in data.get("links"): if link.get("rel") == "http://ostatus.org/schema/1.0/subscribe": template = link["template"] return template
def get_remote_reviews(outbox): """ ingest reviews by a new remote bookwyrm user """ outbox_page = outbox + "?page=true&type=Review" data = get_data(outbox_page) # TODO: pagination? for activity in data["orderedItems"]: if not activity["type"] == "Review": continue activitypub.Review(**activity).to_model()
def get_remote_reviews(outbox): ''' ingest reviews by a new remote bookwyrm user ''' outbox_page = outbox + '?page=true&type=Review' data = get_data(outbox_page) # TODO: pagination? for activity in data['orderedItems']: if not activity['type'] == 'Review': continue activitypub.Review(**activity).to_model(Review)
def get_or_create_remote_server(domain): ''' get info on a remote server ''' try: return FederatedServer.objects.get(server_name=domain) except FederatedServer.DoesNotExist: pass data = get_data('https://%s/.well-known/nodeinfo' % domain) try: nodeinfo_url = data.get('links')[0].get('href') except (TypeError, KeyError): return None data = get_data(nodeinfo_url) server = FederatedServer.objects.create( server_name=domain, application_type=data['software']['name'], application_version=data['software']['version'], ) return server
def resolve_remote_id(model, remote_id, refresh=False, save=True): ''' take a remote_id and return an instance, creating if necessary ''' result = model.find_existing_by_remote_id(remote_id) if result and not refresh: return result # load the data and create the object try: data = get_data(remote_id) except (ConnectorException, ConnectionError): raise ActivitySerializerError( 'Could not connect to host for remote_id in %s model: %s' % \ (model.__name__, remote_id)) # check for existing items with shared unique identifiers if not result: result = model.find_existing(data) if result and not refresh: return result item = model.activity_serializer(**data) # if we're refreshing, "result" will be set and we'll update it return item.to_model(model, instance=result, save=save)