forked from forks/microblog.pub
Enable Webmentions for public posts
This commit is contained in:
parent
4d968264f2
commit
018b7bf553
7 changed files with 65 additions and 7 deletions
|
@ -32,8 +32,8 @@ from config import MEDIA_CACHE
|
||||||
from config import _drop_db
|
from config import _drop_db
|
||||||
from core import feed
|
from core import feed
|
||||||
from core.activitypub import activity_url
|
from core.activitypub import activity_url
|
||||||
from core.activitypub import post_to_outbox
|
|
||||||
from core.activitypub import new_context
|
from core.activitypub import new_context
|
||||||
|
from core.activitypub import post_to_outbox
|
||||||
from core.meta import Box
|
from core.meta import Box
|
||||||
from core.meta import MetaKey
|
from core.meta import MetaKey
|
||||||
from core.meta import _meta
|
from core.meta import _meta
|
||||||
|
|
|
@ -24,6 +24,7 @@ from core.activitypub import Box
|
||||||
from core.activitypub import _actor_hash
|
from core.activitypub import _actor_hash
|
||||||
from core.activitypub import _add_answers_to_question
|
from core.activitypub import _add_answers_to_question
|
||||||
from core.activitypub import _cache_actor_icon
|
from core.activitypub import _cache_actor_icon
|
||||||
|
from core.activitypub import is_from_outbox
|
||||||
from core.activitypub import post_to_outbox
|
from core.activitypub import post_to_outbox
|
||||||
from core.activitypub import save_reply
|
from core.activitypub import save_reply
|
||||||
from core.activitypub import update_cached_actor
|
from core.activitypub import update_cached_actor
|
||||||
|
@ -48,6 +49,7 @@ from core.shared import p
|
||||||
from core.tasks import Tasks
|
from core.tasks import Tasks
|
||||||
from utils import opengraph
|
from utils import opengraph
|
||||||
from utils.media import is_video
|
from utils.media import is_video
|
||||||
|
from utils.webmentions import discover_webmention_endpoint
|
||||||
|
|
||||||
blueprint = flask.Blueprint("tasks", __name__)
|
blueprint = flask.Blueprint("tasks", __name__)
|
||||||
|
|
||||||
|
@ -305,6 +307,41 @@ def task_cache_attachment() -> _Response:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/task/send_webmention", methods=["POST"])
|
||||||
|
def task_send_webmention() -> _Response:
|
||||||
|
task = p.parse(flask.request)
|
||||||
|
app.logger.info(f"task={task!r}")
|
||||||
|
note_url = task.payload["note_url"]
|
||||||
|
link = task.payload["link"]
|
||||||
|
remote_id = task.payload["remote_id"]
|
||||||
|
try:
|
||||||
|
app.logger.info(f"trying to send webmention source={note_url} target={link}")
|
||||||
|
webmention_endpoint = discover_webmention_endpoint(link)
|
||||||
|
if not webmention_endpoint:
|
||||||
|
app.logger.info("no webmention endpoint")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
resp = requests.post(
|
||||||
|
webmention_endpoint,
|
||||||
|
data={"source": note_url, "target": link},
|
||||||
|
headers={"User-Agent": config.USER_AGENT},
|
||||||
|
)
|
||||||
|
app.logger.info(f"webmention endpoint resp={resp}/{resp.text}")
|
||||||
|
resp.raise_for_status()
|
||||||
|
except HTTPError as err:
|
||||||
|
app.logger.exception("request failed")
|
||||||
|
if 400 >= err.response.status_code >= 499:
|
||||||
|
app.logger.info("client error, no retry")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
raise TaskError() from err
|
||||||
|
except Exception as err:
|
||||||
|
app.logger.exception(f"failed to cache actor for {link}/{remote_id}/{note_url}")
|
||||||
|
raise TaskError() from err
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/task/cache_actor", methods=["POST"])
|
@blueprint.route("/task/cache_actor", methods=["POST"])
|
||||||
def task_cache_actor() -> _Response:
|
def task_cache_actor() -> _Response:
|
||||||
task = p.parse(flask.request)
|
task = p.parse(flask.request)
|
||||||
|
@ -319,10 +356,18 @@ def task_cache_actor() -> _Response:
|
||||||
|
|
||||||
# Fetch the Open Grah metadata if it's a `Create`
|
# Fetch the Open Grah metadata if it's a `Create`
|
||||||
if activity.has_type(ap.ActivityType.CREATE):
|
if activity.has_type(ap.ActivityType.CREATE):
|
||||||
links = opengraph.links_from_note(activity.get_object().to_dict())
|
obj = activity.get_object()
|
||||||
|
links = opengraph.links_from_note(obj.to_dict())
|
||||||
if links:
|
if links:
|
||||||
Tasks.fetch_og_meta(iri)
|
Tasks.fetch_og_meta(iri)
|
||||||
|
|
||||||
|
# Send Webmentions only if it's from the outbox, and public
|
||||||
|
if (
|
||||||
|
is_from_outbox(obj)
|
||||||
|
and ap.get_visibility(obj) == ap.Visibility.PUBLIC
|
||||||
|
):
|
||||||
|
Tasks.send_webmentions(activity, links)
|
||||||
|
|
||||||
if activity.has_type(ap.ActivityType.FOLLOW):
|
if activity.has_type(ap.ActivityType.FOLLOW):
|
||||||
if actor.id == config.ID:
|
if actor.id == config.ID:
|
||||||
# It's a new following, cache the "object" (which is the actor we follow)
|
# It's a new following, cache the "object" (which is the actor we follow)
|
||||||
|
|
|
@ -6,11 +6,11 @@ from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
from bleach import linkify
|
||||||
from itsdangerous import JSONWebSignatureSerializer
|
from itsdangerous import JSONWebSignatureSerializer
|
||||||
from little_boxes import strtobool
|
from little_boxes import strtobool
|
||||||
from little_boxes.activitypub import CTX_AS as AP_DEFAULT_CTX
|
from little_boxes.activitypub import CTX_AS as AP_DEFAULT_CTX
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
from bleach import linkify
|
|
||||||
|
|
||||||
import sass
|
import sass
|
||||||
from utils.emojis import _load_emojis
|
from utils.emojis import _load_emojis
|
||||||
|
|
|
@ -9,6 +9,7 @@ from little_boxes.errors import NotAnActivityError
|
||||||
import config
|
import config
|
||||||
from core.activitypub import _answer_key
|
from core.activitypub import _answer_key
|
||||||
from core.activitypub import handle_replies
|
from core.activitypub import handle_replies
|
||||||
|
from core.activitypub import new_context
|
||||||
from core.activitypub import post_to_outbox
|
from core.activitypub import post_to_outbox
|
||||||
from core.activitypub import update_cached_actor
|
from core.activitypub import update_cached_actor
|
||||||
from core.db import DB
|
from core.db import DB
|
||||||
|
@ -163,6 +164,7 @@ def _follow_process_inbox(activity: ap.Follow, new_meta: _NewMeta) -> None:
|
||||||
actor_id = activity.get_actor().id
|
actor_id = activity.get_actor().id
|
||||||
accept = ap.Accept(
|
accept = ap.Accept(
|
||||||
actor=config.ID,
|
actor=config.ID,
|
||||||
|
context=new_context(activity),
|
||||||
object={
|
object={
|
||||||
"type": "Follow",
|
"type": "Follow",
|
||||||
"id": activity.id,
|
"id": activity.id,
|
||||||
|
|
|
@ -3,6 +3,7 @@ from datetime import datetime
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
from little_boxes import activitypub as ap
|
from little_boxes import activitypub as ap
|
||||||
from poussetaches import PousseTaches
|
from poussetaches import PousseTaches
|
||||||
|
@ -40,6 +41,18 @@ class Tasks:
|
||||||
|
|
||||||
p.push({"url": url, "iri": iri}, "/task/cache_emoji")
|
p.push({"url": url, "iri": iri}, "/task/cache_emoji")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def send_webmentions(activity: ap.Create, links: Set[str]) -> None:
|
||||||
|
for link in links:
|
||||||
|
p.push(
|
||||||
|
{
|
||||||
|
"link": link,
|
||||||
|
"note_url": activity.get_object().get_url(),
|
||||||
|
"remote_id": activity.id,
|
||||||
|
},
|
||||||
|
"/task/send_webmention",
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cache_emojis(activity: ap.BaseActivity) -> None:
|
def cache_emojis(activity: ap.BaseActivity) -> None:
|
||||||
for emoji in activity.get_emojis():
|
for emoji in activity.get_emojis():
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import json
|
|
||||||
|
|
||||||
import little_boxes.activitypub as ap
|
import little_boxes.activitypub as ap
|
||||||
import mf2py
|
import mf2py
|
||||||
import requests
|
import requests
|
||||||
|
@ -49,7 +47,7 @@ def lookup(url: str) -> ap.BaseActivity:
|
||||||
# Maybe the page was JSON-LD?
|
# Maybe the page was JSON-LD?
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
return ap.parse_activity(data)
|
return ap.parse_activity(data)
|
||||||
except json.JSONDecodeError:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Try content negotiation (retry with the AP Accept header)
|
# Try content negotiation (retry with the AP Accept header)
|
||||||
|
|
|
@ -40,7 +40,7 @@ def _discover_webmention_endoint(url: str) -> Optional[str]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def discover_webmention_endoint(url: str) -> Optional[str]:
|
def discover_webmention_endpoint(url: str) -> Optional[str]:
|
||||||
"""Discover the Webmention endpoint of a given URL, if any.
|
"""Discover the Webmention endpoint of a given URL, if any.
|
||||||
|
|
||||||
Passes all the tests at https://webmention.rocks!
|
Passes all the tests at https://webmention.rocks!
|
||||||
|
|
Loading…
Reference in a new issue