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)