activities.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import celery
  2. import json
  3. import rdflib
  4. import requests
  5. import requests_http_signature
  6. from .. import activitypub
  7. from .. import feeds
  8. from .. import model
  9. from .. import settings
  10. from . import broker_url
  11. from . import broker
  12. from . import database_session
  13. log = celery.utils.log.get_task_logger(__name__)
  14. log.setLevel(settings.LOG_LEVEL)
  15. # The following is a decorator that accepts a dictionary as input and adds it to
  16. # _PATTERNS_ together with the decorated function. The dictionary is used as a
  17. # pattern for matching incoming Activities. If an incoming Activity matches one
  18. # of the patters in _PATTERNS_, then the corresponding function is executed.
  19. _PATTERNS_ = []
  20. def pattern(activity_pattern):
  21. def closure(func):
  22. def decorator(*args, **kwargs):
  23. func(*args, **kwargs)
  24. global _PATTERNS_
  25. _PATTERNS_.append((activity_pattern, decorator))
  26. return decorator
  27. return closure
  28. @broker.task
  29. def perform(actor_uri, activity):
  30. """
  31. This task is responsible for accepting an incoming Activity after it's been
  32. validated (see tasks.delivery) and decide what to do with it.
  33. :param actor_uri: URI of the Actor that has received the Activity
  34. :param activity: the Activity that was sent to the Actor
  35. """
  36. activity = activitypub.Activity(activity)
  37. activity.node('actor')
  38. activity.node('object')
  39. with database_session() as database:
  40. # Recreate the actor class from its URI
  41. actor = model.from_uri(database, actor_uri)
  42. if not actor:
  43. log.debug('Actor {} doesn\'t exist. Ignoring incoming Activity.'.format(actor_uri))
  44. return
  45. for activity_pattern, function in _PATTERNS_:
  46. if activity.match(activity_pattern):
  47. return function(database, actor, activity)
  48. log.debug('Activity {} did not match any pattern. Ignoring incoming Activity.'.format(activity['id']))
  49. @pattern({
  50. 'type': 'Follow',
  51. 'id': {},
  52. 'actor': {
  53. 'id': {}
  54. },
  55. 'object': {
  56. # 'type': 'Person',
  57. 'id': {}
  58. },
  59. })
  60. def follow(database, actor, activity):
  61. log.debug('Handling incoming Follow Activity.')
  62. # A Follow request should be sent to the right Actor
  63. if activity['object']['id'] != actor.local_uri:
  64. log.debug('Ignoring Activity {}. Actor {} has received a Follow request addressed to Actor {}' \
  65. .format(activity['id'], actor.local_uri, activity['object']['id']))
  66. return
  67. # Check if the local actor is already following the remote actor
  68. if database.query(
  69. database.query(model.Collection) \
  70. .filter(model.Collection.uri == actor.following_uri) \
  71. .filter(model.Collection.item == activity['actor']['id']) \
  72. .exists()
  73. ).scalar():
  74. log.info('Actor {} is already following {}. Ignoring Follow request.'.format(actor.local_uri, activity['actor']['id']))
  75. return
  76. # Add the remote actor to our followers collection
  77. database.add(model.Collection(uri = actor.followers_uri,
  78. item = activity['actor']['id']))
  79. # Add a feed
  80. database.add(model.Feed(
  81. actor_uri = actor.local_uri,
  82. content = json.dumps(feeds.follow(activity['actor'], actor.jsonld))
  83. ))
  84. # Automatically accept Follow requests and add the remote actor to the
  85. # following collection
  86. database.add(model.Collection(uri = actor.following_uri,
  87. item = activity['actor']['id']))
  88. # Add a feed
  89. database.add(model.Feed(
  90. actor_uri = actor.local_uri,
  91. content = json.dumps(feeds.follow(actor.jsonld, activity['actor']))
  92. ))
  93. # Cache a copy of the remote actor for quick lookups
  94. database.merge(model.Resource(
  95. uri = activity['actor']['id'],
  96. document = json.dumps(activity['actor'])))
  97. # Commit before sending the Accept Activity
  98. database.commit()
  99. # Create and send Accept Activity
  100. activitypub.Accept(actor = actor.uri,
  101. object = activity['id']) \
  102. .distribute()
  103. @pattern({
  104. 'type': 'Accept',
  105. 'id': {},
  106. 'actor': {
  107. 'id': {}
  108. },
  109. 'object': {
  110. 'type': 'Follow',
  111. 'id': {},
  112. 'actor': {},
  113. 'object': {}
  114. },
  115. })
  116. def accept_follow(database, actor, activity):
  117. """
  118. Accept of a Follow request.
  119. """
  120. log.debug('Handling incoming Accept(Follow) Activity.')
  121. follow_activity = activity['object']
  122. follow_activity.node('actor')
  123. follow_activity.node('object')
  124. if actor.local_uri == follow_activity['actor']['id'] \
  125. and follow_activity['object']['id'] == activity['actor']['id']:
  126. log.debug('{} has accepted follow request from {}'.format(
  127. activity['actor']['id'], actor.local_uri))
  128. # Now that the request was accepted, we add the actor to the "followers" list
  129. database.add(model.Collection(uri = actor.followers_uri,
  130. item = activity['actor']['id']))
  131. # Add a feed
  132. database.add(model.Feed(
  133. actor_uri = actor.local_uri,
  134. content = json.dumps(feeds.follow(activity['actor'], actor.jsonld))
  135. ))
  136. database.commit()