from urllib.parse import urlparse
import requests
from bs4 import BeautifulSoup, Comment
from ..rsd.discover import rsd_discovery
from ..utils.urls import canonicalize_url
class TrackbackError(Exception):
"""Base class for trackback errors."""
pass
class ConnectionError(TrackbackError):
"""Raised when a connection error occurs."""
pass
class InvalidStatusCodeError(TrackbackError):
"""Raised when the server returns an invalid status code."""
pass
def rsd_trackback_discovery(url: str) -> str:
"""
Discover the trackback URL from a URL using RSD.
:param url: The URL to discover the trackback URL from.
:returns: The trackback URL.
Example:
.. code-block:: python
from indieweb_utils import rsd_trackback_discovery
rsd_trackback_discovery('http://example.com/post/123')
"""
return rsd_discovery(url, "trackback:ping")
[docs]def discover_trackback_url(url: str) -> str:
"""
Discover the trackback URL from a URL.
:param url: The URL to discover the trackback URL from.
:returns: The trackback URL.
Example:
.. code-block:: python
from indieweb_utils import discover_trackback_url
discover_trackback_url('http://example.com/post/123')
"""
get_trackback_url_request = requests.get(url)
if get_trackback_url_request.status_code != 200:
raise InvalidStatusCodeError(
"The server returned a status code of {}.".format(get_trackback_url_request.status_code)
)
soup = BeautifulSoup(get_trackback_url_request.text, "html.parser")
# get all comments
comments = soup.find_all(string=lambda text: isinstance(text, Comment))
for c in comments:
soup = BeautifulSoup(c, "lxml")
rdf = soup.find("rdf:description")
if not rdf:
continue
trackback_url = rdf.get("trackback:ping")
if not trackback_url:
raise TrackbackError("No trackback URL found in RDF.")
domain = urlparse(url).netloc
return canonicalize_url(trackback_url, domain)
trackback_url = rsd_trackback_discovery(url)
if trackback_url:
return trackback_url
return ""
[docs]def send_trackback(target_url, source_url, title: str = None, excerpt: str = None, blog_name: str = None) -> None:
"""
Send a trackback to a URL.
:param target_url: The URL to send the trackback to.
:param source_url: The URL of your post.
:param title: The title of your post.
:param excerpt: An excerpt of your post.
:param blog_name: The name of your blog.
:returns: The status code and message from the server.
:raises ConnectionError: Raised when a connection error occurs.
:raises InvalidStatusCodeError: Raised when the server returns an invalid status code.
:raises TrackbackError: Raised when the server returns an invalid response.
Example:
.. code-block:: python
from indieweb_utils import send_trackback
send_trackback(
source_url='http://example.com/post/123#trackback',
target_url='http://example.com/post/123',
title='My Post',
excerpt='This is my post',
blog_name='My Blog'
)
"""
endpoint_url = discover_trackback_url(target_url)
try:
send_trackback_request = requests.post(
endpoint_url,
data={"url": source_url, "title": title, "excerpt": excerpt, "blog_name": blog_name},
headers={
"User-Agent": "IndieWeb Utils",
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
},
)
except requests.exceptions.ConnectionError:
raise ConnectionError("Could not connect to the server.")
if send_trackback_request.status_code != 200:
raise InvalidStatusCodeError(
"The server returned a status code of {}.".format(send_trackback_request.status_code)
)
soup = BeautifulSoup(send_trackback_request.text, "lxml")
soup = soup.find("response")
if not soup:
raise TrackbackError("The server returned an invalid response.")
if soup.find("error") and soup.find("error").text != "0":
raise TrackbackError("The server returned an error: {}".format(soup.find("message").text))