from django.db import models from django.conf import settings from . import acobject, following, collection import kepi.bowler_pub.crypto as crypto from kepi.bowler_pub.utils import configured_url, uri_to_url import logging import re logger = logging.getLogger(name='kepi') LIST_NAMES = [ 'inbox', 'outbox', 'followers', 'following', ] ###################### class AcActor(acobject.AcObject): """ An AcActor is a kind of AcObject representing a person, an organisation, a bot, or anything else that can post stuff and interact with other AcActors. """ privateKey = models.TextField( blank=True, null=True, ) f_name = models.TextField( verbose_name='name', help_text = 'Your name, in human-friendly form. '+\ 'Something like "Alice Liddell".', ) publicKey = models.TextField( blank=True, null=True, verbose_name='public key', ) auto_follow = models.BooleanField( default=True, help_text="If True, follow requests will be accepted automatically.", ) f_summary = models.TextField( max_length=255, help_text="Your biography. Something like "+\ '"I enjoy falling down rabbitholes."', default='', verbose_name='bio', ) icon = models.ImageField( help_text="A small square image used to identify you.", null=True, verbose_name='icon', ) header = models.ImageField( help_text="A large image, wider than it's tall, which appears "+\ "at the top of your profile page.", null=True, verbose_name='header image', ) def _generate_id(self): return None def _check_provided_id(self): if not re.match(r'@[a-z0-9_-]+$', self.id, re.IGNORECASE): raise ValueError("Actor IDs begin with an @ "+\ "followed by one or more characters from "+\ "A-Z, a-z, 0-9, underscore, or hyphen. "+\ "You gave: "+self.id) def _after_create(self): if self.privateKey is None and self.publicKey is None: if not self.is_local: logger.warn('%s: Attempt to save remote without key', self.url) else: logger.info('%s: generating key pair.', self.url) key = crypto.Key() self.privateKey = key.private_as_pem() self.publicKey = key.public_as_pem() self.save() def __str__(self): if self.is_local: return '[{}]'.format( self.id, ) else: return '[remote user {}]'.format( self.id, ) @property def key_name(self): """ The name of this key. """ return '%s#main-key' % (self.url,) def list_url(self, name): return configured_url('COLLECTION_LINK', username = self.id[1:], listname = name, ) def __setitem__(self, name, value): if name=='privateKey': self.privateKey = value logger.info('%s: setting private key to %s', self, self.privateKey) self.save() elif name=='publicKey': from kepi.bowler_pub.utils import as_json self.publicKey = as_json(value, indent=None) logger.info('%s: setting public key to %s', self, self.publicKey) self.save() else: super().__setitem__(name, value) @property def icon_or_default(self): if self.icon: return self.icon which = ord(self.id[1]) % 10 return uri_to_url('/static/defaults/avatar_{}.jpg'.format( which, )) @property def header_or_default(self): if self.header: return self.header return uri_to_url('/static/defaults/header.jpg') def __getitem__(self, name): if self.is_local: if name in LIST_NAMES: return self.list_url(name) elif name=='privateKey': return self.privateKey elif name in ('preferredUsername', 'username'): return self.id[1:] elif name=='url': return self.url elif name=='feedURL': return configured_url('USER_FEED_LINK', username = self.id[1:], ) if name=='publicKey': if not self.publicKey: logger.debug('%s: we have no known public key', self) return None from json import loads result = loads(self.publicKey) logger.debug('%s: public key is %s', self, result) return result result = super().__getitem__(name) return result @property def activity_form(self): result = super().activity_form result['publicKey'] = { 'id': self.url + '#main-key', 'owner': self.url, 'publicKeyPem': self.publicKey, } for listname in LIST_NAMES: result[listname] = self.list_url(listname) for attribute in [ 'url', 'preferredUsername', ]: result[attribute] = self[attribute] for delendum in [ 'published', ]: del result[delendum] result['endpoints'] = { 'sharedInbox': configured_url('SHARED_INBOX_LINK'), } result['tag'] = [] result['attachment'] = [] result['icon'] = { "type":"Image", "mediaType":"image/jpeg", "url": self['avatar'], } result['image'] = { "type":"Image", "mediaType":"image/jpeg", "url": self['header'], } return result @property def moved_to(self): return None # TODO @property def fields(self): return [] # FIXME @property def emojis(self): return [] # FIXME @property def locked(self): return False # TODO @property def default_visibility(self): return 'public' # FIXME @property def default_sensitive(self): return False # FIXME @property def bot(self): return False # FIXME @property def language(self): return settings.KEPI['LANGUAGES'][0] # FIXME @property def following_count(self): return following.Following.objects.filter( follower=self.id, pending=False).count() @property def followers_count(self): return following.Following.objects.filter( following=self.id, pending=False).count() @property def statuses_count(self): return collection.Collection.get( user = self, collection = 'outbox', create_if_missing = True, ).members.count() class AcApplication(AcActor): pass class AcGroup(AcActor): pass class AcOrganization(AcActor): pass class AcPerson(AcActor): class Meta: verbose_name = 'person' verbose_name_plural = 'people' class AcService(AcActor): pass