chapeau/kepi/bowler_pub/models/item.py

366 wiersze
8.0 KiB
Python

from django.db import models
from . import acobject, audience
from .. import PUBLIC_IDS
from django.conf import settings
import logging
import random
logger = logging.getLogger(name='kepi')
######################
class AcItem(acobject.AcObject):
f_content = models.CharField(
max_length=255,
blank=True,
)
f_attributedTo = models.CharField(
max_length=255,
blank=True,
)
serial = models.IntegerField(
default = 0,
)
def _generate_id(self):
if not self.f_attributedTo:
logger.info(' -- new object has no f_attributedTo')
return super()._generate_id()
max_so_far = AcItem.objects.filter(
f_attributedTo = self.f_attributedTo,
).aggregate(models.Max('serial'))['serial__max']
if max_so_far is None:
max_so_far = 0
self.serial = max_so_far + random.randint(0, 256)
logger.info(' -- max serial so far is %d; using serial %d',
max_so_far, self.serial)
# TODO: For now, we return the same IDs as the superclass.
# When I'm sure the serial numbers work and the tests pass,
# we'll have IDs in the form "/username/serial".
return super()._generate_id()
@property
def visibility(self):
audiences = audience.Audience.get_audiences_for(self)
audience_to = set(audiences.get('to', []))
audience_cc = set(audiences.get('cc', []))
logger.debug('%s: checking visibility in audiences',
self.id)
logger.debug(' To: %s', audience_to)
logger.debug(' Cc: %s', audience_cc)
if not audience_to.union(audience_cc):
logger.debug(' -- neither to nor cc, so direct')
return 'direct'
elif PUBLIC_IDS.intersection(audience_to):
return 'public'
elif PUBLIC_IDS.intersection(audience_cc):
return 'unlisted'
actor = self.actor
if actor is None:
logger.debug('%s: posted by %s, whom we don\'t know about',
self.id, self.f_attributedTo)
else:
logger.debug('%s: checking visibility from poster: %s',
self.id, actor)
followers_url = actor['followers']
logger.debug(' -- is %s in %s?',
followers_url, audience_to)
if followers_url in audience_to:
logger.debug(' -- yes, so private')
return 'private'
# By now, it's either direct or limited.
# Direct means there's a mention with a
# recipient's name in it.
mentions = self.mentions
logger.debug(' -- is %s in %s?',
audience_to, mentions)
if audience_to.intersection(mentions):
logger.debug(' -- yes, so it\'s direct')
return 'direct'
logger.debug(' -- fallback to limited')
return 'limited'
def __getitem__(self, name):
if self.is_local:
if name=='language':
return settings.LANGUAGE_CODE
result = super().__getitem__(name)
return result
@property
def text(self):
return self.f_content
@property
def html(self):
# FIXME obviously we need to quote this and stuff
return '<p>%s</p>' % (self.f_content,)
@property
def thread(self):
from kepi.bowler_pub.find import find
if hasattr(self, '_thread'):
return self._thread
result = self
logger.debug('Searching for thread of %s',
result)
while True:
parent = result['inReplyTo']
if parent is None:
break
logger.debug(' -- scanning its parent %s',
parent)
parent = find(
parent,
do_not_fetch = True)
if parent is None:
logger.debug(' -- which is remote and not cached')
break
result = parent
logger.debug(' -- result is %s',
result)
self._thread = result
return result
@property
def is_reply(self):
return self['inReplyTo'] is not None
@property
def ancestors(self):
# FIXME
return []
@property
def descendants(self):
# FIXME - do they want *all* descendants in *all* threads?
return []
@property
def in_reply_to_account_id(self):
parent = self['inReplyTo__obj']
if parent is None:
return None
return parent['attributedTo']
@property
def actor(self):
"""
The AcActor who posted this status.
We might have no AcActor record for the poster,
in which case this property will be None.
You can still find the name of the actor in f_attributedTo.
"""
from kepi.bowler_pub.find import find
return find(self.f_attributedTo, local_only=True)
@property
def account(self):
return self['attributedTo']
@property
def mentions(self):
from kepi.bowler_pub.models.mention import Mention
logger.info('Finding Mentions for %s', self)
return [x.to_actor for x in
Mention.objects.filter(from_status=self)]
@property
def conversation(self):
# FIXME I really don't understand conversation values
return None
@property
def activity_form(self):
result = super().activity_form
result['url'] = result['id']
# defaults
for f, default in [
('inReplyTo', None),
('sensitive', False),
('attachment', []),
('tag', []),
]:
if f not in result:
result[f] = default
if 'content' in result and 'contentMap' not in result:
result['contentMap'] = {
settings.LANGUAGE_CODE: result['content'],
}
return result
@property
def language(self):
return self['language']
@property
def sensitive(self):
# FIXME
return False
@property
def spoiler_text(self):
# FIXME
return ''
@property
def emojis(self):
# FIXME
return []
@property
def reblogs_count(self):
# FIXME
return 0
@property
def favourites_count(self):
# FIXME
return 0
@property
def reblogged(self):
return self.reblogs_count!=0
@property
def favourited(self):
return self.favourites_count!=0
@property
def muted(self):
# FIXME
return False
@property
def media_attachments(self):
# FIXME
return []
@property
def tags(self):
# FIXME
return []
@property
def card(self):
return None
@property
def poll(self):
return None
@property
def application(self):
return self['_application']
@property
def language(self):
return self['language']
@property
def pinned(self):
# FIXME
return False
@property
def posted_by(self):
return self.f_attributedTo
##############################
class AcArticle(AcItem):
pass
class AcAudio(AcItem):
pass
class AcDocument(AcItem):
pass
class AcEvent(AcItem):
pass
class AcImage(AcItem):
pass
class AcNote(AcItem):
# Why isn't Note a subclass of Document?
class Meta:
verbose_name = 'note'
def __str__(self):
content = self.f_content
if len(content)>70:
content = content[:68]+'...'
result = '(%s) "%s"' % (
self.id,
content)
return result
class AcPage(AcItem):
# i.e. a web page
pass
class AcPlace(AcItem):
pass
class AcProfile(AcItem):
pass
class AcRelationship(AcItem):
pass
class AcVideo(AcItem):
pass