Something something like soundcloud but not like soundcloud.
Log in, upload records, done.
Simple, easy, KISS.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

activitypub.py 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. from flask import Blueprint, request, abort, current_app, Response, jsonify, flash, render_template, redirect, url_for
  2. from little_boxes import activitypub
  3. from little_boxes.httpsig import verify_request
  4. from activitypub.vars import Box
  5. from tasks import post_to_inbox
  6. from activitypub.utils import activity_from_doc, build_ordered_collection
  7. from models import Activity, User
  8. from flask_accept import accept_fallback
  9. from flask_babelex import gettext
  10. bp_ap = Blueprint("bp_ap", __name__)
  11. @bp_ap.route("/user/<string:name>/inbox", methods=["GET", "POST"])
  12. def user_inbox(name):
  13. be = activitypub.get_backend()
  14. if not be:
  15. abort(500)
  16. data = request.get_json(force=True)
  17. if not data:
  18. abort(500)
  19. current_app.logger.debug(f"req_headers={request.headers}")
  20. current_app.logger.debug(f"raw_data={data}")
  21. try:
  22. if not verify_request(request.method, request.path, request.headers, request.data):
  23. raise Exception("failed to verify request")
  24. except Exception:
  25. current_app.logger.exception("failed to verify request")
  26. try:
  27. data = be.fetch_iri(data["id"])
  28. except Exception:
  29. current_app.logger.exception(f"failed to fetch remote id " f"at {data['id']}")
  30. resp = {"error": "failed to verify request " "(using HTTP signatures or fetching the IRI)"}
  31. response = jsonify(resp)
  32. response.mimetype = "application/json; charset=utf-8"
  33. response.status = 422
  34. return response
  35. activity = activitypub.parse_activity(data)
  36. current_app.logger.debug(f"inbox_activity={activity}/{data}")
  37. post_to_inbox(activity)
  38. return Response(status=201)
  39. @bp_ap.route("/user/<string:name>/outbox", methods=["GET", "POST"])
  40. def user_outbox(name):
  41. be = activitypub.get_backend()
  42. if not be:
  43. abort(500)
  44. data = request.get_json(force=True)
  45. if not data:
  46. abort(500)
  47. current_app.logger.debug(f"req_headers={request.headers}")
  48. current_app.logger.debug(f"raw_data={data}")
  49. @bp_ap.route("/user/<string:name>/followings", methods=["GET"])
  50. @accept_fallback
  51. def followings(name):
  52. pcfg = {"title": gettext("%(username)s' followings", username=name)}
  53. user = User.query.filter(User.name == name).first()
  54. if not user:
  55. flash(gettext("User not found"), "error")
  56. return redirect(url_for("bp_main.home"))
  57. followings_list = user.actor[0].followings
  58. return render_template(
  59. "users/followings.jinja2", pcfg=pcfg, user=user, actor=user.actor[0], followings=followings_list
  60. )
  61. @bp_ap.route("/user/<string:name>/followings", methods=["GET", "POST"])
  62. @followings.support("application/json", "application/activity+json")
  63. def user_followings(name):
  64. be = activitypub.get_backend()
  65. if not be:
  66. abort(500)
  67. # data = request.get_json(force=True)
  68. # if not data:
  69. # abort(500)
  70. current_app.logger.debug(f"req_headers={request.headers}")
  71. # current_app.logger.debug(f"raw_data={data}")
  72. user = User.query.filter(User.name == name).first()
  73. if not user:
  74. abort(404)
  75. actor = user.actor[0]
  76. followings_list = actor.followings
  77. return jsonify(**build_ordered_collection(followings_list, actor.url, request.args.get("page"), switch_side=True))
  78. @bp_ap.route("/user/<string:name>/followers", methods=["GET"])
  79. @accept_fallback
  80. def followers(name):
  81. pcfg = {"title": gettext("%(username)s' followers", username=name)}
  82. user = User.query.filter(User.name == name).first()
  83. if not user:
  84. flash(gettext("User not found"), "error")
  85. return redirect(url_for("bp_main.home"))
  86. followers_list = user.actor[0].followers
  87. return render_template(
  88. "users/followers.jinja2", pcfg=pcfg, user=user, actor=user.actor[0], followers=followers_list
  89. )
  90. @bp_ap.route("/user/<string:name>/followers", methods=["GET", "POST"])
  91. @followers.support("application/json", "application/activity+json")
  92. def user_followers(name):
  93. be = activitypub.get_backend()
  94. if not be:
  95. abort(500)
  96. # data = request.get_json(force=True)
  97. # if not data:
  98. # abort(500)
  99. current_app.logger.debug(f"req_headers={request.headers}")
  100. # current_app.logger.debug(f"raw_data={data}")
  101. user = User.query.filter(User.name == name).first()
  102. if not user:
  103. abort(404)
  104. actor = user.actor[0]
  105. followers_list = actor.followers
  106. return jsonify(**build_ordered_collection(followers_list, actor.url, request.args.get("page")))
  107. @bp_ap.route("/inbox", methods=["GET", "POST"])
  108. def inbox():
  109. be = activitypub.get_backend()
  110. if not be:
  111. abort(500)
  112. data = request.get_json(force=True)
  113. if not data:
  114. abort(500)
  115. current_app.logger.debug(f"req_headers={request.headers}")
  116. current_app.logger.debug(f"raw_data={data}")
  117. try:
  118. if not verify_request(request.method, request.path, request.headers, request.data):
  119. raise Exception("failed to verify request")
  120. except Exception:
  121. current_app.logger.exception("failed to verify request")
  122. try:
  123. data = be.fetch_iri(data["id"])
  124. except Exception:
  125. current_app.logger.exception(f"failed to fetch remote id " f"at {data['id']}")
  126. resp = {"error": "failed to verify request " "(using HTTP signatures or fetching the IRI)"}
  127. response = jsonify(resp)
  128. response.mimetype = "application/json; charset=utf-8"
  129. response.status = 422
  130. return response
  131. activity = activitypub.parse_activity(data)
  132. current_app.logger.debug(f"inbox_activity={activity}/{data}")
  133. post_to_inbox(activity)
  134. return Response(status=201)
  135. @bp_ap.route("/outbox", methods=["GET", "POST"])
  136. def outbox():
  137. be = activitypub.get_backend()
  138. if not be:
  139. abort(500)
  140. data = request.get_json(force=True)
  141. if not data:
  142. abort(500)
  143. current_app.logger.debug(f"req_headers={request.headers}")
  144. current_app.logger.debug(f"raw_data={data}")
  145. @bp_ap.route("/outbox/<string:item_id>", methods=["GET", "POST"])
  146. def outbox_item(item_id):
  147. be = activitypub.get_backend()
  148. if not be:
  149. abort(500)
  150. # data = request.get_json()
  151. # if not data:
  152. # abort(500)
  153. current_app.logger.debug(f"req_headers={request.headers}")
  154. # current_app.logger.debug(f"raw_data={data}")
  155. current_app.logger.debug(f"activity url {be.activity_url(item_id)}")
  156. item = Activity.query.filter(Activity.box == Box.OUTBOX.value, Activity.url == be.activity_url(item_id)).first()
  157. if not item:
  158. abort(404)
  159. if item.meta_deleted:
  160. obj = activitypub.parse_activity(item.payload)
  161. resp = jsonify(**obj.get_tombstone().to_dict())
  162. resp.status_code = 410
  163. return resp
  164. current_app.logger.debug(f"item payload=={item.payload}")
  165. return jsonify(**activity_from_doc(item.payload))
  166. @bp_ap.route("/outbox/<string:item_id>/activity", methods=["GET", "POST"])
  167. def outbox_item_activity(item_id):
  168. be = activitypub.get_backend()
  169. if not be:
  170. abort(500)
  171. item = Activity.query.filter(Activity.box == Box.OUTBOX.value, Activity.url == be.activity_url(item_id)).first()
  172. if not item:
  173. abort(404)
  174. obj = activity_from_doc(item.payload)
  175. if item.meta_deleted:
  176. obj = activitypub.parse_activity(item.payload)
  177. resp = jsonify(**obj.get_object().get_tombstone().to_dict())
  178. resp.status_code = 410
  179. return resp
  180. if obj["type"] != activitypub.ActivityType.CREATE.value:
  181. abort(404)
  182. return jsonify(**obj["object"])