Skip to content

Commit 861ec2f

Browse files
committed
Add Bluesky to supported socials
1 parent be99f4c commit 861ec2f

File tree

5 files changed

+71
-0
lines changed

5 files changed

+71
-0
lines changed

data/examples/europython/speakers.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"homepage": null,
1111
"gitx": "https://github.com/F3DC8A",
1212
"linkedin_url": "https://www.linkedin.com/in/F3DC8A",
13+
"bluesky_url": "https://bsky.app/profile/username.bsky.social",
1314
"mastodon_url": null,
1415
"twitter_url": null,
1516
"website_url": "https://ep2024.europython.eu/speaker/a-speaker"

data/examples/pretalx/speakers.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,21 @@
8989
"review": null,
9090
"person": "F3DC8A",
9191
"options": []
92+
},
93+
{
94+
"id": 272249,
95+
"question": {
96+
"id": 3416,
97+
"question": {
98+
"en": "Social (Bluesky)"
99+
}
100+
},
101+
"answer": "username",
102+
"answer_file": null,
103+
"submission": null,
104+
"review": null,
105+
"person": "F3DC8A",
106+
"options": []
92107
},
93108
{
94109
"id": 272249,

src/misc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class SpeakerQuestion:
77
twitter = "Social (X/Twitter)"
88
mastodon = "Social (Mastodon)"
99
linkedin = "Social (LinkedIn)"
10+
bluesky = "Social (Bluesky)"
1011
gitx = "Social (Github/Gitlab)"
1112

1213

src/models/europython.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class EuroPythonSpeaker(BaseModel):
2828
twitter_url: str | None = None
2929
mastodon_url: str | None = None
3030
linkedin_url: str | None = None
31+
bluesky_url: str | None = None
3132
gitx: str | None = None
3233

3334
@computed_field
@@ -58,6 +59,11 @@ def extract_answers(cls, values) -> dict:
5859
answer.answer_text.strip().split()[0]
5960
)
6061

62+
if answer.question_text == SpeakerQuestion.bluesky:
63+
values["bluesky_url"] = cls.extract_bluesky_url(
64+
answer.answer_text.strip().split()[0]
65+
)
66+
6167
if answer.question_text == SpeakerQuestion.linkedin:
6268
values["linkedin_url"] = cls.extract_linkedin_url(
6369
answer.answer_text.strip().split()[0]
@@ -114,6 +120,36 @@ def extract_linkedin_url(text: str) -> str:
114120

115121
return linkedin_url.split("?")[0]
116122

123+
@staticmethod
124+
def extract_bluesky_url(text: str) -> str:
125+
"""
126+
Returns a normalized BlueSky URL in the form https://bsky.app/profile/<USERNAME>.bsky.social,
127+
or uses the entire domain if it's custom (e.g., .dev).
128+
"""
129+
text = text.split("?", 1)[0].strip()
130+
131+
if text.startswith("https://"):
132+
text = text[8:]
133+
elif text.startswith("http://"):
134+
text = text[7:]
135+
136+
if text.startswith("www."):
137+
text = text[4:]
138+
139+
for marker in ("bsky.app/profile/", "bsky/"):
140+
if marker in text:
141+
text = text.split(marker, 1)[1]
142+
break
143+
# case custom domain
144+
else:
145+
text = text.rsplit("/", 1)[-1]
146+
147+
# if there's no dot, assume it's a non-custom handle and append '.bsky.social'
148+
if '.' not in text:
149+
text += ".bsky.social"
150+
151+
return f"https://bsky.app/profile/{text}"
152+
117153

118154
class EuroPythonSession(BaseModel):
119155
"""

tests/test_social_media_extractions.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,21 @@ def test_extract_mastodon_url(input_string: str, result: str) -> None:
3232
)
3333
def test_extract_linkedin_url(input_string: str, result: str) -> None:
3434
assert EuroPythonSpeaker.extract_linkedin_url(input_string) == result
35+
36+
@pytest.mark.parametrize(
37+
("input_string", "result"),
38+
[
39+
("username", "https://bsky.app/profile/username.bsky.social"),
40+
("username.dev", "https://bsky.app/profile/username.dev"),
41+
("username.bsky.social", "https://bsky.app/profile/username.bsky.social"),
42+
("bsky.app/profile/username", "https://bsky.app/profile/username.bsky.social"),
43+
("bsky/username", "https://bsky.app/profile/username.bsky.social"),
44+
("www.bsky.app/profile/username", "https://bsky.app/profile/username.bsky.social"),
45+
("www.bsky.app/profile/username.bsky.social", "https://bsky.app/profile/username.bsky.social"),
46+
("http://bsky.app/profile/username", "https://bsky.app/profile/username.bsky.social"),
47+
("https://bsky.app/profile/username.com", "https://bsky.app/profile/username.com"),
48+
("https://bsky.app/profile/username.bsky.social", "https://bsky.app/profile/username.bsky.social"),
49+
],
50+
)
51+
def test_extract_bluesky_url(input_string: str, result: str) -> None:
52+
assert EuroPythonSpeaker.extract_bluesky_url(input_string) == result

0 commit comments

Comments
 (0)