+78
-16
like_stats_page.js
+78
-16
like_stats_page.js
···
10
10
this.submitButton = $(this.pageElement.querySelector('input[type="submit"]'), HTMLInputElement);
11
11
this.progressBar = $(this.pageElement.querySelector('input[type=submit] + progress'), HTMLProgressElement);
12
12
13
-
this.receivedTable = $(this.pageElement.querySelector('.received-likes'));
14
-
this.givenTable = $(this.pageElement.querySelector('.given-likes'));
13
+
this.receivedTable = $(this.pageElement.querySelector('.received-likes'), HTMLTableElement);
14
+
this.givenTable = $(this.pageElement.querySelector('.given-likes'), HTMLTableElement);
15
15
16
16
this.appView = new BlueskyAPI('public.api.bsky.app', false);
17
17
···
69
69
let fetchGivenLikes = this.fetchGivenLikes(requestedDays);
70
70
71
71
let receivedLikes = await this.fetchReceivedLikes(requestedDays);
72
-
let received = countElementsBy(receivedLikes, (x) => x.actor.handle);
72
+
let receivedStats = this.sumUpReceivedLikes(receivedLikes);
73
+
let topReceived = this.getTopEntries(receivedStats);
73
74
74
-
await this.renderResults(received, this.receivedTable);
75
+
await this.renderResults(topReceived, this.receivedTable);
75
76
76
77
let givenLikes = await fetchGivenLikes;
77
-
let given = countElementsBy(givenLikes, (x) => atURI(x.value.subject.uri).repo);
78
+
let givenStats = this.sumUpGivenLikes(givenLikes);
79
+
let topGiven = this.getTopEntries(givenStats);
78
80
79
-
await this.renderResults(given, this.givenTable);
81
+
let profileInfo = await appView.getRequest('app.bsky.actor.getProfiles', { actors: topGiven.map(x => x.did) });
82
+
83
+
for (let profile of profileInfo.profiles) {
84
+
let user = /** @type {LikeStat} */ (topGiven.find(x => x.did == profile.did));
85
+
user.handle = profile.handle;
86
+
user.avatar = profile.avatar;
87
+
}
88
+
89
+
await this.renderResults(topGiven, this.givenTable);
80
90
81
91
this.receivedTable.style.display = 'table';
82
92
this.givenTable.style.display = 'table';
···
151
161
return results.flat();
152
162
}
153
163
154
-
async renderResults(counts, table) {
155
-
let tableBody = $(table.querySelector('tbody'));
156
-
tableBody.innerHTML = '';
164
+
/**
165
+
* @typedef {{ handle?: string, did?: string, avatar?: string, count: number }} LikeStat
166
+
* @typedef {Record<string, LikeStat>} LikeStatHash
167
+
*/
168
+
169
+
/** @param {json[]} likes, @returns {LikeStatHash} */
170
+
171
+
sumUpReceivedLikes(likes) {
172
+
/** @type {LikeStatHash} */
173
+
let stats = {};
174
+
175
+
for (let like of likes) {
176
+
let handle = like.actor.handle;
157
177
158
-
let entries = Object.entries(counts).sort(this.sortResults).slice(0, 20);
178
+
if (!stats[handle]) {
179
+
stats[handle] = { handle: handle, count: 0, avatar: like.actor.avatar };
180
+
}
159
181
160
-
for (let [user, count] of entries) {
161
-
let handle = user.startsWith('did:') ? await accountAPI.fetchHandleForDid(user) : user;
182
+
stats[handle].count += 1;
183
+
}
162
184
185
+
return stats;
186
+
}
187
+
188
+
/** @param {json[]} likes, @returns {LikeStatHash} */
189
+
190
+
sumUpGivenLikes(likes) {
191
+
/** @type {LikeStatHash} */
192
+
let stats = {};
193
+
194
+
for (let like of likes) {
195
+
let did = atURI(like.value.subject.uri).repo;
196
+
197
+
if (!stats[did]) {
198
+
stats[did] = { did: did, count: 0 };
199
+
}
200
+
201
+
stats[did].count += 1;
202
+
}
203
+
204
+
return stats;
205
+
}
206
+
207
+
/** @param {LikeStatHash} counts, @returns {LikeStat[]} */
208
+
209
+
getTopEntries(counts) {
210
+
return Object.entries(counts).sort(this.sortResults).map(x => x[1]).slice(0, 20);
211
+
}
212
+
213
+
/** @param {LikeStat[]} topEntries, @param {HTMLTableElement} table, @returns {Promise<void>} */
214
+
215
+
async renderResults(topEntries, table) {
216
+
let tableBody = $(table.querySelector('tbody'));
217
+
tableBody.innerHTML = '';
218
+
219
+
for (let user of topEntries) {
163
220
let tr = $tag('tr');
164
221
tr.append(
165
-
$tag('td', { html: `<a href="https://bsky.app/profile/${handle}" target="_blank">${handle}</a>` }),
166
-
$tag('td', { text: count })
222
+
$tag('td', {
223
+
html: `<img class="avatar" src="${user.avatar}"> ` +
224
+
`<a href="https://bsky.app/profile/${user.handle}" target="_blank">${user.handle}</a>`
225
+
}),
226
+
$tag('td', { text: user.count })
167
227
);
168
228
169
229
tableBody.append(tr);
···
201
261
this.progressBar.value = totalProgress;
202
262
}
203
263
264
+
/** @param {[string, LikeStat]} a, @param {[string, LikeStat]} b, @returns {-1|1|0} */
265
+
204
266
sortResults(a, b) {
205
-
if (a[1] < b[1]) {
267
+
if (a[1].count < b[1].count) {
206
268
return 1;
207
-
} else if (a[1] > b[1]) {
269
+
} else if (a[1].count > b[1].count) {
208
270
return -1;
209
271
} else {
210
272
return 0;
+8
style.css
+8
style.css
···
866
866
padding: 7px 10px;
867
867
}
868
868
869
+
#like_stats_page .scan-result .avatar {
870
+
width: 24px;
871
+
border-radius: 14px;
872
+
vertical-align: middle;
873
+
margin-right: 2px;
874
+
padding: 2px;
875
+
}
876
+
869
877
@media (prefers-color-scheme: dark) {
870
878
body {
871
879
background-color: rgb(39, 39, 37);
-20
utils.js
-20
utils.js
···
150
150
url.searchParams.set('post', postId);
151
151
return url.toString();
152
152
}
153
-
154
-
/**
155
-
* @template T
156
-
* @param {T[]} list
157
-
* @param {(T) => string} prop
158
-
* @returns {Record<string, number>}
159
-
*/
160
-
161
-
function countElementsBy(list, prop) {
162
-
/** @type {Record<string, number>} */
163
-
let counts = {};
164
-
165
-
for (let obj of list) {
166
-
let value = prop(obj);
167
-
counts[value] = counts[value] || 0;
168
-
counts[value] += 1;
169
-
}
170
-
171
-
return counts;
172
-
}