试图复刻 Bluesky 贴贴圈
1<!DOCTYPE html>
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>半个贴贴圈</title>
6 <script>
7 async function getReplyScore(handle) {
8 const ENDPOINT_URL = "https://public.api.bsky.app/xrpc/";
9
10 async function handle2DID(handle) {
11 try {
12 const response = await fetch(
13 `${ENDPOINT_URL}com.atproto.identity.resolveHandle?handle=${handle}`
14 );
15 if (!response.ok) {
16 throw new Error(`http response code: ${response.status}`);
17 }
18 const data = await response.json();
19 console.log("data:", data);
20 return data;
21 } catch (error) {
22 console.error("request fail:", error);
23 throw error;
24 }
25 }
26
27 async function getPosts(did) {
28 try {
29 const response = await fetch(
30 `${ENDPOINT_URL}app.bsky.feed.getAuthorFeed?actor=${did}&limit=100&includePins=true`
31 );
32 console.log(
33 `${ENDPOINT_URL}app.bsky.feed.getAuthorFeed?actor=${did}&limit=100&includePins=true`
34 );
35 if (!response.ok) {
36 throw new Error(`http response code: ${response.status}`);
37 }
38 const data = await response.json();
39 // console.log("data:", data);
40 return data;
41 } catch (error) {
42 console.error("request fail:", error);
43 throw error;
44 }
45 }
46
47 async function getReplies(uri) {
48 try {
49 const response = await fetch(
50 `${ENDPOINT_URL}app.bsky.feed.getPostThread?uri=${uri}&depth=1`
51 );
52 console.log(
53 `${ENDPOINT_URL}app.bsky.feed.getPostThread?uri=${uri}&depth=1`
54 );
55 if (!response.ok) {
56 throw new Error(`http response code: ${response.status}`);
57 }
58 const data = await response.json();
59 // console.log("data:", data);
60 return data;
61 } catch (error) {
62 console.error("request fail:", error);
63 throw error;
64 }
65 }
66
67 async function getHandle(did) {
68 try {
69 const response = await fetch(
70 `${ENDPOINT_URL}app.bsky.actor.getProfile?actor=${did}`
71 );
72 console.log(
73 `${ENDPOINT_URL}app.bsky.actor.getProfile?actor=${did}`
74 );
75 if (!response.ok) {
76 throw new Error(`http response code: ${response.status}`);
77 }
78 const data = await response.json();
79 // console.log("data:", data);
80 return data;
81 } catch (error) {
82 console.error("request fail:", error);
83 throw error;
84 }
85 }
86
87 function getDidByAtUrl(aturl) {
88 return aturl.split("/")[2];
89 }
90
91 var result = {};
92
93 const did_result = await handle2DID(handle);
94 var did = did_result.did;
95
96 const posts_result = await getPosts(did);
97 for (const item of posts_result.feed) {
98 // will recognize posts with no reason, whose replies will + 1.5
99 // posts has reply.parent, parent + 1
100 if ("reason" in item) {
101 console.warn(
102 `${item.post.uri} has reason ${item.reason.$type}, ignored`
103 );
104 } else {
105 if ("reply" in item) {
106 console.log(`${item.post.uri} replies ${item.reply.parent.uri}`);
107 did_of_the_guy = getDidByAtUrl(item.reply.parent.uri);
108 if (!(did_of_the_guy in result)) {
109 result[did_of_the_guy] = 0.0;
110 }
111 result[did_of_the_guy] += 1.0;
112 } else {
113 main_uri = item.post.uri;
114 reply_result = await getReplies(main_uri);
115 if ("thread" in reply_result) {
116 for (const replypost of reply_result.thread.replies) {
117 reply_post_uri = replypost.post.uri;
118 did_of_the_guy = getDidByAtUrl(reply_post_uri);
119 if (!(did_of_the_guy in result)) {
120 result[did_of_the_guy] = 0.0;
121 }
122 result[did_of_the_guy] += 1.5;
123 }
124 } else {
125 console.warn(`${item.post.uri} has no replies`);
126 }
127 }
128 }
129 }
130
131 result_html = '<div class="result">\n';
132
133 for (const final_did in result) {
134 reply_count = result[final_did];
135 handle_result = await getHandle(final_did);
136 handle = handle_result.handle;
137 avatar = handle_result.avatar;
138 // console.log(`${handle}'s reply_count is ${reply_count}, avatar ${handle_result.avatar}`);
139 result_html += `<div did="${final_did}">\n`;
140 result_html += `<img src="${avatar}">\n`;
141 result_html += `<a class="handle">${handle}</a>\n`;
142 result_html += `<span class="replycount">${reply_count}</span>\n`;
143 result_html += "</div>\n";
144 // console.log(result_html);
145 }
146
147 result_html += "</div>";
148
149 return result_html;
150 }
151
152 // 使用示例
153 async function main() {
154 try {
155 const did = await getReplyScore(document.getElementById("bruh").value);
156 console.log("result:", did);
157 document.getElementById("result").innerHTML = did
158 } catch (error) {
159 console.error("fail:", error);
160 }
161 }
162
163 main();
164 </script>
165 </head>
166 <body>
167 <input type="text" id="bruh" value="输入 handle" />
168 <button onclick="main()">启动</button>
169 <div id="result"></div>
170 </body>
171</html>