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