import re
import bs4
import pydantic
from ows_text_campaigns.campaigns.exceptions import InvalidShortLinkTagError
from ows_text_campaigns.campaigns.types import (
ShortenedUrl,
)
URL_REGEX = re.compile(r"""(?i)\b((?:https?://|www\.)[^\s<>'"()]+)""")
def extract_shortened_urls_from_markup(
markup: str, *, raise_on_error: bool = False
) -> list[ShortenedUrl]:
result: list[ShortenedUrl] = []
soup = bs4.BeautifulSoup(markup, "html.parser")
for tag in soup.select("a[data-short-link-id]"):
try:
result.append(
ShortenedUrl.model_validate(
{
"id": tag.get("data-short-link-id"),
"url": tag.get("data-short-link-url"),
"method": tag.get("data-short-link-method"),
"path": tag.get("data-short-link-path"),
"domain": tag.get("data-short-link-domain"),
},
)
)
except pydantic.ValidationError as exc:
if raise_on_error:
attr_names = ", ".join(
["".join(map(str, error["loc"])) for error in exc.errors()]
)
raise InvalidShortLinkTagError(
f"Missing or invalid `data-short-link-*` attribute(s) in short link tag: {attr_names}"
) from exc
continue
return result
def extract_non_shortened_urls_from_content(content: str) -> list[str]:
soup = bs4.BeautifulSoup(content, "html.parser")
for tag in soup.select("a[data-short-link-id]"):
tag.decompose()
return list(set(URL_REGEX.findall(str(soup))))
def create_short_link_tag(shortened_url: ShortenedUrl) -> str:
return (
f''
f"{shortened_url.preview_url}"
)
def replace_short_link_tags(markup: str, *, shortened_urls: dict[str, str]) -> str:
soup = bs4.BeautifulSoup(markup, "html.parser")
for tag in soup.select("a[data-short-link-id]"):
shortened_url_id = tag.get("data-short-link-id")
if not isinstance(shortened_url_id, str):
continue
shortened_url = shortened_urls.get(shortened_url_id)
if not shortened_url:
continue
# Create a new tag
new_tag = soup.new_tag("a", href=shortened_url)
new_tag.string = shortened_url
tag.replace_with(new_tag)
return str(soup)
def replace_standard_links_tags(markup: str) -> str:
soup = bs4.BeautifulSoup(markup, "html.parser")
for tag in soup.select('a[data-short-link-method="STANDARD"]'):
shortened_url = tag.get("data-short-link-url")
if not isinstance(shortened_url, str):
continue
# Create a new tag
new_tag = soup.new_tag("a", href=shortened_url)
new_tag.string = shortened_url
tag.replace_with(new_tag)
return str(soup)