+23
package.json
+23
package.json
···
1
+
{
2
+
"name": "atproto-goofin",
3
+
"version": "1.0.0",
4
+
"description": "",
5
+
"scripts": {
6
+
"toplikes": "node src/toplikes.ts",
7
+
"whodoilike": "node src/whodoilike.ts"
8
+
},
9
+
"keywords": [],
10
+
"author": "isabel roses <isabel@isabelroses.com>",
11
+
"license": "MIT",
12
+
"packageManager": "pnpm@10.14.0",
13
+
"dependencies": {
14
+
"@atcute/bluesky": "^3.2.0",
15
+
"@atcute/client": "^4.0.3",
16
+
"@atcute/lexicons": "^1.1.0",
17
+
"dotenv": "^17.2.1"
18
+
},
19
+
"type": "module",
20
+
"devDependencies": {
21
+
"@types/node": "^24.3.0"
22
+
}
23
+
}
+97
pnpm-lock.yaml
+97
pnpm-lock.yaml
···
1
+
lockfileVersion: '9.0'
2
+
3
+
settings:
4
+
autoInstallPeers: true
5
+
excludeLinksFromLockfile: false
6
+
7
+
importers:
8
+
9
+
.:
10
+
dependencies:
11
+
'@atcute/bluesky':
12
+
specifier: ^3.2.0
13
+
version: 3.2.0
14
+
'@atcute/client':
15
+
specifier: ^4.0.3
16
+
version: 4.0.3
17
+
'@atcute/lexicons':
18
+
specifier: ^1.1.0
19
+
version: 1.1.0
20
+
dotenv:
21
+
specifier: ^17.2.1
22
+
version: 17.2.1
23
+
devDependencies:
24
+
'@types/node':
25
+
specifier: ^24.3.0
26
+
version: 24.3.0
27
+
28
+
packages:
29
+
30
+
'@atcute/atproto@3.1.1':
31
+
resolution: {integrity: sha512-D+RLTIPF0xLu7BPZY8KSewAPemJFh+3n3zeQ3ROsLxbTtCHbrTDMAmAFexaVRAPGcPYrwXaBUlv7yZjScJolMg==}
32
+
33
+
'@atcute/bluesky@3.2.0':
34
+
resolution: {integrity: sha512-OqPLqUNjXcgQ25MaPdU7H0QcWmZrx6QQk7d5B22A5U4xy+hZJ954kQ5mSAn24Bt0DEm4j/isq1WZovr3vaPTUA==}
35
+
36
+
'@atcute/client@4.0.3':
37
+
resolution: {integrity: sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw==}
38
+
39
+
'@atcute/identity@1.0.3':
40
+
resolution: {integrity: sha512-mNMxbKHFGys03A8JXKk0KfMBzdd0vrYMzZZWjpw1nYTs0+ea6bo5S1hwqVUZxHdo1gFHSe/t63jxQIF4yL9aKw==}
41
+
42
+
'@atcute/lexicons@1.1.0':
43
+
resolution: {integrity: sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q==}
44
+
45
+
'@badrap/valita@0.4.6':
46
+
resolution: {integrity: sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==}
47
+
engines: {node: '>= 18'}
48
+
49
+
'@types/node@24.3.0':
50
+
resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==}
51
+
52
+
dotenv@17.2.1:
53
+
resolution: {integrity: sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==}
54
+
engines: {node: '>=12'}
55
+
56
+
esm-env@1.2.2:
57
+
resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
58
+
59
+
undici-types@7.10.0:
60
+
resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==}
61
+
62
+
snapshots:
63
+
64
+
'@atcute/atproto@3.1.1':
65
+
dependencies:
66
+
'@atcute/lexicons': 1.1.0
67
+
68
+
'@atcute/bluesky@3.2.0':
69
+
dependencies:
70
+
'@atcute/atproto': 3.1.1
71
+
'@atcute/lexicons': 1.1.0
72
+
73
+
'@atcute/client@4.0.3':
74
+
dependencies:
75
+
'@atcute/identity': 1.0.3
76
+
'@atcute/lexicons': 1.1.0
77
+
78
+
'@atcute/identity@1.0.3':
79
+
dependencies:
80
+
'@atcute/lexicons': 1.1.0
81
+
'@badrap/valita': 0.4.6
82
+
83
+
'@atcute/lexicons@1.1.0':
84
+
dependencies:
85
+
esm-env: 1.2.2
86
+
87
+
'@badrap/valita@0.4.6': {}
88
+
89
+
'@types/node@24.3.0':
90
+
dependencies:
91
+
undici-types: 7.10.0
92
+
93
+
dotenv@17.2.1: {}
94
+
95
+
esm-env@1.2.2: {}
96
+
97
+
undici-types@7.10.0: {}
+11
shell.nix
+11
shell.nix
+55
src/toplikes.ts
+55
src/toplikes.ts
···
1
+
import { Client, simpleFetchHandler } from '@atcute/client';
2
+
import { AppBskyFeedPost } from '@atcute/bluesky';
3
+
import { parseResourceUri } from '@atcute/lexicons';
4
+
import fs from 'fs';
5
+
import 'dotenv/config';
6
+
7
+
const client = new Client({
8
+
handler: simpleFetchHandler({ service: 'https://public.api.bsky.app' }),
9
+
});
10
+
11
+
async function getLikedPosts(cursor: string | undefined = undefined, posts: AppBskyFeedPost.Main[] = []): Promise<AppBskyFeedPost.Main[]> {
12
+
const response = await client.get('app.bsky.feed.getAuthorFeed', {
13
+
params: {
14
+
actor: process.env.DID,
15
+
filter: "posts_no_replies",
16
+
cursor
17
+
}
18
+
});
19
+
20
+
if (!response.ok) {
21
+
console.log(response)
22
+
return [];
23
+
}
24
+
25
+
for (const feedItem of response.data.feed) {
26
+
if (feedItem.post.author.did != process.env.DID) continue;
27
+
28
+
posts.push(feedItem);
29
+
}
30
+
31
+
if (response.data.cursor) {
32
+
await getLikedPosts(response.data.cursor, posts)
33
+
}
34
+
35
+
return posts;
36
+
}
37
+
38
+
async function main() {
39
+
const posts = await getLikedPosts();
40
+
41
+
posts.sort((a, b) => b.post.likeCount - a.post.likeCount)
42
+
43
+
fs.writeFileSync('dist/toplikes.md', 'Post | likes');
44
+
fs.appendFileSync('dist/toplikes.md', '\n-----|------');
45
+
46
+
for (const post of posts) {
47
+
const uri = parseResourceUri(post.post.uri);
48
+
49
+
if (!uri.ok) continue;
50
+
51
+
fs.appendFileSync('dist/toplikes.md', `\nhttps://bsky.app/profile/${uri.value.repo}/post/${uri.value.rkey} | ${post.post.likeCount}`);
52
+
}
53
+
}
54
+
55
+
main()
+65
src/whodoilike.ts
+65
src/whodoilike.ts
···
1
+
import { Client, CredentialManager } from '@atcute/client';
2
+
import { AppBskyFeedPost } from '@atcute/bluesky';
3
+
import fs from 'fs';
4
+
import 'dotenv/config';
5
+
6
+
async function getLikedPosts(cursor: string | undefined = undefined, posts: AppBskyFeedPost.Main[] = []): Promise<AppBskyFeedPost.Main[]> {
7
+
const response = await rpc.get('app.bsky.feed.getActorLikes', {
8
+
params: {
9
+
actor: process.env.DID,
10
+
cursor
11
+
}
12
+
});
13
+
14
+
if (!response.ok) {
15
+
console.log(response)
16
+
return [];
17
+
}
18
+
19
+
for (const feedItem of response.data.feed) {
20
+
// ewwww, whos liking their own posts
21
+
if (feedItem.post.author.did == process.env.DID) continue;
22
+
23
+
posts.push(feedItem);
24
+
}
25
+
26
+
if (response.data.cursor) {
27
+
return getLikedPosts(response.data.cursor, posts)
28
+
}
29
+
30
+
return posts;
31
+
}
32
+
33
+
async function main() {
34
+
const posts = await getLikedPosts();
35
+
36
+
fs.writeFileSync('dist/whodoilike.md', 'DID | handle | times likes');
37
+
fs.appendFileSync('dist/whodoilike.md', '\n----|------|-----------');
38
+
39
+
const didToLikes = new Map();
40
+
for (const post of posts) {
41
+
const { handle, did } = post.post;
42
+
43
+
if (didToLikes.has(did)) {
44
+
let didsLikes = didToLikes.get(did);
45
+
didToLikes.set(did, { handle, likes: didsLikes + 1 });
46
+
} else {
47
+
didToLikes.set(did, { handle, likes: 1 })
48
+
}
49
+
}
50
+
51
+
var descDidToLikes = new Map([...didToLikes.entries()].sort((a, b) => b[1].likes - a[1].likes));
52
+
53
+
for (const [did, likers] of descDidToLikes) {
54
+
fs.appendFileSync('dist/whodoilike.md', `\n${did} | ${likers.handle} | ${likers.likes}`);
55
+
}
56
+
}
57
+
58
+
// hoist me pls
59
+
const manager = new CredentialManager({ service: process.env.PDS });
60
+
const rpc = new Client({ handler: manager });
61
+
await manager.login({ identifier: process.env.DID, password: process.env.PASSWORD });
62
+
63
+
const mydid = "";
64
+
65
+
main()
+23
tsconfig.json
+23
tsconfig.json
···
1
+
{
2
+
"compilerOptions": {
3
+
"types": ["@atcute/bluesky", "@types/node"],
4
+
"outDir": "dist/",
5
+
"esModuleInterop": true,
6
+
"skipLibCheck": true,
7
+
"target": "ESNext",
8
+
"allowJs": true,
9
+
"resolveJsonModule": true,
10
+
"moduleDetection": "force",
11
+
"isolatedModules": true,
12
+
"verbatimModuleSyntax": true,
13
+
"strict": true,
14
+
"noImplicitOverride": true,
15
+
"noUnusedLocals": true,
16
+
"noUnusedParameters": true,
17
+
"useDefineForClassFields": false,
18
+
"noFallthroughCasesInSwitch": true,
19
+
"module": "NodeNext",
20
+
"sourceMap": true,
21
+
"declaration": true
22
+
}
23
+
}