Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions linkedin_mcp_server/scraping/link_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ class RawReference(TypedDict, total=False):
"feed": 50,
}

# A label must carry at least one letter or digit in any script, so the class is
# Unicode-aware rather than ``[A-Za-z0-9]`` — otherwise every reference on a
# Cyrillic, CJK or Arabic-script profile is dropped. The four Hangul fillers are
# excluded because they carry the word property while rendering as nothing: a
# label made only of them would pass as valid and yield an invisible reference
# text, shadowing the aria-label fallback. They are the only invisible code
# points in ``[^\W_]`` (verified by scanning the full Unicode range).
_LABEL_CONTENT_RE = re.compile("[^\\W_\u115f\u1160\u3164\uffa0]")

_URL_LIKE_RE = re.compile(r"^(?:https?://|/)\S+$", re.IGNORECASE)
_DUPLICATE_HALVES_RE = re.compile(r"^(?P<value>.+?)\s+(?P=value)$")
_WHITESPACE_RE = re.compile(r"\s+")
Expand Down Expand Up @@ -322,7 +331,12 @@ def choose_reference_text(


def clean_label(value: str, kind: ReferenceKind) -> str | None:
"""Normalize and compact a candidate label."""
"""Normalize and compact a candidate label.

Rejects the label when nothing visible survives normalization, so
non-Latin names are kept while punctuation-only strings are dropped.
See ``_LABEL_CONTENT_RE``.
"""
value = _WHITESPACE_RE.sub(" ", value).strip()
if not value:
return None
Expand Down Expand Up @@ -358,7 +372,7 @@ def clean_label(value: str, kind: ReferenceKind) -> str | None:
return None
if len(value) > 80:
return None
if not re.search(r"[A-Za-z0-9]", value):
if not _LABEL_CONTENT_RE.search(value):
return None

return value
Expand Down
71 changes: 71 additions & 0 deletions tests/test_link_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,77 @@ def test_rejects_single_character_labels(self):
}
]

def test_keeps_cyrillic_only_names(self):
"""Regression: a non-Latin (Cyrillic) name must survive label
cleaning — the alphanumeric guard uses a Unicode word check, not
[A-Za-z0-9], so search results from RU/BY/other locales keep
their person references instead of being dropped."""
references = build_references(
[
{
"href": "https://www.linkedin.com/in/margo-yunanova/",
"text": "Маргарита Юнанова",
}
],
"search_results",
)

assert references == [
{
"kind": "person",
"url": "/in/margo-yunanova/",
"text": "Маргарита Юнанова",
"context": "search result",
}
]

def test_rejects_punctuation_only_labels_across_scripts(self):
"""The Unicode word guard must still reject pure punctuation and
symbols (no letter or digit in any script)."""
references = build_references(
[
{
"href": "https://www.linkedin.com/in/williamhgates/",
"text": "—·—",
"aria_label": "Bill Gates",
}
],
"main_profile",
)

assert references == [
{
"kind": "person",
"url": "/in/williamhgates/",
"text": "Bill Gates",
"context": "top card",
}
]

def test_rejects_invisible_hangul_filler_labels(self):
"""Hangul fillers carry the Unicode word property but render as
nothing, so a label made only of them must not shadow the
aria-label fallback with invisible text."""
references = build_references(
[
{
"href": "https://www.linkedin.com/in/williamhgates/",
"text": "\u115f\u1160\u3164\uffa0",
"aria_label": "Bill Gates",
}
],
"main_profile",
)

assert references == [
{
"kind": "person",
"url": "/in/williamhgates/",
"text": "Bill Gates",
"context": "top card",
}
]

def test_preserves_words_starting_with_view(self):
references = build_references(
[
Expand Down
Loading