Source code for indieweb_utils.webmentions.send

from dataclasses import dataclass
from typing import List, Optional
from urllib import parse as url_parse

import requests

from ..utils.urls import _is_http_url
from . import discovery


@dataclass
class Header:
    name: str
    value: str


class MissingSourceError(Exception):
    pass


class MissingTargetError(Exception):
    pass


class UnsupportedProtocolError(Exception):
    """
    Raised if a provided webmention source or target uses a protocol other than http:// or https://.
    """

    pass


class TargetIsNotApprovedDomain(Exception):
    pass


class GenericWebmentionError(Exception):
    pass


class CouldNotConnectToWebmentionEndpoint(Exception):
    pass


[docs]@dataclass class SendWebmentionResponse: title: str description: str url: str status_code: Optional[int] headers: List[Header]
def _validate_webmention(source: str, target: str): """ Check if a webmention has a provided source, target, and valid protocol. """ if not source: raise MissingSourceError("A source was not provided.") if not target: raise MissingTargetError("A target was not provided.") if not _is_http_url(source) or not _is_http_url(target): raise UnsupportedProtocolError("Only HTTP/HTTPS URLs are supported.")
[docs]def send_webmention( source: str, target: str, me: str = None, code: str = None, realm: str = None, target_webmention_endpoint: str = None, ) -> SendWebmentionResponse: """ Send a webmention to a target URL. :param source: The source URL of the webmention. :type source: str :param target: The target URL to which you want to send the webmention. :type target: str :param me: The URL of the user. :type me: str :param code: An authorization code that grants access to the Webmention source (optional). See https://indieweb.org/Private-Webmention#Auth_Code_Generation for more information. :type code: str :param realm: A unique value for the intended recipient or audience (optional). See https://indieweb.org/Private-Webmention#Auth_Code_Generation for more information. :type realm: str :param target_webmention_endpoint: The webmention endpoint of the target URL. If this value is provided, Webmention endpoint discovery on the target will be skipped. :return: The response from the webmention endpoint. :rtype: SendWebmentionResponse Example: .. code-block:: python import indieweb_utils response = indieweb_utils.send_webmention( source="https://example.com", target="https://example.example.com/post/1", me="https://test.example" ) :raises TargetIsNotApprovedDomain: Target is not in list of approved domains. :raises GenericWebmentionError: Generic webmention error. :raises CouldNotConnectToWebmentionEndpoint: Could not connect to the receiver's webmention endpoint. """ _validate_webmention(source, target) # if domain is not approved, don't allow access if me is not None: target_domain = url_parse.urlsplit(target).scheme raw_domain = me if "/" in me.strip("/"): raw_domain = url_parse.urlsplit(me).scheme if not target_domain.endswith(raw_domain): raise TargetIsNotApprovedDomain("Target must be a {me} post.") if not target_webmention_endpoint: response = discovery.discover_webmention_endpoint(target) target_webmention_endpoint = response.endpoint request_data = { "source": source, "target": target, } if code and realm: request_data["code"] = code request_data["realm"] = realm # make post request to endpoint with source and target as values try: r = requests.post( target_webmention_endpoint, data=request_data, headers={"Content-Type": "application/x-www-form-urlencoded"}, ) except requests.exceptions.RequestException: raise CouldNotConnectToWebmentionEndpoint("Could not connect to the receiver's webmention endpoint.") message = str(r.json().get("summary", "")) valid_status_codes = (200, 201, 202) headers = [Header(name=str(k), value=str(v)) for k, v in r.headers.items()] if r.status_code not in valid_status_codes: if message == "": raise GenericWebmentionError( "Target Webmention endpoint returned a status code that was not 200, 201, or 202." ) raise GenericWebmentionError(message) return SendWebmentionResponse( title=message, description=message, url=target, status_code=r.status_code, headers=headers )