tangled
alpha
login
or
join now
ligo.at
/
core
6
fork
atom
decentralized and customizable links page on top of atproto
ligo.at
atproto
link-in-bio
python
uv
6
fork
atom
overview
issues
2
pulls
pipelines
handle errors
nauta.one
5 months ago
fef68ff8
8a6f29bd
+54
-32
2 changed files
expand all
collapse all
unified
split
src
main.py
templates
profile.html
+30
-10
src/main.py
···
9
9
profiles: dict[str, tuple[str, str]] = {}
10
10
11
11
PLC_DIRECTORY = "https://plc.directory"
12
12
+
SCHEMA = "one.nauta"
12
13
13
14
14
15
@app.route("/")
···
24
25
reload = request.args.get("reload") is not None
25
26
26
27
did = resolve_did_from_handle(handle, reload=reload)
28
28
+
if did is None:
29
29
+
return "did not found", 404
27
30
pds = resolve_pds_from_did(did, reload=reload)
31
31
+
if pds is None:
32
32
+
return "pds not found", 404
28
33
profile = load_profile(pds, did, reload=reload)
29
29
-
links = load_links(pds, did, reload=reload)
34
34
+
links = load_links(pds, did, reload=reload) or []
30
35
return render_template("profile.html", profile=profile, links=links)
31
36
32
37
33
33
-
def load_links(pds: str, did: str, reload: bool = False) -> list[dict[str, str]]:
38
38
+
def load_links(pds: str, did: str, reload: bool = False) -> list[dict[str, str]] | None:
34
39
if did in links and not reload:
35
40
app.logger.debug(f"returning cached links for {did}")
36
41
return links[did]
37
42
38
38
-
response = get_record(pds, did, "one.nauta.actor.links", "self")
43
43
+
response = get_record(pds, did, f"{SCHEMA}.actor.links", "self")
44
44
+
if response is None:
45
45
+
return None
46
46
+
39
47
record = json.loads(response)
40
48
link = record["value"]["links"]
41
49
app.logger.debug(f"caching links for {did}")
···
43
51
return link
44
52
45
53
46
46
-
def load_profile(pds: str, did: str, reload: bool = False) -> tuple[str, str]:
54
54
+
def load_profile(pds: str, did: str, reload: bool = False) -> tuple[str, str] | None:
47
55
if did in profiles and not reload:
48
56
app.logger.debug(f"returning cached profile for {did}")
49
57
return profiles[did]
50
58
51
51
-
response = get_record(pds, did, "app.bsky.actor.profile", "self")
59
59
+
response = get_record(pds, did, f"{SCHEMA}.actor.profile", "self")
60
60
+
if response is None:
61
61
+
response = get_record(pds, did, "app.bsky.actor.profile", "self")
62
62
+
if response is None:
63
63
+
return None
64
64
+
52
65
record = json.loads(response)
53
66
value: dict[str, str] = record["value"]
54
67
profile = (value["displayName"], value["description"])
···
57
70
return profile
58
71
59
72
60
60
-
def resolve_pds_from_did(did: str, reload: bool = False) -> str:
73
73
+
def resolve_pds_from_did(did: str, reload: bool = False) -> str | None:
61
74
if did in pdss and not reload:
62
75
app.logger.debug(f"returning cached pds for {did}")
63
76
return pdss[did]
64
77
65
78
response = http_get(f"{PLC_DIRECTORY}/{did}")
79
79
+
if response is None:
80
80
+
return None
66
81
parsed = json.loads(response)
67
82
pds = parsed["service"][0]["serviceEndpoint"]
68
83
pdss[did] = pds
···
70
85
return pds
71
86
72
87
73
73
-
def resolve_did_from_handle(handle: str, reload: bool = False) -> str:
88
88
+
def resolve_did_from_handle(handle: str, reload: bool = False) -> str | None:
74
89
if handle in dids and not reload:
75
90
app.logger.debug(f"returning cached did for {handle}")
76
91
return dids[handle]
77
92
78
93
response = http_get(f"https://dns.google/resolve?name=_atproto.{handle}&type=TXT")
94
94
+
if response is None:
95
95
+
return None
79
96
parsed = json.loads(response)
80
97
answers = parsed["Answer"]
81
98
if len(answers) < 1:
···
89
106
return did
90
107
91
108
92
92
-
def get_record(pds: str, repo: str, collection: str, record: str) -> str:
109
109
+
def get_record(pds: str, repo: str, collection: str, record: str) -> str | None:
93
110
response = http_get(
94
111
f"{pds}/xrpc/com.atproto.repo.getRecord?repo={repo}&collection={collection}&rkey={record}"
95
112
)
96
113
return response
97
114
98
115
99
99
-
def http_get(url: str) -> str:
100
100
-
return http_request.urlopen(url).read()
116
116
+
def http_get(url: str) -> str | None:
117
117
+
try:
118
118
+
return http_request.urlopen(url).read()
119
119
+
except http_request.HTTPError:
120
120
+
return None
+24
-22
src/templates/profile.html
···
1
1
<!doctype html>
2
2
<html>
3
3
-
<head>
4
4
-
<link
5
5
-
rel="stylesheet"
6
6
-
href="{{ url_for('static', filename='style.css') }}"
7
7
-
/>
8
8
-
</head>
9
9
-
<body>
10
10
-
<div class="wrapper">
11
11
-
<header>
12
12
-
<h1>{{ profile.0 }}</h1>
13
13
-
<span class="tagline">{{ profile.1 }}</span>
14
14
-
</header>
15
15
-
<ul>
16
16
-
{% for link in links %}
17
17
-
<li style="color: {{ link.color }}">
18
18
-
<a href="{{ link.url }}">{{ link.title }}</a>
19
19
-
</li>
20
20
-
{% endfor %}
21
21
-
</ul>
22
22
-
</div>
23
23
-
<!-- .wrapper -->
24
24
-
</body>
3
3
+
<head>
4
4
+
<meta charset="utf-8" />
5
5
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
+
<base target="_blank" />
7
7
+
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" />
8
8
+
</head>
9
9
+
<body>
10
10
+
<div class="wrapper">
11
11
+
<header>
12
12
+
<h1>{{ profile.0 }}</h1>
13
13
+
{% if profile.1 %}
14
14
+
<span class="tagline">{{ profile.1 }}</span>
15
15
+
{% endif %}
16
16
+
</header>
17
17
+
<ul>
18
18
+
{% for link in links %}
19
19
+
<li style="color: {{ link.color }}">
20
20
+
<a href="{{ link.url }}">{{ link.title }}</a>
21
21
+
</li>
22
22
+
{% endfor %}
23
23
+
</ul>
24
24
+
</div>
25
25
+
<!-- .wrapper -->
26
26
+
</body>
25
27
</html>