def _get_remote_followers(profile, exclude=None): """Get remote followers for a profile.""" followers = [] for follower in Profile.objects.filter(following=profile, user__isnull=True): if follower.handle != exclude: followers.append(generate_diaspora_profile_id(follower.handle, follower.guid)) return followers
def send_share(content_id): """Handle sending a share of a Content object to the federation layer. Currently we only deliver public shares. """ try: content = Content.objects.get(id=content_id, visibility=Visibility.PUBLIC, content_type=ContentType.SHARE, local=True) except Content.DoesNotExist: logger.warning("No local share found with id %s", content_id) return entity = make_federable_content(content) if entity: if settings.DEBUG: # Don't send in development mode return recipients = _get_remote_followers(content.author) if not content.share_of.local: # Send to original author recipients.append( generate_diaspora_profile_id(content.share_of.author.handle, content.share_of.author.guid), ) logger.debug("send_share - sending to recipients: %s", recipients) handle_send(entity, content.author, recipients) else: logger.warning("send_share - No entity for %s", content)
def test_handle_send_is_called(self, mock_maker, mock_send): send_share(self.share.id) mock_send.assert_called_once_with( "entity", self.share.author, [generate_diaspora_profile_id(self.content.author.handle, self.content.author.guid)], )
def test_exclude_is_excluded(self): followers = set(_get_remote_followers(self.user.profile, exclude=self.remote_follower.handle)) self.assertEqual( followers, { generate_diaspora_profile_id(self.remote_follower2.handle, self.remote_follower2.guid), } )
def test_send_reply_to_remote_author(self, mock_make, mock_forward, mock_sender): send_reply(self.reply2.id) mock_sender.assert_called_once_with("entity", self.reply2.author, [ generate_diaspora_profile_id(self.remote_content.author.handle, self.remote_content.author.guid), ]) self.assertTrue(mock_forward.called is False)
def test_limited_profile_retraction_not_sent_to_relay( self, mock_make, mock_send): send_profile_retraction(self.limited_profile) mock_send.assert_called_once_with( "entity", self.limited_profile, [ generate_diaspora_profile_id(self.remote_profile.handle, self.remote_profile.guid), ], )
def test_handle_send_is_called(self, mock_make, mock_send): send_profile_retraction(self.public_profile) mock_send.assert_called_once_with( "entity", self.public_profile, [ 'diaspora://[email protected]/profile/', generate_diaspora_profile_id(self.remote_profile.handle, self.remote_profile.guid), ], )
def test_send_reply__limited_content(self, mock_make, mock_forward, mock_sender): send_reply(self.limited_reply.id) mock_sender.assert_called_once_with( "entity", self.limited_reply.author, [( generate_diaspora_profile_id(self.remote_profile.handle, guid=self.remote_profile.guid), self.remote_profile.key, )], )
def test_handle_send_is_called__limited_content(self, mock_maker, mock_send): send_content(self.limited_content.id, recipient_id=self.remote_profile.id) mock_send.assert_called_once_with( "entity", self.limited_content.author, [( generate_diaspora_profile_id(self.remote_profile.handle, guid=self.remote_profile.guid), self.remote_profile.key, )], )
def _get_remote_participants_for_content(target_content, participants=None, exclude=None, include_remote=False): """Get remote participants for a target content. Look at both replies and shares of target local content. Does a recursive call to get also replies of shares, even if those shares are remote. """ if not participants: participants = [] if not include_remote and not target_content.local: return participants replies = Content.objects.filter(parent_id=target_content.id, visibility=Visibility.PUBLIC, local=False) for reply in replies: if reply.author.handle != exclude: participants.append(generate_diaspora_profile_id(reply.author.handle, reply.author.guid)) if target_content.content_type == ContentType.CONTENT: shares = Content.objects.filter(share_of_id=target_content.id, visibility=Visibility.PUBLIC, local=False) for share in shares: if share.author.handle != exclude: participants.append(generate_diaspora_profile_id(share.author.handle, share.author.guid)) participants = _get_remote_participants_for_content( share, participants, exclude=exclude, include_remote=True ) return participants
def send_follow_change(profile_id, followed_id, follow): """Handle sending of a local follow of a remote profile.""" try: profile = Profile.objects.get(id=profile_id, user__isnull=False) except Profile.DoesNotExist: logger.warning("send_follow_change - No local profile %s found to send follow with", profile_id) return try: remote_profile = Profile.objects.get(id=followed_id, user__isnull=True) except Profile.DoesNotExist: logger.warning("send_follow_change - No remote profile %s found to send follow for", followed_id) return if settings.DEBUG: # Don't send in development mode return entity = base.Follow(handle=profile.handle, target_handle=remote_profile.handle, following=follow) recipients = [ (generate_diaspora_profile_id(remote_profile.handle, remote_profile.guid), remote_profile.key), ] logger.debug("send_follow_change - sending to recipients: %s", recipients) handle_send(entity, profile, recipients) # Also trigger a profile send send_profile(profile_id, recipients=[generate_diaspora_profile_id(remote_profile.handle, remote_profile.guid)])
def test_forward_entity__limited_content(self, mock_send): entity = Comment(handle=self.limited_reply.author.handle, guid=self.limited_reply.guid) forward_entity(entity, self.limited_content.id) mock_send.assert_called_once_with( entity, self.limited_reply.author, [( generate_diaspora_profile_id( self.remote_limited_reply.author.handle, self.remote_limited_reply.author.guid, ), self.remote_limited_reply.author.key, )], parent_user=self.limited_content.author)
def extract_mentions(self): """ Extract mentions from an entity with ``raw_content``. :return: set """ if not hasattr(self, "raw_content"): return set() mentions = re.findall(r'@{[^;]+; [\w.-]+@[^}]+}', self.raw_content) if not mentions: return set() mentions = {s.split(';')[1].strip(' }') for s in mentions} mentions = {generate_diaspora_profile_id(s) for s in mentions} return mentions
def send_reply(content_id): """ Handle sending a Content object that is a reply out via the federation layer. """ try: content = Content.objects.get( id=content_id, visibility__in=(Visibility.PUBLIC, Visibility.LIMITED), content_type=ContentType.REPLY, local=True, ) except Content.DoesNotExist: logger.warning("No content found with id %s", content_id) return entity = make_federable_content(content) if not entity: logger.warning("send_reply - No entity for %s", content) if settings.DEBUG: # Don't send in development mode return # Send directly (remote parent) or as a relayable (local parent) if content.parent.local: forward_entity(entity, content.parent.id) else: # We only need to send to the original author parent_author = content.parent.author if content.visibility == Visibility.PUBLIC: recipients = [ generate_diaspora_profile_id(parent_author.handle, parent_author.guid), ] else: recipients = [ (generate_diaspora_profile_id(parent_author.handle, parent_author.guid), parent_author.key), ] logger.debug("send_reply - sending to recipients: %s", recipients) handle_send(entity, content.author, recipients)
def get_diaspora_profile_by_handle(handle): """ Return a local Profile suitable for the federation library profile using Diaspora handle. """ from socialhome.users.models import Profile # Circulars profile = Profile.objects.select_related('user').only('guid', 'user__username').get(handle=handle) profile_path = reverse("users:detail", kwargs={"username": profile.user.username}) return { "id": generate_diaspora_profile_id(handle, profile.guid), "profile_path": profile_path, # We don't support atom feeds yet, but since diaspora has a bug currently (0.7.3.x), # we need to specify something here. Let's use the profile url here too. # TODO remove this once diaspora releases the bug fix "atom_path": profile_path, }
def send_content(content_id, recipient_id=None): """ Handle sending a Content object out via the federation layer. """ try: content = Content.objects.get( id=content_id, visibility__in=(Visibility.PUBLIC, Visibility.LIMITED), content_type=ContentType.CONTENT, local=True, ) except Content.DoesNotExist: logger.warning("No local content found with id %s", content_id) return if recipient_id: try: recipient = Profile.objects.get(id=recipient_id, user__isnull=True) except Profile.DoesNotExist: logger.warning("No remote recipient found with id %s", recipient_id) return else: recipient = None entity = make_federable_content(content) if entity: if settings.DEBUG: # Don't send in development mode return if recipient: recipients = [ (generate_diaspora_profile_id(recipient.handle, recipient.guid), recipient.key), ] else: recipients = [settings.SOCIALHOME_RELAY_ID] recipients.extend(_get_remote_followers(content.author)) logger.debug("send_content - sending to recipients: %s", recipients) handle_send(entity, content.author, recipients) else: logger.warning("send_content - No entity for %s", content)
def parse_nodeinfo_document(doc, host): result = deepcopy(defaults) nodeinfo_version = doc.get('version', '1.0') result['host'] = host result['name'] = doc.get('metadata', {}).get('nodeName', host) result['version'] = doc.get('software', {}).get('version', '') result['platform'] = doc.get('software', {}).get('name', 'unknown').lower() if nodeinfo_version in ('1.0', '1.1'): inbound = doc.get('protocols', {}).get('inbound', []) outbound = doc.get('protocols', {}).get('outbound', []) protocols = sorted(list(set(inbound + outbound))) result['protocols'] = protocols else: result['protocols'] = doc.get('protocols', []) inbound = doc.get('services', {}).get('inbound', []) outbound = doc.get('services', {}).get('outbound', []) services = sorted(list(set(inbound + outbound))) result['services'] = services result['open_signups'] = doc.get('openRegistrations', False) result['activity']['users']['total'] = int_or_none( doc.get('usage', {}).get('users', {}).get('total')) result['activity']['users']['half_year'] = int_or_none( doc.get('usage', {}).get('users', {}).get('activeHalfyear')) monthly = int_or_none( doc.get('usage', {}).get('users', {}).get('activeMonth')) result['activity']['users']['monthly'] = monthly if monthly: result['activity']['users']['weekly'] = int( monthly * MONTHLY_USERS_WEEKLY_MULTIPLIER) result['activity']['local_posts'] = int_or_none( doc.get('usage', {}).get('localPosts')) result['activity']['local_comments'] = int_or_none( doc.get('usage', {}).get('localComments')) result['features'] = doc.get('metadata', {}) admin_handle = doc.get('metadata', {}).get('adminAccount', None) if admin_handle: result['organization']['account'] = generate_diaspora_profile_id( "%s@%s" % (admin_handle, host)) return result
def get_profile_by_handle(handle): return { "id": generate_diaspora_profile_id(handle, "1234"), "profile_path": "/profile/1234/", "atom_path": "/profile/1234/atom.xml", }
def test_generate_diaspora_profile_id(): assert generate_diaspora_profile_id( "*****@*****.**", "1234") == "diaspora://[email protected]/profile/1234" assert generate_diaspora_profile_id( "*****@*****.**") == "diaspora://[email protected]/profile/"
def test_send_to_given_recipients_only(self, mock_federable, mock_send): recipients = [generate_diaspora_profile_id(self.remote_profile.handle, self.remote_profile.guid)] send_profile(self.profile.id, recipients=recipients) mock_send.assert_called_once_with("profile", self.profile, recipients)
def _get_limited_recipients(sender, content): return [ (generate_diaspora_profile_id(profile.handle, profile.guid), profile.key) for profile in content.limited_visibilities.all() if profile.handle != sender ]