+65
-2
flake.nix
+65
-2
flake.nix
···
12
12
nixpkgsFor = forAllSystems (system:
13
13
import nixpkgs {
14
14
inherit system;
15
-
overlays = [self.overlay.default];
15
+
overlays = [self.overlays.default];
16
16
});
17
17
in {
18
-
overlay.default = final: prev: let
18
+
overlays.default = final: prev: let
19
19
pname = "plonk";
20
20
version = "0.1.0";
21
21
in {
···
23
23
buildNpmPackage {
24
24
inherit pname version;
25
25
src = ./.;
26
+
nativeBuildInputs = [makeBinaryWrapper];
26
27
packageJson = ./package.json;
27
28
buildPhase = "npm run build";
29
+
installPhase = ''
30
+
runHook preInstall
31
+
32
+
mkdir -p $out/bin
33
+
cp -r ./* $out/
34
+
35
+
makeBinaryWrapper ${nodejs}/bin/node $out/bin/$pname \
36
+
--prefix PATH : ${lib.makeBinPath [nodejs]} \
37
+
--add-flags "$out/dist/index.js"
38
+
'';
39
+
28
40
npmDepsHash = "sha256-qGCbaFAHd/s9hOTWMjHCam6Kf6pU6IWPybfwYh0sOwc=";
29
41
};
30
42
};
···
48
60
});
49
61
50
62
formatter = forAllSystems (system: nixpkgsFor."${system}".alejandra);
63
+
64
+
nixosModules.default = {
65
+
config,
66
+
pkgs,
67
+
lib,
68
+
...
69
+
}:
70
+
with lib; {
71
+
options = {
72
+
services.plonk = {
73
+
enable = mkOption {
74
+
type = types.bool;
75
+
default = false;
76
+
description = "Enable plonk";
77
+
};
78
+
port = mkOption {
79
+
type = types.int;
80
+
default = 3000;
81
+
description = "Port to run plonk on";
82
+
};
83
+
cookie_secret = mkOption {
84
+
type = types.str;
85
+
default = "00000000000000000000000000000000";
86
+
description = "Cookie secret";
87
+
};
88
+
};
89
+
};
90
+
91
+
config = mkIf config.services.plonk.enable {
92
+
nixpkgs.overlays = [self.overlays.default];
93
+
systemd.services.plonk = {
94
+
description = "plonk service";
95
+
wantedBy = ["multi-user.target"];
96
+
97
+
serviceConfig = {
98
+
ListenStream = "0.0.0.0:${toString config.services.plonk.port}";
99
+
ExecStart = "${pkgs.plonk}/bin/plonk";
100
+
Restart = "always";
101
+
};
102
+
103
+
environment = {
104
+
PLONK_PORT = "${toString config.services.plonk.port}";
105
+
PLONK_NODE_ENV = "production";
106
+
PLONK_HOST = "localhost";
107
+
PLONK_PUBLIC_URL = "https://plonk.li";
108
+
PLONK_DB_PATH = "plonk.db";
109
+
PLONK_COOKIE_SECRET = config.services.plonk.cookie_secret;
110
+
};
111
+
};
112
+
};
113
+
};
51
114
};
52
115
}
+35
lexicons/comment.json
+35
lexicons/comment.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "li.plonk.comment",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"key": "tid",
8
+
"record": {
9
+
"type": "object",
10
+
"required": [
11
+
"content",
12
+
"createdAt",
13
+
"post"
14
+
],
15
+
"properties": {
16
+
"content": {
17
+
"type": "string",
18
+
"maxLength": 100000,
19
+
"maxGraphemes": 10000,
20
+
"description": "comment body"
21
+
},
22
+
"createdAt": {
23
+
"type": "string",
24
+
"format": "datetime",
25
+
"description": "comment creation timestamp"
26
+
},
27
+
"post": {
28
+
"type": "ref",
29
+
"ref": "com.atproto.repo.strongRef"
30
+
}
31
+
}
32
+
}
33
+
}
34
+
}
35
+
}
+67
lexicons/getRecord.json
+67
lexicons/getRecord.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "com.atproto.repo.getRecord",
4
+
"defs": {
5
+
"main": {
6
+
"type": "query",
7
+
"description": "Get a single record from a repository. Does not require auth.",
8
+
"parameters": {
9
+
"type": "params",
10
+
"required": [
11
+
"repo",
12
+
"collection",
13
+
"rkey"
14
+
],
15
+
"properties": {
16
+
"repo": {
17
+
"type": "string",
18
+
"format": "at-identifier",
19
+
"description": "The handle or DID of the repo."
20
+
},
21
+
"collection": {
22
+
"type": "string",
23
+
"format": "nsid",
24
+
"description": "The NSID of the record collection."
25
+
},
26
+
"rkey": {
27
+
"type": "string",
28
+
"description": "The Record Key."
29
+
},
30
+
"cid": {
31
+
"type": "string",
32
+
"format": "cid",
33
+
"description": "The CID of the version of the record. If not specified, then return the most recent version."
34
+
}
35
+
}
36
+
},
37
+
"output": {
38
+
"encoding": "application/json",
39
+
"schema": {
40
+
"type": "object",
41
+
"required": [
42
+
"uri",
43
+
"value"
44
+
],
45
+
"properties": {
46
+
"uri": {
47
+
"type": "string",
48
+
"format": "at-uri"
49
+
},
50
+
"cid": {
51
+
"type": "string",
52
+
"format": "cid"
53
+
},
54
+
"value": {
55
+
"type": "unknown"
56
+
}
57
+
}
58
+
}
59
+
},
60
+
"errors": [
61
+
{
62
+
"name": "RecordNotFound"
63
+
}
64
+
]
65
+
}
66
+
}
67
+
}
+93
lexicons/listRecords.json
+93
lexicons/listRecords.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "com.atproto.repo.listRecords",
4
+
"defs": {
5
+
"main": {
6
+
"type": "query",
7
+
"description": "List a range of records in a repository, matching a specific collection. Does not require auth.",
8
+
"parameters": {
9
+
"type": "params",
10
+
"required": [
11
+
"repo",
12
+
"collection"
13
+
],
14
+
"properties": {
15
+
"repo": {
16
+
"type": "string",
17
+
"format": "at-identifier",
18
+
"description": "The handle or DID of the repo."
19
+
},
20
+
"collection": {
21
+
"type": "string",
22
+
"format": "nsid",
23
+
"description": "The NSID of the record type."
24
+
},
25
+
"limit": {
26
+
"type": "integer",
27
+
"minimum": 1,
28
+
"maximum": 100,
29
+
"default": 50,
30
+
"description": "The number of records to return."
31
+
},
32
+
"cursor": {
33
+
"type": "string"
34
+
},
35
+
"rkeyStart": {
36
+
"type": "string",
37
+
"description": "DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)"
38
+
},
39
+
"rkeyEnd": {
40
+
"type": "string",
41
+
"description": "DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)"
42
+
},
43
+
"reverse": {
44
+
"type": "boolean",
45
+
"description": "Flag to reverse the order of the returned records."
46
+
}
47
+
}
48
+
},
49
+
"output": {
50
+
"encoding": "application/json",
51
+
"schema": {
52
+
"type": "object",
53
+
"required": [
54
+
"records"
55
+
],
56
+
"properties": {
57
+
"cursor": {
58
+
"type": "string"
59
+
},
60
+
"records": {
61
+
"type": "array",
62
+
"items": {
63
+
"type": "ref",
64
+
"ref": "#record"
65
+
}
66
+
}
67
+
}
68
+
}
69
+
}
70
+
},
71
+
"record": {
72
+
"type": "object",
73
+
"required": [
74
+
"uri",
75
+
"cid",
76
+
"value"
77
+
],
78
+
"properties": {
79
+
"uri": {
80
+
"type": "string",
81
+
"format": "at-uri"
82
+
},
83
+
"cid": {
84
+
"type": "string",
85
+
"format": "cid"
86
+
},
87
+
"value": {
88
+
"type": "unknown"
89
+
}
90
+
}
91
+
}
92
+
}
93
+
}
+1
-1
lexicons/paste.json
+1
-1
lexicons/paste.json
+523
package-lock.json
+523
package-lock.json
···
26
26
"multiformats": "^9.9.0",
27
27
"pino": "^9.3.2",
28
28
"pug": "^3.0.3",
29
+
"shiki": "^3.4.2",
29
30
"uhtml": "^4.5.9"
30
31
},
31
32
"devDependencies": {
···
1484
1485
"win32"
1485
1486
]
1486
1487
},
1488
+
"node_modules/@shikijs/core": {
1489
+
"version": "3.4.2",
1490
+
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.4.2.tgz",
1491
+
"integrity": "sha512-AG8vnSi1W2pbgR2B911EfGqtLE9c4hQBYkv/x7Z+Kt0VxhgQKcW7UNDVYsu9YxwV6u+OJrvdJrMq6DNWoBjihQ==",
1492
+
"license": "MIT",
1493
+
"dependencies": {
1494
+
"@shikijs/types": "3.4.2",
1495
+
"@shikijs/vscode-textmate": "^10.0.2",
1496
+
"@types/hast": "^3.0.4",
1497
+
"hast-util-to-html": "^9.0.5"
1498
+
}
1499
+
},
1500
+
"node_modules/@shikijs/engine-javascript": {
1501
+
"version": "3.4.2",
1502
+
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.4.2.tgz",
1503
+
"integrity": "sha512-1/adJbSMBOkpScCE/SB6XkjJU17ANln3Wky7lOmrnpl+zBdQ1qXUJg2GXTYVHRq+2j3hd1DesmElTXYDgtfSOQ==",
1504
+
"license": "MIT",
1505
+
"dependencies": {
1506
+
"@shikijs/types": "3.4.2",
1507
+
"@shikijs/vscode-textmate": "^10.0.2",
1508
+
"oniguruma-to-es": "^4.3.3"
1509
+
}
1510
+
},
1511
+
"node_modules/@shikijs/engine-oniguruma": {
1512
+
"version": "3.4.2",
1513
+
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.4.2.tgz",
1514
+
"integrity": "sha512-zcZKMnNndgRa3ORja6Iemsr3DrLtkX3cAF7lTJkdMB6v9alhlBsX9uNiCpqofNrXOvpA3h6lHcLJxgCIhVOU5Q==",
1515
+
"license": "MIT",
1516
+
"dependencies": {
1517
+
"@shikijs/types": "3.4.2",
1518
+
"@shikijs/vscode-textmate": "^10.0.2"
1519
+
}
1520
+
},
1521
+
"node_modules/@shikijs/langs": {
1522
+
"version": "3.4.2",
1523
+
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.4.2.tgz",
1524
+
"integrity": "sha512-H6azIAM+OXD98yztIfs/KH5H4PU39t+SREhmM8LaNXyUrqj2mx+zVkr8MWYqjceSjDw9I1jawm1WdFqU806rMA==",
1525
+
"license": "MIT",
1526
+
"dependencies": {
1527
+
"@shikijs/types": "3.4.2"
1528
+
}
1529
+
},
1530
+
"node_modules/@shikijs/themes": {
1531
+
"version": "3.4.2",
1532
+
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.4.2.tgz",
1533
+
"integrity": "sha512-qAEuAQh+brd8Jyej2UDDf+b4V2g1Rm8aBIdvt32XhDPrHvDkEnpb7Kzc9hSuHUxz0Iuflmq7elaDuQAP9bHIhg==",
1534
+
"license": "MIT",
1535
+
"dependencies": {
1536
+
"@shikijs/types": "3.4.2"
1537
+
}
1538
+
},
1539
+
"node_modules/@shikijs/types": {
1540
+
"version": "3.4.2",
1541
+
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.4.2.tgz",
1542
+
"integrity": "sha512-zHC1l7L+eQlDXLnxvM9R91Efh2V4+rN3oMVS2swCBssbj2U/FBwybD1eeLaq8yl/iwT+zih8iUbTBCgGZOYlVg==",
1543
+
"license": "MIT",
1544
+
"dependencies": {
1545
+
"@shikijs/vscode-textmate": "^10.0.2",
1546
+
"@types/hast": "^3.0.4"
1547
+
}
1548
+
},
1549
+
"node_modules/@shikijs/vscode-textmate": {
1550
+
"version": "10.0.2",
1551
+
"resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
1552
+
"integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
1553
+
"license": "MIT"
1554
+
},
1487
1555
"node_modules/@ts-morph/common": {
1488
1556
"version": "0.17.0",
1489
1557
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.17.0.tgz",
···
1602
1670
"@types/send": "*"
1603
1671
}
1604
1672
},
1673
+
"node_modules/@types/hast": {
1674
+
"version": "3.0.4",
1675
+
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
1676
+
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
1677
+
"license": "MIT",
1678
+
"dependencies": {
1679
+
"@types/unist": "*"
1680
+
}
1681
+
},
1605
1682
"node_modules/@types/http-errors": {
1606
1683
"version": "2.0.4",
1607
1684
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
1608
1685
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
1609
1686
"dev": true,
1610
1687
"license": "MIT"
1688
+
},
1689
+
"node_modules/@types/mdast": {
1690
+
"version": "4.0.4",
1691
+
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
1692
+
"integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
1693
+
"license": "MIT",
1694
+
"dependencies": {
1695
+
"@types/unist": "*"
1696
+
}
1611
1697
},
1612
1698
"node_modules/@types/mime": {
1613
1699
"version": "1.3.5",
···
1662
1748
"@types/node": "*",
1663
1749
"@types/send": "*"
1664
1750
}
1751
+
},
1752
+
"node_modules/@types/unist": {
1753
+
"version": "3.0.3",
1754
+
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
1755
+
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
1756
+
"license": "MIT"
1757
+
},
1758
+
"node_modules/@ungap/structured-clone": {
1759
+
"version": "1.3.0",
1760
+
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
1761
+
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
1762
+
"license": "ISC"
1665
1763
},
1666
1764
"node_modules/@webreflection/signal": {
1667
1765
"version": "2.1.2",
···
2095
2193
"cborg": "cli.js"
2096
2194
}
2097
2195
},
2196
+
"node_modules/ccount": {
2197
+
"version": "2.0.1",
2198
+
"resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
2199
+
"integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
2200
+
"license": "MIT",
2201
+
"funding": {
2202
+
"type": "github",
2203
+
"url": "https://github.com/sponsors/wooorm"
2204
+
}
2205
+
},
2098
2206
"node_modules/chalk": {
2099
2207
"version": "4.1.2",
2100
2208
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
···
2110
2218
},
2111
2219
"funding": {
2112
2220
"url": "https://github.com/chalk/chalk?sponsor=1"
2221
+
}
2222
+
},
2223
+
"node_modules/character-entities-html4": {
2224
+
"version": "2.1.0",
2225
+
"resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
2226
+
"integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
2227
+
"license": "MIT",
2228
+
"funding": {
2229
+
"type": "github",
2230
+
"url": "https://github.com/sponsors/wooorm"
2231
+
}
2232
+
},
2233
+
"node_modules/character-entities-legacy": {
2234
+
"version": "3.0.0",
2235
+
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
2236
+
"integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
2237
+
"license": "MIT",
2238
+
"funding": {
2239
+
"type": "github",
2240
+
"url": "https://github.com/sponsors/wooorm"
2113
2241
}
2114
2242
},
2115
2243
"node_modules/character-parser": {
···
2189
2317
"node": ">= 0.8"
2190
2318
}
2191
2319
},
2320
+
"node_modules/comma-separated-tokens": {
2321
+
"version": "2.0.3",
2322
+
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
2323
+
"integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
2324
+
"license": "MIT",
2325
+
"funding": {
2326
+
"type": "github",
2327
+
"url": "https://github.com/sponsors/wooorm"
2328
+
}
2329
+
},
2192
2330
"node_modules/commander": {
2193
2331
"version": "9.5.0",
2194
2332
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
···
2361
2499
"node": ">= 0.8"
2362
2500
}
2363
2501
},
2502
+
"node_modules/dequal": {
2503
+
"version": "2.0.3",
2504
+
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
2505
+
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
2506
+
"license": "MIT",
2507
+
"engines": {
2508
+
"node": ">=6"
2509
+
}
2510
+
},
2364
2511
"node_modules/destroy": {
2365
2512
"version": "1.2.0",
2366
2513
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
···
2378
2525
"license": "Apache-2.0",
2379
2526
"engines": {
2380
2527
"node": ">=8"
2528
+
}
2529
+
},
2530
+
"node_modules/devlop": {
2531
+
"version": "1.1.0",
2532
+
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
2533
+
"integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
2534
+
"license": "MIT",
2535
+
"dependencies": {
2536
+
"dequal": "^2.0.0"
2537
+
},
2538
+
"funding": {
2539
+
"type": "github",
2540
+
"url": "https://github.com/sponsors/wooorm"
2381
2541
}
2382
2542
},
2383
2543
"node_modules/diff": {
···
3043
3203
"node": ">= 0.4"
3044
3204
}
3045
3205
},
3206
+
"node_modules/hast-util-to-html": {
3207
+
"version": "9.0.5",
3208
+
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
3209
+
"integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
3210
+
"license": "MIT",
3211
+
"dependencies": {
3212
+
"@types/hast": "^3.0.0",
3213
+
"@types/unist": "^3.0.0",
3214
+
"ccount": "^2.0.0",
3215
+
"comma-separated-tokens": "^2.0.0",
3216
+
"hast-util-whitespace": "^3.0.0",
3217
+
"html-void-elements": "^3.0.0",
3218
+
"mdast-util-to-hast": "^13.0.0",
3219
+
"property-information": "^7.0.0",
3220
+
"space-separated-tokens": "^2.0.0",
3221
+
"stringify-entities": "^4.0.0",
3222
+
"zwitch": "^2.0.4"
3223
+
},
3224
+
"funding": {
3225
+
"type": "opencollective",
3226
+
"url": "https://opencollective.com/unified"
3227
+
}
3228
+
},
3229
+
"node_modules/hast-util-whitespace": {
3230
+
"version": "3.0.0",
3231
+
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
3232
+
"integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
3233
+
"license": "MIT",
3234
+
"dependencies": {
3235
+
"@types/hast": "^3.0.0"
3236
+
},
3237
+
"funding": {
3238
+
"type": "opencollective",
3239
+
"url": "https://opencollective.com/unified"
3240
+
}
3241
+
},
3046
3242
"node_modules/help-me": {
3047
3243
"version": "5.0.0",
3048
3244
"resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
···
3056
3252
"integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==",
3057
3253
"license": "MIT"
3058
3254
},
3255
+
"node_modules/html-void-elements": {
3256
+
"version": "3.0.0",
3257
+
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
3258
+
"integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
3259
+
"license": "MIT",
3260
+
"funding": {
3261
+
"type": "github",
3262
+
"url": "https://github.com/sponsors/wooorm"
3263
+
}
3264
+
},
3059
3265
"node_modules/htmlparser2": {
3060
3266
"version": "9.1.0",
3061
3267
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
···
3404
3610
"dev": true,
3405
3611
"license": "ISC"
3406
3612
},
3613
+
"node_modules/mdast-util-to-hast": {
3614
+
"version": "13.2.0",
3615
+
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
3616
+
"integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
3617
+
"license": "MIT",
3618
+
"dependencies": {
3619
+
"@types/hast": "^3.0.0",
3620
+
"@types/mdast": "^4.0.0",
3621
+
"@ungap/structured-clone": "^1.0.0",
3622
+
"devlop": "^1.0.0",
3623
+
"micromark-util-sanitize-uri": "^2.0.0",
3624
+
"trim-lines": "^3.0.0",
3625
+
"unist-util-position": "^5.0.0",
3626
+
"unist-util-visit": "^5.0.0",
3627
+
"vfile": "^6.0.0"
3628
+
},
3629
+
"funding": {
3630
+
"type": "opencollective",
3631
+
"url": "https://opencollective.com/unified"
3632
+
}
3633
+
},
3407
3634
"node_modules/media-typer": {
3408
3635
"version": "0.3.0",
3409
3636
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
···
3441
3668
"node": ">= 0.6"
3442
3669
}
3443
3670
},
3671
+
"node_modules/micromark-util-character": {
3672
+
"version": "2.1.1",
3673
+
"resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
3674
+
"integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
3675
+
"funding": [
3676
+
{
3677
+
"type": "GitHub Sponsors",
3678
+
"url": "https://github.com/sponsors/unifiedjs"
3679
+
},
3680
+
{
3681
+
"type": "OpenCollective",
3682
+
"url": "https://opencollective.com/unified"
3683
+
}
3684
+
],
3685
+
"license": "MIT",
3686
+
"dependencies": {
3687
+
"micromark-util-symbol": "^2.0.0",
3688
+
"micromark-util-types": "^2.0.0"
3689
+
}
3690
+
},
3691
+
"node_modules/micromark-util-encode": {
3692
+
"version": "2.0.1",
3693
+
"resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
3694
+
"integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
3695
+
"funding": [
3696
+
{
3697
+
"type": "GitHub Sponsors",
3698
+
"url": "https://github.com/sponsors/unifiedjs"
3699
+
},
3700
+
{
3701
+
"type": "OpenCollective",
3702
+
"url": "https://opencollective.com/unified"
3703
+
}
3704
+
],
3705
+
"license": "MIT"
3706
+
},
3707
+
"node_modules/micromark-util-sanitize-uri": {
3708
+
"version": "2.0.1",
3709
+
"resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
3710
+
"integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
3711
+
"funding": [
3712
+
{
3713
+
"type": "GitHub Sponsors",
3714
+
"url": "https://github.com/sponsors/unifiedjs"
3715
+
},
3716
+
{
3717
+
"type": "OpenCollective",
3718
+
"url": "https://opencollective.com/unified"
3719
+
}
3720
+
],
3721
+
"license": "MIT",
3722
+
"dependencies": {
3723
+
"micromark-util-character": "^2.0.0",
3724
+
"micromark-util-encode": "^2.0.0",
3725
+
"micromark-util-symbol": "^2.0.0"
3726
+
}
3727
+
},
3728
+
"node_modules/micromark-util-symbol": {
3729
+
"version": "2.0.1",
3730
+
"resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
3731
+
"integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
3732
+
"funding": [
3733
+
{
3734
+
"type": "GitHub Sponsors",
3735
+
"url": "https://github.com/sponsors/unifiedjs"
3736
+
},
3737
+
{
3738
+
"type": "OpenCollective",
3739
+
"url": "https://opencollective.com/unified"
3740
+
}
3741
+
],
3742
+
"license": "MIT"
3743
+
},
3744
+
"node_modules/micromark-util-types": {
3745
+
"version": "2.0.2",
3746
+
"resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
3747
+
"integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
3748
+
"funding": [
3749
+
{
3750
+
"type": "GitHub Sponsors",
3751
+
"url": "https://github.com/sponsors/unifiedjs"
3752
+
},
3753
+
{
3754
+
"type": "OpenCollective",
3755
+
"url": "https://opencollective.com/unified"
3756
+
}
3757
+
],
3758
+
"license": "MIT"
3759
+
},
3444
3760
"node_modules/micromatch": {
3445
3761
"version": "4.0.8",
3446
3762
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
···
3671
3987
"wrappy": "1"
3672
3988
}
3673
3989
},
3990
+
"node_modules/oniguruma-parser": {
3991
+
"version": "0.12.1",
3992
+
"resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz",
3993
+
"integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==",
3994
+
"license": "MIT"
3995
+
},
3996
+
"node_modules/oniguruma-to-es": {
3997
+
"version": "4.3.3",
3998
+
"resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.3.tgz",
3999
+
"integrity": "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==",
4000
+
"license": "MIT",
4001
+
"dependencies": {
4002
+
"oniguruma-parser": "^0.12.1",
4003
+
"regex": "^6.0.1",
4004
+
"regex-recursion": "^6.0.2"
4005
+
}
4006
+
},
3674
4007
"node_modules/p-finally": {
3675
4008
"version": "1.0.0",
3676
4009
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
···
3972
4305
"asap": "~2.0.3"
3973
4306
}
3974
4307
},
4308
+
"node_modules/property-information": {
4309
+
"version": "7.1.0",
4310
+
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
4311
+
"integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
4312
+
"license": "MIT",
4313
+
"funding": {
4314
+
"type": "github",
4315
+
"url": "https://github.com/sponsors/wooorm"
4316
+
}
4317
+
},
3975
4318
"node_modules/proxy-addr": {
3976
4319
"version": "2.0.7",
3977
4320
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
···
4284
4627
"node": ">= 12.13.0"
4285
4628
}
4286
4629
},
4630
+
"node_modules/regex": {
4631
+
"version": "6.0.1",
4632
+
"resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz",
4633
+
"integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==",
4634
+
"license": "MIT",
4635
+
"dependencies": {
4636
+
"regex-utilities": "^2.3.0"
4637
+
}
4638
+
},
4639
+
"node_modules/regex-recursion": {
4640
+
"version": "6.0.2",
4641
+
"resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz",
4642
+
"integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==",
4643
+
"license": "MIT",
4644
+
"dependencies": {
4645
+
"regex-utilities": "^2.3.0"
4646
+
}
4647
+
},
4648
+
"node_modules/regex-utilities": {
4649
+
"version": "2.3.0",
4650
+
"resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz",
4651
+
"integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==",
4652
+
"license": "MIT"
4653
+
},
4287
4654
"node_modules/resolve": {
4288
4655
"version": "1.22.8",
4289
4656
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
···
4564
4931
"node": ">=8"
4565
4932
}
4566
4933
},
4934
+
"node_modules/shiki": {
4935
+
"version": "3.4.2",
4936
+
"resolved": "https://registry.npmjs.org/shiki/-/shiki-3.4.2.tgz",
4937
+
"integrity": "sha512-wuxzZzQG8kvZndD7nustrNFIKYJ1jJoWIPaBpVe2+KHSvtzMi4SBjOxrigs8qeqce/l3U0cwiC+VAkLKSunHQQ==",
4938
+
"license": "MIT",
4939
+
"dependencies": {
4940
+
"@shikijs/core": "3.4.2",
4941
+
"@shikijs/engine-javascript": "3.4.2",
4942
+
"@shikijs/engine-oniguruma": "3.4.2",
4943
+
"@shikijs/langs": "3.4.2",
4944
+
"@shikijs/themes": "3.4.2",
4945
+
"@shikijs/types": "3.4.2",
4946
+
"@shikijs/vscode-textmate": "^10.0.2",
4947
+
"@types/hast": "^3.0.4"
4948
+
}
4949
+
},
4567
4950
"node_modules/side-channel": {
4568
4951
"version": "1.0.6",
4569
4952
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
···
4660
5043
},
4661
5044
"engines": {
4662
5045
"node": ">= 8"
5046
+
}
5047
+
},
5048
+
"node_modules/space-separated-tokens": {
5049
+
"version": "2.0.2",
5050
+
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
5051
+
"integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
5052
+
"license": "MIT",
5053
+
"funding": {
5054
+
"type": "github",
5055
+
"url": "https://github.com/sponsors/wooorm"
4663
5056
}
4664
5057
},
4665
5058
"node_modules/split2": {
···
4753
5146
"node": ">=8"
4754
5147
}
4755
5148
},
5149
+
"node_modules/stringify-entities": {
5150
+
"version": "4.0.4",
5151
+
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
5152
+
"integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
5153
+
"license": "MIT",
5154
+
"dependencies": {
5155
+
"character-entities-html4": "^2.0.0",
5156
+
"character-entities-legacy": "^3.0.0"
5157
+
},
5158
+
"funding": {
5159
+
"type": "github",
5160
+
"url": "https://github.com/sponsors/wooorm"
5161
+
}
5162
+
},
4756
5163
"node_modules/strip-ansi": {
4757
5164
"version": "7.1.0",
4758
5165
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
···
5042
5449
"license": "MIT",
5043
5450
"bin": {
5044
5451
"tree-kill": "cli.js"
5452
+
}
5453
+
},
5454
+
"node_modules/trim-lines": {
5455
+
"version": "3.0.1",
5456
+
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
5457
+
"integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
5458
+
"license": "MIT",
5459
+
"funding": {
5460
+
"type": "github",
5461
+
"url": "https://github.com/sponsors/wooorm"
5045
5462
}
5046
5463
},
5047
5464
"node_modules/ts-interface-checker": {
···
5752
6169
"dev": true,
5753
6170
"license": "MIT"
5754
6171
},
6172
+
"node_modules/unist-util-is": {
6173
+
"version": "6.0.0",
6174
+
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
6175
+
"integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
6176
+
"license": "MIT",
6177
+
"dependencies": {
6178
+
"@types/unist": "^3.0.0"
6179
+
},
6180
+
"funding": {
6181
+
"type": "opencollective",
6182
+
"url": "https://opencollective.com/unified"
6183
+
}
6184
+
},
6185
+
"node_modules/unist-util-position": {
6186
+
"version": "5.0.0",
6187
+
"resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
6188
+
"integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
6189
+
"license": "MIT",
6190
+
"dependencies": {
6191
+
"@types/unist": "^3.0.0"
6192
+
},
6193
+
"funding": {
6194
+
"type": "opencollective",
6195
+
"url": "https://opencollective.com/unified"
6196
+
}
6197
+
},
6198
+
"node_modules/unist-util-stringify-position": {
6199
+
"version": "4.0.0",
6200
+
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
6201
+
"integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
6202
+
"license": "MIT",
6203
+
"dependencies": {
6204
+
"@types/unist": "^3.0.0"
6205
+
},
6206
+
"funding": {
6207
+
"type": "opencollective",
6208
+
"url": "https://opencollective.com/unified"
6209
+
}
6210
+
},
6211
+
"node_modules/unist-util-visit": {
6212
+
"version": "5.0.0",
6213
+
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
6214
+
"integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
6215
+
"license": "MIT",
6216
+
"dependencies": {
6217
+
"@types/unist": "^3.0.0",
6218
+
"unist-util-is": "^6.0.0",
6219
+
"unist-util-visit-parents": "^6.0.0"
6220
+
},
6221
+
"funding": {
6222
+
"type": "opencollective",
6223
+
"url": "https://opencollective.com/unified"
6224
+
}
6225
+
},
6226
+
"node_modules/unist-util-visit-parents": {
6227
+
"version": "6.0.1",
6228
+
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
6229
+
"integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
6230
+
"license": "MIT",
6231
+
"dependencies": {
6232
+
"@types/unist": "^3.0.0",
6233
+
"unist-util-is": "^6.0.0"
6234
+
},
6235
+
"funding": {
6236
+
"type": "opencollective",
6237
+
"url": "https://opencollective.com/unified"
6238
+
}
6239
+
},
5755
6240
"node_modules/unpipe": {
5756
6241
"version": "1.0.0",
5757
6242
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
···
5796
6281
"license": "MIT",
5797
6282
"engines": {
5798
6283
"node": ">= 0.8"
6284
+
}
6285
+
},
6286
+
"node_modules/vfile": {
6287
+
"version": "6.0.3",
6288
+
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
6289
+
"integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
6290
+
"license": "MIT",
6291
+
"dependencies": {
6292
+
"@types/unist": "^3.0.0",
6293
+
"vfile-message": "^4.0.0"
6294
+
},
6295
+
"funding": {
6296
+
"type": "opencollective",
6297
+
"url": "https://opencollective.com/unified"
6298
+
}
6299
+
},
6300
+
"node_modules/vfile-message": {
6301
+
"version": "4.0.2",
6302
+
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
6303
+
"integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
6304
+
"license": "MIT",
6305
+
"dependencies": {
6306
+
"@types/unist": "^3.0.0",
6307
+
"unist-util-stringify-position": "^4.0.0"
6308
+
},
6309
+
"funding": {
6310
+
"type": "opencollective",
6311
+
"url": "https://opencollective.com/unified"
5799
6312
}
5800
6313
},
5801
6314
"node_modules/void-elements": {
···
6003
6516
"license": "MIT",
6004
6517
"funding": {
6005
6518
"url": "https://github.com/sponsors/colinhacks"
6519
+
}
6520
+
},
6521
+
"node_modules/zwitch": {
6522
+
"version": "2.0.4",
6523
+
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
6524
+
"integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
6525
+
"license": "MIT",
6526
+
"funding": {
6527
+
"type": "github",
6528
+
"url": "https://github.com/sponsors/wooorm"
6006
6529
}
6007
6530
}
6008
6531
}
+5
package.json
+5
package.json
···
30
30
"multiformats": "^9.9.0",
31
31
"pino": "^9.3.2",
32
32
"pug": "^3.0.3",
33
+
"shiki": "^3.4.2",
33
34
"uhtml": "^4.5.9"
34
35
},
35
36
"devDependencies": {
···
49
50
"!src/**/__tests__/**",
50
51
"!src/**/*.test.*"
51
52
],
53
+
"loader": {
54
+
".pug": "copy",
55
+
".woff2": "copy"
56
+
},
52
57
"splitting": false,
53
58
"sourcemap": true,
54
59
"clean": true
src/assets/NerdIosevka-Regular.woff2
src/assets/NerdIosevka-Regular.woff2
This is a binary file and will not be displayed.
+4
-4
src/auth/client.ts
+4
-4
src/auth/client.ts
···
4
4
import { SessionStore, StateStore } from "./storage";
5
5
6
6
export const createClient = async (db: Database) => {
7
-
const publicUrl = env.PUBLIC_URL;
8
-
const url = publicUrl || `http://127.0.0.1:${env.PORT}`;
7
+
const publicUrl = env.PLONK_PUBLIC_URL;
8
+
const url = publicUrl || `http://127.0.0.1:${env.PLONK_PORT}`;
9
9
const enc = encodeURIComponent;
10
10
return new NodeOAuthClient({
11
11
clientMetadata: {
12
-
client_name: "AT Protocol Express App",
12
+
client_name: "plonk.li",
13
13
client_id: publicUrl
14
14
? `${url}/client-metadata.json`
15
-
: `http://localhost?redirect_uri=${enc(`${url}/oauth/callback`)}&scope=${enc("atproto transition:generic")}`,
15
+
: `http://${env.PLONK_HOST}?redirect_uri=${enc(`${url}/oauth/callback`)}&scope=${enc("atproto transition:generic")}`,
16
16
client_uri: url,
17
17
redirect_uris: [`${url}/oauth/callback`],
18
18
scope: "atproto transition:generic",
+2
-2
src/db.ts
+2
-2
src/db.ts
···
47
47
indexedAt: string;
48
48
pasteUri: string;
49
49
pasteCid: string;
50
-
}
50
+
};
51
51
52
52
type AuthSessionJson = string;
53
53
type AuthStateJson = string;
···
115
115
async down(db: Kysely<unknown>) {
116
116
await db.schema.dropTable("comments").execute();
117
117
},
118
-
}
118
+
};
119
119
120
120
function generateShortString(length: number): string {
121
121
return randomBytes(length).toString("base64url").substring(0, length);
+2
-2
src/index.ts
+2
-2
src/index.ts
···
32
32
) {}
33
33
34
34
static async create() {
35
-
const db: Database = createDb(env.DB_PATH);
35
+
const db: Database = createDb(env.PLONK_DB_PATH);
36
36
await migrateToLatest(db);
37
37
const idResolver = createIdResolver();
38
38
const ctx: Ctx = {
···
55
55
app.use(router);
56
56
app.use((_req, res) => res.sendStatus(404));
57
57
58
-
const server = app.listen(env.PORT);
58
+
const server = app.listen(env.PLONK_PORT);
59
59
60
60
return new Server(app, server, ctx);
61
61
}
+8
-8
src/ingester.ts
+8
-8
src/ingester.ts
···
2
2
import { IdResolver } from "@atproto/identity";
3
3
import { Firehose } from "@atproto/sync";
4
4
import type { Database } from "#/db";
5
-
import * as Paste from "#/lexicons/types/ovh/plonk/paste";
6
-
import * as Comment from "#/lexicons/types/ovh/plonk/comment";
5
+
import * as Paste from "#/lexicons/types/li/plonk/paste";
6
+
import * as Comment from "#/lexicons/types/li/plonk/comment";
7
7
8
8
export function createIngester(db: Database, idResolver: IdResolver) {
9
9
const logger = pino({ name: "firehose ingestion" });
···
17
17
18
18
// If the write is a valid status update
19
19
if (
20
-
evt.collection === "ovh.plonk.paste" &&
20
+
evt.collection === "li.plonk.paste" &&
21
21
Paste.isRecord(record) &&
22
22
Paste.validateRecord(record).success
23
23
) {
···
43
43
)
44
44
.execute();
45
45
} else if (
46
-
evt.collection === "ovh.plonk.comment" &&
46
+
evt.collection === "li.plonk.comment" &&
47
47
Comment.isRecord(record) &&
48
48
Comment.validateRecord(record).success
49
49
) {
···
70
70
}
71
71
} else if (
72
72
evt.event === "delete" &&
73
-
evt.collection === "ovh.plonk.paste"
73
+
evt.collection === "li.plonk.paste"
74
74
) {
75
75
// Remove the status from our SQLite
76
76
await db
···
79
79
.execute();
80
80
} else if (
81
81
evt.event === "delete" &&
82
-
evt.collection === "ovh.plonk.comment"
82
+
evt.collection === "li.plonk.comment"
83
83
) {
84
84
// Remove the status from our SQLite
85
85
await db
86
86
.deleteFrom("comment")
87
87
.where("uri", "=", evt.uri.toString())
88
88
.execute();
89
-
}
89
+
}
90
90
},
91
91
onError: (err) => {
92
92
logger.error({ err }, "error on firehose ingestion");
93
93
},
94
-
filterCollections: ["ovh.plonk.paste"],
94
+
filterCollections: ["li.plonk.paste"],
95
95
excludeIdentity: true,
96
96
excludeAccount: true,
97
97
});
+105
-105
src/lexicons/index.ts
+105
-105
src/lexicons/index.ts
···
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
import {
5
-
createServer as createXrpcServer,
6
-
Server as XrpcServer,
7
-
Options as XrpcOptions,
8
-
AuthVerifier,
9
-
StreamAuthVerifier,
10
-
} from '@atproto/xrpc-server'
11
-
import { schemas } from './lexicons'
12
-
import * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord'
13
-
import * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords'
5
+
createServer as createXrpcServer,
6
+
Server as XrpcServer,
7
+
Options as XrpcOptions,
8
+
AuthVerifier,
9
+
StreamAuthVerifier,
10
+
} from "@atproto/xrpc-server";
11
+
import { schemas } from "./lexicons";
12
+
import * as ComAtprotoRepoGetRecord from "./types/com/atproto/repo/getRecord";
13
+
import * as ComAtprotoRepoListRecords from "./types/com/atproto/repo/listRecords";
14
14
15
15
export function createServer(options?: XrpcOptions): Server {
16
-
return new Server(options)
16
+
return new Server(options);
17
17
}
18
18
19
19
export class Server {
20
-
xrpc: XrpcServer
21
-
ovh: OvhNS
22
-
com: ComNS
23
-
app: AppNS
20
+
xrpc: XrpcServer;
21
+
li: LiNS;
22
+
com: ComNS;
23
+
app: AppNS;
24
24
25
-
constructor(options?: XrpcOptions) {
26
-
this.xrpc = createXrpcServer(schemas, options)
27
-
this.ovh = new OvhNS(this)
28
-
this.com = new ComNS(this)
29
-
this.app = new AppNS(this)
30
-
}
25
+
constructor(options?: XrpcOptions) {
26
+
this.xrpc = createXrpcServer(schemas, options);
27
+
this.li = new LiNS(this);
28
+
this.com = new ComNS(this);
29
+
this.app = new AppNS(this);
30
+
}
31
31
}
32
32
33
-
export class OvhNS {
34
-
_server: Server
35
-
plonk: OvhPlonkNS
33
+
export class LiNS {
34
+
_server: Server;
35
+
plonk: LiPlonkNS;
36
36
37
-
constructor(server: Server) {
38
-
this._server = server
39
-
this.plonk = new OvhPlonkNS(server)
40
-
}
37
+
constructor(server: Server) {
38
+
this._server = server;
39
+
this.plonk = new LiPlonkNS(server);
40
+
}
41
41
}
42
42
43
-
export class OvhPlonkNS {
44
-
_server: Server
43
+
export class LiPlonkNS {
44
+
_server: Server;
45
45
46
-
constructor(server: Server) {
47
-
this._server = server
48
-
}
46
+
constructor(server: Server) {
47
+
this._server = server;
48
+
}
49
49
}
50
50
51
51
export class ComNS {
52
-
_server: Server
53
-
atproto: ComAtprotoNS
52
+
_server: Server;
53
+
atproto: ComAtprotoNS;
54
54
55
-
constructor(server: Server) {
56
-
this._server = server
57
-
this.atproto = new ComAtprotoNS(server)
58
-
}
55
+
constructor(server: Server) {
56
+
this._server = server;
57
+
this.atproto = new ComAtprotoNS(server);
58
+
}
59
59
}
60
60
61
61
export class ComAtprotoNS {
62
-
_server: Server
63
-
repo: ComAtprotoRepoNS
62
+
_server: Server;
63
+
repo: ComAtprotoRepoNS;
64
64
65
-
constructor(server: Server) {
66
-
this._server = server
67
-
this.repo = new ComAtprotoRepoNS(server)
68
-
}
65
+
constructor(server: Server) {
66
+
this._server = server;
67
+
this.repo = new ComAtprotoRepoNS(server);
68
+
}
69
69
}
70
70
71
71
export class ComAtprotoRepoNS {
72
-
_server: Server
72
+
_server: Server;
73
73
74
-
constructor(server: Server) {
75
-
this._server = server
76
-
}
74
+
constructor(server: Server) {
75
+
this._server = server;
76
+
}
77
77
78
-
getRecord<AV extends AuthVerifier>(
79
-
cfg: ConfigOf<
80
-
AV,
81
-
ComAtprotoRepoGetRecord.Handler<ExtractAuth<AV>>,
82
-
ComAtprotoRepoGetRecord.HandlerReqCtx<ExtractAuth<AV>>
83
-
>,
84
-
) {
85
-
const nsid = 'com.atproto.repo.getRecord' // @ts-ignore
86
-
return this._server.xrpc.method(nsid, cfg)
87
-
}
78
+
getRecord<AV extends AuthVerifier>(
79
+
cfg: ConfigOf<
80
+
AV,
81
+
ComAtprotoRepoGetRecord.Handler<ExtractAuth<AV>>,
82
+
ComAtprotoRepoGetRecord.HandlerReqCtx<ExtractAuth<AV>>
83
+
>,
84
+
) {
85
+
const nsid = "com.atproto.repo.getRecord"; // @ts-ignore
86
+
return this._server.xrpc.method(nsid, cfg);
87
+
}
88
88
89
-
listRecords<AV extends AuthVerifier>(
90
-
cfg: ConfigOf<
91
-
AV,
92
-
ComAtprotoRepoListRecords.Handler<ExtractAuth<AV>>,
93
-
ComAtprotoRepoListRecords.HandlerReqCtx<ExtractAuth<AV>>
94
-
>,
95
-
) {
96
-
const nsid = 'com.atproto.repo.listRecords' // @ts-ignore
97
-
return this._server.xrpc.method(nsid, cfg)
98
-
}
89
+
listRecords<AV extends AuthVerifier>(
90
+
cfg: ConfigOf<
91
+
AV,
92
+
ComAtprotoRepoListRecords.Handler<ExtractAuth<AV>>,
93
+
ComAtprotoRepoListRecords.HandlerReqCtx<ExtractAuth<AV>>
94
+
>,
95
+
) {
96
+
const nsid = "com.atproto.repo.listRecords"; // @ts-ignore
97
+
return this._server.xrpc.method(nsid, cfg);
98
+
}
99
99
}
100
100
101
101
export class AppNS {
102
-
_server: Server
103
-
bsky: AppBskyNS
102
+
_server: Server;
103
+
bsky: AppBskyNS;
104
104
105
-
constructor(server: Server) {
106
-
this._server = server
107
-
this.bsky = new AppBskyNS(server)
108
-
}
105
+
constructor(server: Server) {
106
+
this._server = server;
107
+
this.bsky = new AppBskyNS(server);
108
+
}
109
109
}
110
110
111
111
export class AppBskyNS {
112
-
_server: Server
113
-
actor: AppBskyActorNS
112
+
_server: Server;
113
+
actor: AppBskyActorNS;
114
114
115
-
constructor(server: Server) {
116
-
this._server = server
117
-
this.actor = new AppBskyActorNS(server)
118
-
}
115
+
constructor(server: Server) {
116
+
this._server = server;
117
+
this.actor = new AppBskyActorNS(server);
118
+
}
119
119
}
120
120
121
121
export class AppBskyActorNS {
122
-
_server: Server
122
+
_server: Server;
123
123
124
-
constructor(server: Server) {
125
-
this._server = server
126
-
}
124
+
constructor(server: Server) {
125
+
this._server = server;
126
+
}
127
127
}
128
128
129
129
type SharedRateLimitOpts<T> = {
130
-
name: string
131
-
calcKey?: (ctx: T) => string
132
-
calcPoints?: (ctx: T) => number
133
-
}
130
+
name: string;
131
+
calcKey?: (ctx: T) => string;
132
+
calcPoints?: (ctx: T) => number;
133
+
};
134
134
type RouteRateLimitOpts<T> = {
135
-
durationMs: number
136
-
points: number
137
-
calcKey?: (ctx: T) => string
138
-
calcPoints?: (ctx: T) => number
139
-
}
140
-
type HandlerOpts = { blobLimit?: number }
141
-
type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T>
135
+
durationMs: number;
136
+
points: number;
137
+
calcKey?: (ctx: T) => string;
138
+
calcPoints?: (ctx: T) => number;
139
+
};
140
+
type HandlerOpts = { blobLimit?: number };
141
+
type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T>;
142
142
type ConfigOf<Auth, Handler, ReqCtx> =
143
-
| Handler
144
-
| {
145
-
auth?: Auth
146
-
opts?: HandlerOpts
147
-
rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[]
148
-
handler: Handler
149
-
}
143
+
| Handler
144
+
| {
145
+
auth?: Auth;
146
+
opts?: HandlerOpts;
147
+
rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[];
148
+
handler: Handler;
149
+
};
150
150
type ExtractAuth<AV extends AuthVerifier | StreamAuthVerifier> = Extract<
151
-
Awaited<ReturnType<AV>>,
152
-
{ credentials: unknown }
153
-
>
151
+
Awaited<ReturnType<AV>>,
152
+
{ credentials: unknown }
153
+
>;
+491
-491
src/lexicons/lexicons.ts
+491
-491
src/lexicons/lexicons.ts
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
-
import { LexiconDoc, Lexicons } from '@atproto/lexicon'
4
+
import { LexiconDoc, Lexicons } from "@atproto/lexicon";
5
5
6
6
export const schemaDict = {
7
-
OvhPlonkComment: {
8
-
lexicon: 1,
9
-
id: 'ovh.plonk.comment',
10
-
defs: {
11
-
main: {
12
-
type: 'record',
13
-
key: 'tid',
14
-
record: {
15
-
type: 'object',
16
-
required: ['content', 'createdAt', 'post'],
17
-
properties: {
18
-
content: {
19
-
type: 'string',
20
-
maxLength: 100000,
21
-
maxGraphemes: 10000,
22
-
description: 'comment body',
23
-
},
24
-
createdAt: {
25
-
type: 'string',
26
-
format: 'datetime',
27
-
description: 'comment creation timestamp',
28
-
},
29
-
post: {
30
-
type: 'ref',
31
-
ref: 'lex:com.atproto.repo.strongRef',
32
-
},
33
-
},
34
-
},
35
-
},
36
-
},
37
-
},
38
-
ComAtprotoLabelDefs: {
39
-
lexicon: 1,
40
-
id: 'com.atproto.label.defs',
41
-
defs: {
42
-
label: {
43
-
type: 'object',
44
-
description:
45
-
'Metadata tag on an atproto resource (eg, repo or record).',
46
-
required: ['src', 'uri', 'val', 'cts'],
47
-
properties: {
48
-
ver: {
49
-
type: 'integer',
50
-
description: 'The AT Protocol version of the label object.',
51
-
},
52
-
src: {
53
-
type: 'string',
54
-
format: 'did',
55
-
description: 'DID of the actor who created this label.',
56
-
},
57
-
uri: {
58
-
type: 'string',
59
-
format: 'uri',
60
-
description:
61
-
'AT URI of the record, repository (account), or other resource that this label applies to.',
62
-
},
63
-
cid: {
64
-
type: 'string',
65
-
format: 'cid',
66
-
description:
67
-
"Optionally, CID specifying the specific version of 'uri' resource this label applies to.",
68
-
},
69
-
val: {
70
-
type: 'string',
71
-
maxLength: 128,
72
-
description:
73
-
'The short string name of the value or type of this label.',
74
-
},
75
-
neg: {
76
-
type: 'boolean',
77
-
description:
78
-
'If true, this is a negation label, overwriting a previous label.',
79
-
},
80
-
cts: {
81
-
type: 'string',
82
-
format: 'datetime',
83
-
description: 'Timestamp when this label was created.',
84
-
},
85
-
exp: {
86
-
type: 'string',
87
-
format: 'datetime',
88
-
description:
89
-
'Timestamp at which this label expires (no longer applies).',
90
-
},
91
-
sig: {
92
-
type: 'bytes',
93
-
description: 'Signature of dag-cbor encoded label.',
94
-
},
95
-
},
96
-
},
97
-
selfLabels: {
98
-
type: 'object',
99
-
description:
100
-
'Metadata tags on an atproto record, published by the author within the record.',
101
-
required: ['values'],
102
-
properties: {
103
-
values: {
104
-
type: 'array',
105
-
items: {
106
-
type: 'ref',
107
-
ref: 'lex:com.atproto.label.defs#selfLabel',
108
-
},
109
-
maxLength: 10,
110
-
},
111
-
},
112
-
},
113
-
selfLabel: {
114
-
type: 'object',
115
-
description:
116
-
'Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.',
117
-
required: ['val'],
118
-
properties: {
119
-
val: {
120
-
type: 'string',
121
-
maxLength: 128,
122
-
description:
123
-
'The short string name of the value or type of this label.',
124
-
},
125
-
},
126
-
},
127
-
labelValueDefinition: {
128
-
type: 'object',
129
-
description:
130
-
'Declares a label value and its expected interpretations and behaviors.',
131
-
required: ['identifier', 'severity', 'blurs', 'locales'],
132
-
properties: {
133
-
identifier: {
134
-
type: 'string',
135
-
description:
136
-
"The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).",
137
-
maxLength: 100,
138
-
maxGraphemes: 100,
139
-
},
140
-
severity: {
141
-
type: 'string',
142
-
description:
143
-
"How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.",
144
-
knownValues: ['inform', 'alert', 'none'],
145
-
},
146
-
blurs: {
147
-
type: 'string',
148
-
description:
149
-
"What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.",
150
-
knownValues: ['content', 'media', 'none'],
151
-
},
152
-
defaultSetting: {
153
-
type: 'string',
154
-
description: 'The default setting for this label.',
155
-
knownValues: ['ignore', 'warn', 'hide'],
156
-
default: 'warn',
157
-
},
158
-
adultOnly: {
159
-
type: 'boolean',
160
-
description:
161
-
'Does the user need to have adult content enabled in order to configure this label?',
162
-
},
163
-
locales: {
164
-
type: 'array',
165
-
items: {
166
-
type: 'ref',
167
-
ref: 'lex:com.atproto.label.defs#labelValueDefinitionStrings',
168
-
},
169
-
},
170
-
},
171
-
},
172
-
labelValueDefinitionStrings: {
173
-
type: 'object',
174
-
description:
175
-
'Strings which describe the label in the UI, localized into a specific language.',
176
-
required: ['lang', 'name', 'description'],
177
-
properties: {
178
-
lang: {
179
-
type: 'string',
180
-
description:
181
-
'The code of the language these strings are written in.',
182
-
format: 'language',
183
-
},
184
-
name: {
185
-
type: 'string',
186
-
description: 'A short human-readable name for the label.',
187
-
maxGraphemes: 64,
188
-
maxLength: 640,
189
-
},
190
-
description: {
191
-
type: 'string',
192
-
description:
193
-
'A longer description of what the label means and why it might be applied.',
194
-
maxGraphemes: 10000,
195
-
maxLength: 100000,
196
-
},
197
-
},
198
-
},
199
-
labelValue: {
200
-
type: 'string',
201
-
knownValues: [
202
-
'!hide',
203
-
'!no-promote',
204
-
'!warn',
205
-
'!no-unauthenticated',
206
-
'dmca-violation',
207
-
'doxxing',
208
-
'porn',
209
-
'sexual',
210
-
'nudity',
211
-
'nsfl',
212
-
'gore',
213
-
],
214
-
},
215
-
},
216
-
},
217
-
ComAtprotoRepoGetRecord: {
218
-
lexicon: 1,
219
-
id: 'com.atproto.repo.getRecord',
220
-
defs: {
221
-
main: {
222
-
type: 'query',
223
-
description:
224
-
'Get a single record from a repository. Does not require auth.',
225
-
parameters: {
226
-
type: 'params',
227
-
required: ['repo', 'collection', 'rkey'],
228
-
properties: {
229
-
repo: {
230
-
type: 'string',
231
-
format: 'at-identifier',
232
-
description: 'The handle or DID of the repo.',
233
-
},
234
-
collection: {
235
-
type: 'string',
236
-
format: 'nsid',
237
-
description: 'The NSID of the record collection.',
238
-
},
239
-
rkey: {
240
-
type: 'string',
241
-
description: 'The Record Key.',
242
-
},
243
-
cid: {
244
-
type: 'string',
245
-
format: 'cid',
246
-
description:
247
-
'The CID of the version of the record. If not specified, then return the most recent version.',
248
-
},
249
-
},
250
-
},
251
-
output: {
252
-
encoding: 'application/json',
253
-
schema: {
254
-
type: 'object',
255
-
required: ['uri', 'value'],
256
-
properties: {
257
-
uri: {
258
-
type: 'string',
259
-
format: 'at-uri',
260
-
},
261
-
cid: {
262
-
type: 'string',
263
-
format: 'cid',
264
-
},
265
-
value: {
266
-
type: 'unknown',
267
-
},
268
-
},
269
-
},
270
-
},
271
-
errors: [
272
-
{
273
-
name: 'RecordNotFound',
274
-
},
275
-
],
276
-
},
277
-
},
278
-
},
279
-
ComAtprotoRepoListRecords: {
280
-
lexicon: 1,
281
-
id: 'com.atproto.repo.listRecords',
282
-
defs: {
283
-
main: {
284
-
type: 'query',
285
-
description:
286
-
'List a range of records in a repository, matching a specific collection. Does not require auth.',
287
-
parameters: {
288
-
type: 'params',
289
-
required: ['repo', 'collection'],
290
-
properties: {
291
-
repo: {
292
-
type: 'string',
293
-
format: 'at-identifier',
294
-
description: 'The handle or DID of the repo.',
295
-
},
296
-
collection: {
297
-
type: 'string',
298
-
format: 'nsid',
299
-
description: 'The NSID of the record type.',
300
-
},
301
-
limit: {
302
-
type: 'integer',
303
-
minimum: 1,
304
-
maximum: 100,
305
-
default: 50,
306
-
description: 'The number of records to return.',
307
-
},
308
-
cursor: {
309
-
type: 'string',
310
-
},
311
-
rkeyStart: {
312
-
type: 'string',
313
-
description:
314
-
'DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)',
315
-
},
316
-
rkeyEnd: {
317
-
type: 'string',
318
-
description:
319
-
'DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)',
320
-
},
321
-
reverse: {
322
-
type: 'boolean',
323
-
description: 'Flag to reverse the order of the returned records.',
324
-
},
325
-
},
326
-
},
327
-
output: {
328
-
encoding: 'application/json',
329
-
schema: {
330
-
type: 'object',
331
-
required: ['records'],
332
-
properties: {
333
-
cursor: {
334
-
type: 'string',
335
-
},
336
-
records: {
337
-
type: 'array',
338
-
items: {
339
-
type: 'ref',
340
-
ref: 'lex:com.atproto.repo.listRecords#record',
341
-
},
342
-
},
343
-
},
344
-
},
345
-
},
346
-
},
347
-
record: {
348
-
type: 'object',
349
-
required: ['uri', 'cid', 'value'],
350
-
properties: {
351
-
uri: {
352
-
type: 'string',
353
-
format: 'at-uri',
354
-
},
355
-
cid: {
356
-
type: 'string',
357
-
format: 'cid',
358
-
},
359
-
value: {
360
-
type: 'unknown',
361
-
},
362
-
},
363
-
},
364
-
},
365
-
},
366
-
OvhPlonkPaste: {
367
-
lexicon: 1,
368
-
id: 'ovh.plonk.paste',
369
-
defs: {
370
-
main: {
371
-
type: 'record',
372
-
key: 'tid',
373
-
record: {
374
-
type: 'object',
375
-
required: ['code', 'shortUrl', 'lang', 'title', 'createdAt'],
376
-
properties: {
377
-
code: {
378
-
type: 'string',
379
-
minLength: 1,
380
-
maxGraphemes: 65536,
381
-
maxLength: 65536,
382
-
},
383
-
shortUrl: {
384
-
type: 'string',
385
-
minLength: 2,
386
-
maxGraphemes: 10,
387
-
maxLength: 10,
388
-
},
389
-
lang: {
390
-
type: 'string',
391
-
minLength: 1,
392
-
maxGraphemes: 20,
393
-
maxLength: 20,
394
-
},
395
-
title: {
396
-
type: 'string',
397
-
minLength: 1,
398
-
maxGraphemes: 100,
399
-
maxLength: 100,
400
-
},
401
-
createdAt: {
402
-
type: 'string',
403
-
format: 'datetime',
404
-
},
405
-
},
406
-
},
407
-
},
408
-
},
409
-
},
410
-
AppBskyActorProfile: {
411
-
lexicon: 1,
412
-
id: 'app.bsky.actor.profile',
413
-
defs: {
414
-
main: {
415
-
type: 'record',
416
-
description: 'A declaration of a Bluesky account profile.',
417
-
key: 'literal:self',
418
-
record: {
419
-
type: 'object',
420
-
properties: {
421
-
displayName: {
422
-
type: 'string',
423
-
maxGraphemes: 64,
424
-
maxLength: 640,
425
-
},
426
-
description: {
427
-
type: 'string',
428
-
description: 'Free-form profile description text.',
429
-
maxGraphemes: 256,
430
-
maxLength: 2560,
431
-
},
432
-
avatar: {
433
-
type: 'blob',
434
-
description:
435
-
"Small image to be displayed next to posts from account. AKA, 'profile picture'",
436
-
accept: ['image/png', 'image/jpeg'],
437
-
maxSize: 1000000,
438
-
},
439
-
banner: {
440
-
type: 'blob',
441
-
description:
442
-
'Larger horizontal image to display behind profile view.',
443
-
accept: ['image/png', 'image/jpeg'],
444
-
maxSize: 1000000,
445
-
},
446
-
labels: {
447
-
type: 'union',
448
-
description:
449
-
'Self-label values, specific to the Bluesky application, on the overall account.',
450
-
refs: ['lex:com.atproto.label.defs#selfLabels'],
451
-
},
452
-
joinedViaStarterPack: {
453
-
type: 'ref',
454
-
ref: 'lex:com.atproto.repo.strongRef',
455
-
},
456
-
createdAt: {
457
-
type: 'string',
458
-
format: 'datetime',
459
-
},
460
-
},
461
-
},
462
-
},
463
-
},
464
-
},
465
-
ComAtprotoRepoStrongRef: {
466
-
lexicon: 1,
467
-
id: 'com.atproto.repo.strongRef',
468
-
description: 'A URI with a content-hash fingerprint.',
469
-
defs: {
470
-
main: {
471
-
type: 'object',
472
-
required: ['uri', 'cid'],
473
-
properties: {
474
-
uri: {
475
-
type: 'string',
476
-
format: 'at-uri',
477
-
},
478
-
cid: {
479
-
type: 'string',
480
-
format: 'cid',
481
-
},
482
-
},
483
-
},
484
-
},
485
-
},
486
-
}
487
-
export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[]
488
-
export const lexicons: Lexicons = new Lexicons(schemas)
7
+
LiPlonkComment: {
8
+
lexicon: 1,
9
+
id: "li.plonk.comment",
10
+
defs: {
11
+
main: {
12
+
type: "record",
13
+
key: "tid",
14
+
record: {
15
+
type: "object",
16
+
required: ["content", "createdAt", "post"],
17
+
properties: {
18
+
content: {
19
+
type: "string",
20
+
maxLength: 100000,
21
+
maxGraphemes: 10000,
22
+
description: "comment body",
23
+
},
24
+
createdAt: {
25
+
type: "string",
26
+
format: "datetime",
27
+
description: "comment creation timestamp",
28
+
},
29
+
post: {
30
+
type: "ref",
31
+
ref: "lex:com.atproto.repo.strongRef",
32
+
},
33
+
},
34
+
},
35
+
},
36
+
},
37
+
},
38
+
ComAtprotoLabelDefs: {
39
+
lexicon: 1,
40
+
id: "com.atproto.label.defs",
41
+
defs: {
42
+
label: {
43
+
type: "object",
44
+
description:
45
+
"Metadata tag on an atproto resource (eg, repo or record).",
46
+
required: ["src", "uri", "val", "cts"],
47
+
properties: {
48
+
ver: {
49
+
type: "integer",
50
+
description: "The AT Protocol version of the label object.",
51
+
},
52
+
src: {
53
+
type: "string",
54
+
format: "did",
55
+
description: "DID of the actor who created this label.",
56
+
},
57
+
uri: {
58
+
type: "string",
59
+
format: "uri",
60
+
description:
61
+
"AT URI of the record, repository (account), or other resource that this label applies to.",
62
+
},
63
+
cid: {
64
+
type: "string",
65
+
format: "cid",
66
+
description:
67
+
"Optionally, CID specifying the specific version of 'uri' resource this label applies to.",
68
+
},
69
+
val: {
70
+
type: "string",
71
+
maxLength: 128,
72
+
description:
73
+
"The short string name of the value or type of this label.",
74
+
},
75
+
neg: {
76
+
type: "boolean",
77
+
description:
78
+
"If true, this is a negation label, overwriting a previous label.",
79
+
},
80
+
cts: {
81
+
type: "string",
82
+
format: "datetime",
83
+
description: "Timestamp when this label was created.",
84
+
},
85
+
exp: {
86
+
type: "string",
87
+
format: "datetime",
88
+
description:
89
+
"Timestamp at which this label expires (no longer applies).",
90
+
},
91
+
sig: {
92
+
type: "bytes",
93
+
description: "Signature of dag-cbor encoded label.",
94
+
},
95
+
},
96
+
},
97
+
selfLabels: {
98
+
type: "object",
99
+
description:
100
+
"Metadata tags on an atproto record, published by the author within the record.",
101
+
required: ["values"],
102
+
properties: {
103
+
values: {
104
+
type: "array",
105
+
items: {
106
+
type: "ref",
107
+
ref: "lex:com.atproto.label.defs#selfLabel",
108
+
},
109
+
maxLength: 10,
110
+
},
111
+
},
112
+
},
113
+
selfLabel: {
114
+
type: "object",
115
+
description:
116
+
"Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.",
117
+
required: ["val"],
118
+
properties: {
119
+
val: {
120
+
type: "string",
121
+
maxLength: 128,
122
+
description:
123
+
"The short string name of the value or type of this label.",
124
+
},
125
+
},
126
+
},
127
+
labelValueDefinition: {
128
+
type: "object",
129
+
description:
130
+
"Declares a label value and its expected interpretations and behaviors.",
131
+
required: ["identifier", "severity", "blurs", "locales"],
132
+
properties: {
133
+
identifier: {
134
+
type: "string",
135
+
description:
136
+
"The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).",
137
+
maxLength: 100,
138
+
maxGraphemes: 100,
139
+
},
140
+
severity: {
141
+
type: "string",
142
+
description:
143
+
"How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.",
144
+
knownValues: ["inform", "alert", "none"],
145
+
},
146
+
blurs: {
147
+
type: "string",
148
+
description:
149
+
"What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.",
150
+
knownValues: ["content", "media", "none"],
151
+
},
152
+
defaultSetting: {
153
+
type: "string",
154
+
description: "The default setting for this label.",
155
+
knownValues: ["ignore", "warn", "hide"],
156
+
default: "warn",
157
+
},
158
+
adultOnly: {
159
+
type: "boolean",
160
+
description:
161
+
"Does the user need to have adult content enabled in order to configure this label?",
162
+
},
163
+
locales: {
164
+
type: "array",
165
+
items: {
166
+
type: "ref",
167
+
ref: "lex:com.atproto.label.defs#labelValueDefinitionStrings",
168
+
},
169
+
},
170
+
},
171
+
},
172
+
labelValueDefinitionStrings: {
173
+
type: "object",
174
+
description:
175
+
"Strings which describe the label in the UI, localized into a specific language.",
176
+
required: ["lang", "name", "description"],
177
+
properties: {
178
+
lang: {
179
+
type: "string",
180
+
description:
181
+
"The code of the language these strings are written in.",
182
+
format: "language",
183
+
},
184
+
name: {
185
+
type: "string",
186
+
description: "A short human-readable name for the label.",
187
+
maxGraphemes: 64,
188
+
maxLength: 640,
189
+
},
190
+
description: {
191
+
type: "string",
192
+
description:
193
+
"A longer description of what the label means and why it might be applied.",
194
+
maxGraphemes: 10000,
195
+
maxLength: 100000,
196
+
},
197
+
},
198
+
},
199
+
labelValue: {
200
+
type: "string",
201
+
knownValues: [
202
+
"!hide",
203
+
"!no-promote",
204
+
"!warn",
205
+
"!no-unauthenticated",
206
+
"dmca-violation",
207
+
"doxxing",
208
+
"porn",
209
+
"sexual",
210
+
"nudity",
211
+
"nsfl",
212
+
"gore",
213
+
],
214
+
},
215
+
},
216
+
},
217
+
ComAtprotoRepoGetRecord: {
218
+
lexicon: 1,
219
+
id: "com.atproto.repo.getRecord",
220
+
defs: {
221
+
main: {
222
+
type: "query",
223
+
description:
224
+
"Get a single record from a repository. Does not require auth.",
225
+
parameters: {
226
+
type: "params",
227
+
required: ["repo", "collection", "rkey"],
228
+
properties: {
229
+
repo: {
230
+
type: "string",
231
+
format: "at-identifier",
232
+
description: "The handle or DID of the repo.",
233
+
},
234
+
collection: {
235
+
type: "string",
236
+
format: "nsid",
237
+
description: "The NSID of the record collection.",
238
+
},
239
+
rkey: {
240
+
type: "string",
241
+
description: "The Record Key.",
242
+
},
243
+
cid: {
244
+
type: "string",
245
+
format: "cid",
246
+
description:
247
+
"The CID of the version of the record. If not specified, then return the most recent version.",
248
+
},
249
+
},
250
+
},
251
+
output: {
252
+
encoding: "application/json",
253
+
schema: {
254
+
type: "object",
255
+
required: ["uri", "value"],
256
+
properties: {
257
+
uri: {
258
+
type: "string",
259
+
format: "at-uri",
260
+
},
261
+
cid: {
262
+
type: "string",
263
+
format: "cid",
264
+
},
265
+
value: {
266
+
type: "unknown",
267
+
},
268
+
},
269
+
},
270
+
},
271
+
errors: [
272
+
{
273
+
name: "RecordNotFound",
274
+
},
275
+
],
276
+
},
277
+
},
278
+
},
279
+
ComAtprotoRepoListRecords: {
280
+
lexicon: 1,
281
+
id: "com.atproto.repo.listRecords",
282
+
defs: {
283
+
main: {
284
+
type: "query",
285
+
description:
286
+
"List a range of records in a repository, matching a specific collection. Does not require auth.",
287
+
parameters: {
288
+
type: "params",
289
+
required: ["repo", "collection"],
290
+
properties: {
291
+
repo: {
292
+
type: "string",
293
+
format: "at-identifier",
294
+
description: "The handle or DID of the repo.",
295
+
},
296
+
collection: {
297
+
type: "string",
298
+
format: "nsid",
299
+
description: "The NSID of the record type.",
300
+
},
301
+
limit: {
302
+
type: "integer",
303
+
minimum: 1,
304
+
maximum: 100,
305
+
default: 50,
306
+
description: "The number of records to return.",
307
+
},
308
+
cursor: {
309
+
type: "string",
310
+
},
311
+
rkeyStart: {
312
+
type: "string",
313
+
description:
314
+
"DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)",
315
+
},
316
+
rkeyEnd: {
317
+
type: "string",
318
+
description:
319
+
"DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)",
320
+
},
321
+
reverse: {
322
+
type: "boolean",
323
+
description: "Flag to reverse the order of the returned records.",
324
+
},
325
+
},
326
+
},
327
+
output: {
328
+
encoding: "application/json",
329
+
schema: {
330
+
type: "object",
331
+
required: ["records"],
332
+
properties: {
333
+
cursor: {
334
+
type: "string",
335
+
},
336
+
records: {
337
+
type: "array",
338
+
items: {
339
+
type: "ref",
340
+
ref: "lex:com.atproto.repo.listRecords#record",
341
+
},
342
+
},
343
+
},
344
+
},
345
+
},
346
+
},
347
+
record: {
348
+
type: "object",
349
+
required: ["uri", "cid", "value"],
350
+
properties: {
351
+
uri: {
352
+
type: "string",
353
+
format: "at-uri",
354
+
},
355
+
cid: {
356
+
type: "string",
357
+
format: "cid",
358
+
},
359
+
value: {
360
+
type: "unknown",
361
+
},
362
+
},
363
+
},
364
+
},
365
+
},
366
+
LiPlonkPaste: {
367
+
lexicon: 1,
368
+
id: "li.plonk.paste",
369
+
defs: {
370
+
main: {
371
+
type: "record",
372
+
key: "tid",
373
+
record: {
374
+
type: "object",
375
+
required: ["code", "shortUrl", "lang", "title", "createdAt"],
376
+
properties: {
377
+
code: {
378
+
type: "string",
379
+
minLength: 1,
380
+
maxGraphemes: 65536,
381
+
maxLength: 65536,
382
+
},
383
+
shortUrl: {
384
+
type: "string",
385
+
minLength: 2,
386
+
maxGraphemes: 10,
387
+
maxLength: 10,
388
+
},
389
+
lang: {
390
+
type: "string",
391
+
minLength: 1,
392
+
maxGraphemes: 20,
393
+
maxLength: 20,
394
+
},
395
+
title: {
396
+
type: "string",
397
+
minLength: 1,
398
+
maxGraphemes: 100,
399
+
maxLength: 100,
400
+
},
401
+
createdAt: {
402
+
type: "string",
403
+
format: "datetime",
404
+
},
405
+
},
406
+
},
407
+
},
408
+
},
409
+
},
410
+
AppBskyActorProfile: {
411
+
lexicon: 1,
412
+
id: "app.bsky.actor.profile",
413
+
defs: {
414
+
main: {
415
+
type: "record",
416
+
description: "A declaration of a Bluesky account profile.",
417
+
key: "literal:self",
418
+
record: {
419
+
type: "object",
420
+
properties: {
421
+
displayName: {
422
+
type: "string",
423
+
maxGraphemes: 64,
424
+
maxLength: 640,
425
+
},
426
+
description: {
427
+
type: "string",
428
+
description: "Free-form profile description text.",
429
+
maxGraphemes: 256,
430
+
maxLength: 2560,
431
+
},
432
+
avatar: {
433
+
type: "blob",
434
+
description:
435
+
"Small image to be displayed next to posts from account. AKA, 'profile picture'",
436
+
accept: ["image/png", "image/jpeg"],
437
+
maxSize: 1000000,
438
+
},
439
+
banner: {
440
+
type: "blob",
441
+
description:
442
+
"Larger horizontal image to display behind profile view.",
443
+
accept: ["image/png", "image/jpeg"],
444
+
maxSize: 1000000,
445
+
},
446
+
labels: {
447
+
type: "union",
448
+
description:
449
+
"Self-label values, specific to the Bluesky application, on the overall account.",
450
+
refs: ["lex:com.atproto.label.defs#selfLabels"],
451
+
},
452
+
joinedViaStarterPack: {
453
+
type: "ref",
454
+
ref: "lex:com.atproto.repo.strongRef",
455
+
},
456
+
createdAt: {
457
+
type: "string",
458
+
format: "datetime",
459
+
},
460
+
},
461
+
},
462
+
},
463
+
},
464
+
},
465
+
ComAtprotoRepoStrongRef: {
466
+
lexicon: 1,
467
+
id: "com.atproto.repo.strongRef",
468
+
description: "A URI with a content-hash fingerprint.",
469
+
defs: {
470
+
main: {
471
+
type: "object",
472
+
required: ["uri", "cid"],
473
+
properties: {
474
+
uri: {
475
+
type: "string",
476
+
format: "at-uri",
477
+
},
478
+
cid: {
479
+
type: "string",
480
+
format: "cid",
481
+
},
482
+
},
483
+
},
484
+
},
485
+
},
486
+
};
487
+
export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[];
488
+
export const lexicons: Lexicons = new Lexicons(schemas);
489
489
export const ids = {
490
-
OvhPlonkComment: 'ovh.plonk.comment',
491
-
ComAtprotoLabelDefs: 'com.atproto.label.defs',
492
-
ComAtprotoRepoGetRecord: 'com.atproto.repo.getRecord',
493
-
ComAtprotoRepoListRecords: 'com.atproto.repo.listRecords',
494
-
OvhPlonkPaste: 'ovh.plonk.paste',
495
-
AppBskyActorProfile: 'app.bsky.actor.profile',
496
-
ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef',
497
-
}
490
+
LiPlonkComment: "li.plonk.comment",
491
+
ComAtprotoLabelDefs: "com.atproto.label.defs",
492
+
ComAtprotoRepoGetRecord: "com.atproto.repo.getRecord",
493
+
ComAtprotoRepoListRecords: "com.atproto.repo.listRecords",
494
+
LiPlonkPaste: "li.plonk.paste",
495
+
AppBskyActorProfile: "app.bsky.actor.profile",
496
+
ComAtprotoRepoStrongRef: "com.atproto.repo.strongRef",
497
+
};
+26
-26
src/lexicons/types/app/bsky/actor/profile.ts
+26
-26
src/lexicons/types/app/bsky/actor/profile.ts
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
-
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
-
import { lexicons } from '../../../../lexicons'
6
-
import { isObj, hasProp } from '../../../../util'
7
-
import { CID } from 'multiformats/cid'
8
-
import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs'
9
-
import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef'
4
+
import { ValidationResult, BlobRef } from "@atproto/lexicon";
5
+
import { lexicons } from "../../../../lexicons";
6
+
import { isObj, hasProp } from "../../../../util";
7
+
import { CID } from "multiformats/cid";
8
+
import * as ComAtprotoLabelDefs from "../../../com/atproto/label/defs";
9
+
import * as ComAtprotoRepoStrongRef from "../../../com/atproto/repo/strongRef";
10
10
11
11
export interface Record {
12
-
displayName?: string
13
-
/** Free-form profile description text. */
14
-
description?: string
15
-
/** Small image to be displayed next to posts from account. AKA, 'profile picture' */
16
-
avatar?: BlobRef
17
-
/** Larger horizontal image to display behind profile view. */
18
-
banner?: BlobRef
19
-
labels?:
20
-
| ComAtprotoLabelDefs.SelfLabels
21
-
| { $type: string; [k: string]: unknown }
22
-
joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main
23
-
createdAt?: string
24
-
[k: string]: unknown
12
+
displayName?: string;
13
+
/** Free-form profile description text. */
14
+
description?: string;
15
+
/** Small image to be displayed next to posts from account. AKA, 'profile picture' */
16
+
avatar?: BlobRef;
17
+
/** Larger horizontal image to display behind profile view. */
18
+
banner?: BlobRef;
19
+
labels?:
20
+
| ComAtprotoLabelDefs.SelfLabels
21
+
| { $type: string; [k: string]: unknown };
22
+
joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main;
23
+
createdAt?: string;
24
+
[k: string]: unknown;
25
25
}
26
26
27
27
export function isRecord(v: unknown): v is Record {
28
-
return (
29
-
isObj(v) &&
30
-
hasProp(v, '$type') &&
31
-
(v.$type === 'app.bsky.actor.profile#main' ||
32
-
v.$type === 'app.bsky.actor.profile')
33
-
)
28
+
return (
29
+
isObj(v) &&
30
+
hasProp(v, "$type") &&
31
+
(v.$type === "app.bsky.actor.profile#main" ||
32
+
v.$type === "app.bsky.actor.profile")
33
+
);
34
34
}
35
35
36
36
export function validateRecord(v: unknown): ValidationResult {
37
-
return lexicons.validate('app.bsky.actor.profile#main', v)
37
+
return lexicons.validate("app.bsky.actor.profile#main", v);
38
38
}
+94
-94
src/lexicons/types/com/atproto/label/defs.ts
+94
-94
src/lexicons/types/com/atproto/label/defs.ts
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
-
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
-
import { lexicons } from '../../../../lexicons'
6
-
import { isObj, hasProp } from '../../../../util'
7
-
import { CID } from 'multiformats/cid'
4
+
import { ValidationResult, BlobRef } from "@atproto/lexicon";
5
+
import { lexicons } from "../../../../lexicons";
6
+
import { isObj, hasProp } from "../../../../util";
7
+
import { CID } from "multiformats/cid";
8
8
9
9
/** Metadata tag on an atproto resource (eg, repo or record). */
10
10
export interface Label {
11
-
/** The AT Protocol version of the label object. */
12
-
ver?: number
13
-
/** DID of the actor who created this label. */
14
-
src: string
15
-
/** AT URI of the record, repository (account), or other resource that this label applies to. */
16
-
uri: string
17
-
/** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */
18
-
cid?: string
19
-
/** The short string name of the value or type of this label. */
20
-
val: string
21
-
/** If true, this is a negation label, overwriting a previous label. */
22
-
neg?: boolean
23
-
/** Timestamp when this label was created. */
24
-
cts: string
25
-
/** Timestamp at which this label expires (no longer applies). */
26
-
exp?: string
27
-
/** Signature of dag-cbor encoded label. */
28
-
sig?: Uint8Array
29
-
[k: string]: unknown
11
+
/** The AT Protocol version of the label object. */
12
+
ver?: number;
13
+
/** DID of the actor who created this label. */
14
+
src: string;
15
+
/** AT URI of the record, repository (account), or other resource that this label applies to. */
16
+
uri: string;
17
+
/** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */
18
+
cid?: string;
19
+
/** The short string name of the value or type of this label. */
20
+
val: string;
21
+
/** If true, this is a negation label, overwriting a previous label. */
22
+
neg?: boolean;
23
+
/** Timestamp when this label was created. */
24
+
cts: string;
25
+
/** Timestamp at which this label expires (no longer applies). */
26
+
exp?: string;
27
+
/** Signature of dag-cbor encoded label. */
28
+
sig?: Uint8Array;
29
+
[k: string]: unknown;
30
30
}
31
31
32
32
export function isLabel(v: unknown): v is Label {
33
-
return (
34
-
isObj(v) &&
35
-
hasProp(v, '$type') &&
36
-
v.$type === 'com.atproto.label.defs#label'
37
-
)
33
+
return (
34
+
isObj(v) &&
35
+
hasProp(v, "$type") &&
36
+
v.$type === "com.atproto.label.defs#label"
37
+
);
38
38
}
39
39
40
40
export function validateLabel(v: unknown): ValidationResult {
41
-
return lexicons.validate('com.atproto.label.defs#label', v)
41
+
return lexicons.validate("com.atproto.label.defs#label", v);
42
42
}
43
43
44
44
/** Metadata tags on an atproto record, published by the author within the record. */
45
45
export interface SelfLabels {
46
-
values: SelfLabel[]
47
-
[k: string]: unknown
46
+
values: SelfLabel[];
47
+
[k: string]: unknown;
48
48
}
49
49
50
50
export function isSelfLabels(v: unknown): v is SelfLabels {
51
-
return (
52
-
isObj(v) &&
53
-
hasProp(v, '$type') &&
54
-
v.$type === 'com.atproto.label.defs#selfLabels'
55
-
)
51
+
return (
52
+
isObj(v) &&
53
+
hasProp(v, "$type") &&
54
+
v.$type === "com.atproto.label.defs#selfLabels"
55
+
);
56
56
}
57
57
58
58
export function validateSelfLabels(v: unknown): ValidationResult {
59
-
return lexicons.validate('com.atproto.label.defs#selfLabels', v)
59
+
return lexicons.validate("com.atproto.label.defs#selfLabels", v);
60
60
}
61
61
62
62
/** Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel. */
63
63
export interface SelfLabel {
64
-
/** The short string name of the value or type of this label. */
65
-
val: string
66
-
[k: string]: unknown
64
+
/** The short string name of the value or type of this label. */
65
+
val: string;
66
+
[k: string]: unknown;
67
67
}
68
68
69
69
export function isSelfLabel(v: unknown): v is SelfLabel {
70
-
return (
71
-
isObj(v) &&
72
-
hasProp(v, '$type') &&
73
-
v.$type === 'com.atproto.label.defs#selfLabel'
74
-
)
70
+
return (
71
+
isObj(v) &&
72
+
hasProp(v, "$type") &&
73
+
v.$type === "com.atproto.label.defs#selfLabel"
74
+
);
75
75
}
76
76
77
77
export function validateSelfLabel(v: unknown): ValidationResult {
78
-
return lexicons.validate('com.atproto.label.defs#selfLabel', v)
78
+
return lexicons.validate("com.atproto.label.defs#selfLabel", v);
79
79
}
80
80
81
81
/** Declares a label value and its expected interpretations and behaviors. */
82
82
export interface LabelValueDefinition {
83
-
/** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */
84
-
identifier: string
85
-
/** How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing. */
86
-
severity: 'inform' | 'alert' | 'none' | (string & {})
87
-
/** What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing. */
88
-
blurs: 'content' | 'media' | 'none' | (string & {})
89
-
/** The default setting for this label. */
90
-
defaultSetting: 'ignore' | 'warn' | 'hide' | (string & {})
91
-
/** Does the user need to have adult content enabled in order to configure this label? */
92
-
adultOnly?: boolean
93
-
locales: LabelValueDefinitionStrings[]
94
-
[k: string]: unknown
83
+
/** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */
84
+
identifier: string;
85
+
/** How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing. */
86
+
severity: "inform" | "alert" | "none" | (string & {});
87
+
/** What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing. */
88
+
blurs: "content" | "media" | "none" | (string & {});
89
+
/** The default setting for this label. */
90
+
defaultSetting: "ignore" | "warn" | "hide" | (string & {});
91
+
/** Does the user need to have adult content enabled in order to configure this label? */
92
+
adultOnly?: boolean;
93
+
locales: LabelValueDefinitionStrings[];
94
+
[k: string]: unknown;
95
95
}
96
96
97
97
export function isLabelValueDefinition(v: unknown): v is LabelValueDefinition {
98
-
return (
99
-
isObj(v) &&
100
-
hasProp(v, '$type') &&
101
-
v.$type === 'com.atproto.label.defs#labelValueDefinition'
102
-
)
98
+
return (
99
+
isObj(v) &&
100
+
hasProp(v, "$type") &&
101
+
v.$type === "com.atproto.label.defs#labelValueDefinition"
102
+
);
103
103
}
104
104
105
105
export function validateLabelValueDefinition(v: unknown): ValidationResult {
106
-
return lexicons.validate('com.atproto.label.defs#labelValueDefinition', v)
106
+
return lexicons.validate("com.atproto.label.defs#labelValueDefinition", v);
107
107
}
108
108
109
109
/** Strings which describe the label in the UI, localized into a specific language. */
110
110
export interface LabelValueDefinitionStrings {
111
-
/** The code of the language these strings are written in. */
112
-
lang: string
113
-
/** A short human-readable name for the label. */
114
-
name: string
115
-
/** A longer description of what the label means and why it might be applied. */
116
-
description: string
117
-
[k: string]: unknown
111
+
/** The code of the language these strings are written in. */
112
+
lang: string;
113
+
/** A short human-readable name for the label. */
114
+
name: string;
115
+
/** A longer description of what the label means and why it might be applied. */
116
+
description: string;
117
+
[k: string]: unknown;
118
118
}
119
119
120
120
export function isLabelValueDefinitionStrings(
121
-
v: unknown,
121
+
v: unknown,
122
122
): v is LabelValueDefinitionStrings {
123
-
return (
124
-
isObj(v) &&
125
-
hasProp(v, '$type') &&
126
-
v.$type === 'com.atproto.label.defs#labelValueDefinitionStrings'
127
-
)
123
+
return (
124
+
isObj(v) &&
125
+
hasProp(v, "$type") &&
126
+
v.$type === "com.atproto.label.defs#labelValueDefinitionStrings"
127
+
);
128
128
}
129
129
130
130
export function validateLabelValueDefinitionStrings(
131
-
v: unknown,
131
+
v: unknown,
132
132
): ValidationResult {
133
-
return lexicons.validate(
134
-
'com.atproto.label.defs#labelValueDefinitionStrings',
135
-
v,
136
-
)
133
+
return lexicons.validate(
134
+
"com.atproto.label.defs#labelValueDefinitionStrings",
135
+
v,
136
+
);
137
137
}
138
138
139
139
export type LabelValue =
140
-
| '!hide'
141
-
| '!no-promote'
142
-
| '!warn'
143
-
| '!no-unauthenticated'
144
-
| 'dmca-violation'
145
-
| 'doxxing'
146
-
| 'porn'
147
-
| 'sexual'
148
-
| 'nudity'
149
-
| 'nsfl'
150
-
| 'gore'
151
-
| (string & {})
140
+
| "!hide"
141
+
| "!no-promote"
142
+
| "!warn"
143
+
| "!no-unauthenticated"
144
+
| "dmca-violation"
145
+
| "doxxing"
146
+
| "porn"
147
+
| "sexual"
148
+
| "nudity"
149
+
| "nsfl"
150
+
| "gore"
151
+
| (string & {});
+35
-35
src/lexicons/types/com/atproto/repo/getRecord.ts
+35
-35
src/lexicons/types/com/atproto/repo/getRecord.ts
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
-
import express from 'express'
5
-
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
-
import { lexicons } from '../../../../lexicons'
7
-
import { isObj, hasProp } from '../../../../util'
8
-
import { CID } from 'multiformats/cid'
9
-
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
4
+
import express from "express";
5
+
import { ValidationResult, BlobRef } from "@atproto/lexicon";
6
+
import { lexicons } from "../../../../lexicons";
7
+
import { isObj, hasProp } from "../../../../util";
8
+
import { CID } from "multiformats/cid";
9
+
import { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server";
10
10
11
11
export interface QueryParams {
12
-
/** The handle or DID of the repo. */
13
-
repo: string
14
-
/** The NSID of the record collection. */
15
-
collection: string
16
-
/** The Record Key. */
17
-
rkey: string
18
-
/** The CID of the version of the record. If not specified, then return the most recent version. */
19
-
cid?: string
12
+
/** The handle or DID of the repo. */
13
+
repo: string;
14
+
/** The NSID of the record collection. */
15
+
collection: string;
16
+
/** The Record Key. */
17
+
rkey: string;
18
+
/** The CID of the version of the record. If not specified, then return the most recent version. */
19
+
cid?: string;
20
20
}
21
21
22
-
export type InputSchema = undefined
22
+
export type InputSchema = undefined;
23
23
24
24
export interface OutputSchema {
25
-
uri: string
26
-
cid?: string
27
-
value: {}
28
-
[k: string]: unknown
25
+
uri: string;
26
+
cid?: string;
27
+
value: {};
28
+
[k: string]: unknown;
29
29
}
30
30
31
-
export type HandlerInput = undefined
31
+
export type HandlerInput = undefined;
32
32
33
33
export interface HandlerSuccess {
34
-
encoding: 'application/json'
35
-
body: OutputSchema
36
-
headers?: { [key: string]: string }
34
+
encoding: "application/json";
35
+
body: OutputSchema;
36
+
headers?: { [key: string]: string };
37
37
}
38
38
39
39
export interface HandlerError {
40
-
status: number
41
-
message?: string
42
-
error?: 'RecordNotFound'
40
+
status: number;
41
+
message?: string;
42
+
error?: "RecordNotFound";
43
43
}
44
44
45
-
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
45
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough;
46
46
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
47
-
auth: HA
48
-
params: QueryParams
49
-
input: HandlerInput
50
-
req: express.Request
51
-
res: express.Response
52
-
}
47
+
auth: HA;
48
+
params: QueryParams;
49
+
input: HandlerInput;
50
+
req: express.Request;
51
+
res: express.Response;
52
+
};
53
53
export type Handler<HA extends HandlerAuth = never> = (
54
-
ctx: HandlerReqCtx<HA>,
55
-
) => Promise<HandlerOutput> | HandlerOutput
54
+
ctx: HandlerReqCtx<HA>,
55
+
) => Promise<HandlerOutput> | HandlerOutput;
+48
-48
src/lexicons/types/com/atproto/repo/listRecords.ts
+48
-48
src/lexicons/types/com/atproto/repo/listRecords.ts
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
-
import express from 'express'
5
-
import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
-
import { lexicons } from '../../../../lexicons'
7
-
import { isObj, hasProp } from '../../../../util'
8
-
import { CID } from 'multiformats/cid'
9
-
import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
4
+
import express from "express";
5
+
import { ValidationResult, BlobRef } from "@atproto/lexicon";
6
+
import { lexicons } from "../../../../lexicons";
7
+
import { isObj, hasProp } from "../../../../util";
8
+
import { CID } from "multiformats/cid";
9
+
import { HandlerAuth, HandlerPipeThrough } from "@atproto/xrpc-server";
10
10
11
11
export interface QueryParams {
12
-
/** The handle or DID of the repo. */
13
-
repo: string
14
-
/** The NSID of the record type. */
15
-
collection: string
16
-
/** The number of records to return. */
17
-
limit: number
18
-
cursor?: string
19
-
/** DEPRECATED: The lowest sort-ordered rkey to start from (exclusive) */
20
-
rkeyStart?: string
21
-
/** DEPRECATED: The highest sort-ordered rkey to stop at (exclusive) */
22
-
rkeyEnd?: string
23
-
/** Flag to reverse the order of the returned records. */
24
-
reverse?: boolean
12
+
/** The handle or DID of the repo. */
13
+
repo: string;
14
+
/** The NSID of the record type. */
15
+
collection: string;
16
+
/** The number of records to return. */
17
+
limit: number;
18
+
cursor?: string;
19
+
/** DEPRECATED: The lowest sort-ordered rkey to start from (exclusive) */
20
+
rkeyStart?: string;
21
+
/** DEPRECATED: The highest sort-ordered rkey to stop at (exclusive) */
22
+
rkeyEnd?: string;
23
+
/** Flag to reverse the order of the returned records. */
24
+
reverse?: boolean;
25
25
}
26
26
27
-
export type InputSchema = undefined
27
+
export type InputSchema = undefined;
28
28
29
29
export interface OutputSchema {
30
-
cursor?: string
31
-
records: Record[]
32
-
[k: string]: unknown
30
+
cursor?: string;
31
+
records: Record[];
32
+
[k: string]: unknown;
33
33
}
34
34
35
-
export type HandlerInput = undefined
35
+
export type HandlerInput = undefined;
36
36
37
37
export interface HandlerSuccess {
38
-
encoding: 'application/json'
39
-
body: OutputSchema
40
-
headers?: { [key: string]: string }
38
+
encoding: "application/json";
39
+
body: OutputSchema;
40
+
headers?: { [key: string]: string };
41
41
}
42
42
43
43
export interface HandlerError {
44
-
status: number
45
-
message?: string
44
+
status: number;
45
+
message?: string;
46
46
}
47
47
48
-
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
48
+
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough;
49
49
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
50
-
auth: HA
51
-
params: QueryParams
52
-
input: HandlerInput
53
-
req: express.Request
54
-
res: express.Response
55
-
}
50
+
auth: HA;
51
+
params: QueryParams;
52
+
input: HandlerInput;
53
+
req: express.Request;
54
+
res: express.Response;
55
+
};
56
56
export type Handler<HA extends HandlerAuth = never> = (
57
-
ctx: HandlerReqCtx<HA>,
58
-
) => Promise<HandlerOutput> | HandlerOutput
57
+
ctx: HandlerReqCtx<HA>,
58
+
) => Promise<HandlerOutput> | HandlerOutput;
59
59
60
60
export interface Record {
61
-
uri: string
62
-
cid: string
63
-
value: {}
64
-
[k: string]: unknown
61
+
uri: string;
62
+
cid: string;
63
+
value: {};
64
+
[k: string]: unknown;
65
65
}
66
66
67
67
export function isRecord(v: unknown): v is Record {
68
-
return (
69
-
isObj(v) &&
70
-
hasProp(v, '$type') &&
71
-
v.$type === 'com.atproto.repo.listRecords#record'
72
-
)
68
+
return (
69
+
isObj(v) &&
70
+
hasProp(v, "$type") &&
71
+
v.$type === "com.atproto.repo.listRecords#record"
72
+
);
73
73
}
74
74
75
75
export function validateRecord(v: unknown): ValidationResult {
76
-
return lexicons.validate('com.atproto.repo.listRecords#record', v)
76
+
return lexicons.validate("com.atproto.repo.listRecords#record", v);
77
77
}
+14
-14
src/lexicons/types/com/atproto/repo/strongRef.ts
+14
-14
src/lexicons/types/com/atproto/repo/strongRef.ts
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
-
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
-
import { lexicons } from '../../../../lexicons'
6
-
import { isObj, hasProp } from '../../../../util'
7
-
import { CID } from 'multiformats/cid'
4
+
import { ValidationResult, BlobRef } from "@atproto/lexicon";
5
+
import { lexicons } from "../../../../lexicons";
6
+
import { isObj, hasProp } from "../../../../util";
7
+
import { CID } from "multiformats/cid";
8
8
9
9
export interface Main {
10
-
uri: string
11
-
cid: string
12
-
[k: string]: unknown
10
+
uri: string;
11
+
cid: string;
12
+
[k: string]: unknown;
13
13
}
14
14
15
15
export function isMain(v: unknown): v is Main {
16
-
return (
17
-
isObj(v) &&
18
-
hasProp(v, '$type') &&
19
-
(v.$type === 'com.atproto.repo.strongRef#main' ||
20
-
v.$type === 'com.atproto.repo.strongRef')
21
-
)
16
+
return (
17
+
isObj(v) &&
18
+
hasProp(v, "$type") &&
19
+
(v.$type === "com.atproto.repo.strongRef#main" ||
20
+
v.$type === "com.atproto.repo.strongRef")
21
+
);
22
22
}
23
23
24
24
export function validateMain(v: unknown): ValidationResult {
25
-
return lexicons.validate('com.atproto.repo.strongRef#main', v)
25
+
return lexicons.validate("com.atproto.repo.strongRef#main", v);
26
26
}
+29
src/lexicons/types/li/plonk/comment.ts
+29
src/lexicons/types/li/plonk/comment.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from "@atproto/lexicon";
5
+
import { lexicons } from "../../../lexicons";
6
+
import { isObj, hasProp } from "../../../util";
7
+
import { CID } from "multiformats/cid";
8
+
import * as ComAtprotoRepoStrongRef from "../../com/atproto/repo/strongRef";
9
+
10
+
export interface Record {
11
+
/** comment body */
12
+
content: string;
13
+
/** comment creation timestamp */
14
+
createdAt: string;
15
+
post: ComAtprotoRepoStrongRef.Main;
16
+
[k: string]: unknown;
17
+
}
18
+
19
+
export function isRecord(v: unknown): v is Record {
20
+
return (
21
+
isObj(v) &&
22
+
hasProp(v, "$type") &&
23
+
(v.$type === "li.plonk.comment#main" || v.$type === "li.plonk.comment")
24
+
);
25
+
}
26
+
27
+
export function validateRecord(v: unknown): ValidationResult {
28
+
return lexicons.validate("li.plonk.comment#main", v);
29
+
}
+28
src/lexicons/types/li/plonk/paste.ts
+28
src/lexicons/types/li/plonk/paste.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from "@atproto/lexicon";
5
+
import { lexicons } from "../../../lexicons";
6
+
import { isObj, hasProp } from "../../../util";
7
+
import { CID } from "multiformats/cid";
8
+
9
+
export interface Record {
10
+
code: string;
11
+
shortUrl: string;
12
+
lang: string;
13
+
title: string;
14
+
createdAt: string;
15
+
[k: string]: unknown;
16
+
}
17
+
18
+
export function isRecord(v: unknown): v is Record {
19
+
return (
20
+
isObj(v) &&
21
+
hasProp(v, "$type") &&
22
+
(v.$type === "li.plonk.paste#main" || v.$type === "li.plonk.paste")
23
+
);
24
+
}
25
+
26
+
export function validateRecord(v: unknown): ValidationResult {
27
+
return lexicons.validate("li.plonk.paste#main", v);
28
+
}
-29
src/lexicons/types/ovh/plonk/comment.ts
-29
src/lexicons/types/ovh/plonk/comment.ts
···
1
-
/**
2
-
* GENERATED CODE - DO NOT MODIFY
3
-
*/
4
-
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
-
import { lexicons } from '../../../lexicons'
6
-
import { isObj, hasProp } from '../../../util'
7
-
import { CID } from 'multiformats/cid'
8
-
import * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef'
9
-
10
-
export interface Record {
11
-
/** comment body */
12
-
content: string
13
-
/** comment creation timestamp */
14
-
createdAt: string
15
-
post: ComAtprotoRepoStrongRef.Main
16
-
[k: string]: unknown
17
-
}
18
-
19
-
export function isRecord(v: unknown): v is Record {
20
-
return (
21
-
isObj(v) &&
22
-
hasProp(v, '$type') &&
23
-
(v.$type === 'ovh.plonk.comment#main' || v.$type === 'ovh.plonk.comment')
24
-
)
25
-
}
26
-
27
-
export function validateRecord(v: unknown): ValidationResult {
28
-
return lexicons.validate('ovh.plonk.comment#main', v)
29
-
}
-28
src/lexicons/types/ovh/plonk/paste.ts
-28
src/lexicons/types/ovh/plonk/paste.ts
···
1
-
/**
2
-
* GENERATED CODE - DO NOT MODIFY
3
-
*/
4
-
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
-
import { lexicons } from '../../../lexicons'
6
-
import { isObj, hasProp } from '../../../util'
7
-
import { CID } from 'multiformats/cid'
8
-
9
-
export interface Record {
10
-
code: string
11
-
shortUrl: string
12
-
lang: string
13
-
title: string
14
-
createdAt: string
15
-
[k: string]: unknown
16
-
}
17
-
18
-
export function isRecord(v: unknown): v is Record {
19
-
return (
20
-
isObj(v) &&
21
-
hasProp(v, '$type') &&
22
-
(v.$type === 'ovh.plonk.paste#main' || v.$type === 'ovh.plonk.paste')
23
-
)
24
-
}
25
-
26
-
export function validateRecord(v: unknown): ValidationResult {
27
-
return lexicons.validate('ovh.plonk.paste#main', v)
28
-
}
+4
-4
src/lexicons/util.ts
+4
-4
src/lexicons/util.ts
···
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
export function isObj(v: unknown): v is Record<string, unknown> {
5
-
return typeof v === 'object' && v !== null
5
+
return typeof v === "object" && v !== null;
6
6
}
7
7
8
8
export function hasProp<K extends PropertyKey>(
9
-
data: object,
10
-
prop: K,
9
+
data: object,
10
+
prop: K,
11
11
): data is Record<K, unknown> {
12
-
return prop in data
12
+
return prop in data;
13
13
}
+48
src/lib/highlight.ts
+48
src/lib/highlight.ts
···
1
+
import { createHighlighter, HighlighterGeneric, BundledLanguage, BundledTheme } from 'shiki'
2
+
import type { Element } from 'hast'
3
+
4
+
let highlighter: HighlighterGeneric<BundledLanguage, BundledTheme> | null = null
5
+
6
+
export async function getHighlighterInstance() {
7
+
if (!highlighter) {
8
+
highlighter = await createHighlighter({
9
+
themes: ['catppuccin-latte', 'catppuccin-mocha'],
10
+
langs: [
11
+
'javascript', 'typescript', 'python', 'java', 'rust',
12
+
'go', 'c', 'cpp', 'csharp', 'php', 'ruby', 'cobol',
13
+
'plaintext', 'txt', 'bash', 'nix'
14
+
]
15
+
})
16
+
}
17
+
return highlighter
18
+
}
19
+
20
+
export async function highlightCode(code: string, lang: string) {
21
+
const highlighter = await getHighlighterInstance()
22
+
23
+
try {
24
+
return highlighter.codeToHtml(code, {
25
+
lang: lang,
26
+
themes: {
27
+
light: 'catppuccin-latte',
28
+
dark: 'catppuccin-mocha'
29
+
},
30
+
transformers: [
31
+
{
32
+
line(node: Element, line: number) {
33
+
node.properties['code-line'] = line
34
+
}
35
+
}
36
+
]
37
+
})
38
+
} catch (error) {
39
+
console.warn(`Language "${lang}" not supported by Shiki, falling back to plaintext`)
40
+
return highlighter.codeToHtml(code, {
41
+
lang: 'txt',
42
+
themes: {
43
+
light: 'catppuccin-latte',
44
+
dark: 'catppuccin-mocha'
45
+
}
46
+
})
47
+
}
48
+
}
+6
-6
src/lib.ts
+6
-6
src/lib.ts
···
4
4
dotenv.config();
5
5
6
6
export const env = cleanEnv(process.env, {
7
-
NODE_ENV: str({
7
+
PLONK_NODE_ENV: str({
8
8
devDefault: testOnly("test"),
9
9
choices: ["development", "production", "test"],
10
10
}),
11
-
HOST: host({ devDefault: testOnly("localhost") }),
12
-
PORT: port({ devDefault: testOnly(3000) }),
13
-
PUBLIC_URL: str({}),
14
-
DB_PATH: str({ devDefault: ":memory:" }),
15
-
COOKIE_SECRET: str({ devDefault: "00000000000000000000000000000001" }),
11
+
PLONK_HOST: host({ devDefault: testOnly("localhost") }),
12
+
PLONK_PORT: port({ devDefault: testOnly(3000) }),
13
+
PLONK_PUBLIC_URL: str({}),
14
+
PLONK_DB_PATH: str({ devDefault: ":memory:" }),
15
+
PLONK_COOKIE_SECRET: str({ devDefault: "00000000000000000000000000000000" }),
16
16
});
+1
-1
src/mixins/head.pug
+1
-1
src/mixins/head.pug
···
2
2
head
3
3
meta(name="viewport" content="width=device-width, initial-scale=1.0")
4
4
meta(charset='UTF-8')
5
-
title #{title}
5
+
title #{title} ยท plonk.li
6
6
link(rel="stylesheet", href="/styles.css")
7
7
link(rel="preconnect" href="https://rsms.me/")
8
8
link(rel="stylesheet" href="https://rsms.me/inter/inter.css")
+15
src/mixins/header.pug
+15
src/mixins/header.pug
···
1
+
mixin header(ownDid, didHandleMap)
2
+
div.header
3
+
div.left-side
4
+
div.right-side
5
+
p
6
+
a(href="/") home
7
+
| ยท
8
+
if ownDid
9
+
a(href=`/u/${encodeURIComponent(ownDid)}`) my plonks
10
+
| ยท
11
+
a(href="/logout") logout
12
+
else
13
+
a(href="/login") login
14
+
| to get plonkin'
15
+
+3
-2
src/mixins/post.pug
+3
-2
src/mixins/post.pug
···
1
1
mixin post(paste, handle, did)
2
2
div.post
3
3
p
4
-
a(href=`/p/${paste.shortUrl}`)
4
+
a(href=`/p/${paste.shortUrl}`).post-link
5
5
| #{paste.title}
6
6
p.post-info
7
7
| by
···
13
13
| #{paste.lang}
14
14
| ยท
15
15
| #{paste.code.split('\n').length} loc
16
-
16
+
| ยท
17
+
a(href=`/p/${paste.shortUrl}/#comments`) #{paste.commentCount} #{pluralize(paste.commentCount, 'comment')}
+4
src/mixins/utils.pug
+4
src/mixins/utils.pug
+141
-5
src/public/styles.css
+141
-5
src/public/styles.css
···
1
-
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap');
1
+
@font-face {
2
+
font-family: 'NerdIosevka';
3
+
src: url('../assets/NerdIosevka-Regular.woff2') format('woff2');
4
+
font-weight: normal;
5
+
font-style: monospace;
6
+
}
7
+
8
+
@media (prefers-color-scheme: dark) {
9
+
.shiki,
10
+
.shiki span {
11
+
color: var(--shiki-dark) !important;
12
+
background-color: var(--shiki-dark-bg) !important;
13
+
font-style: var(--shiki-dark-font-style) !important;
14
+
font-weight: var(--shiki-dark-font-weight) !important;
15
+
text-decoration: var(--shiki-dark-text-decoration) !important;
16
+
}
17
+
}
2
18
3
19
:root {
4
20
/* Light mode colors */
···
28
44
}
29
45
30
46
* {
31
-
font-family: 'IBM Plex Mono', monospace;
47
+
font-family: 'NerdIosevka', monospace;
48
+
font-size: 0.9rem;
32
49
}
33
50
34
51
body {
···
52
69
}
53
70
54
71
pre {
55
-
background-color: var(--bg-color-muted);
56
72
padding: 1rem;
57
73
overflow-x: auto;
58
74
}
59
75
76
+
.comment-body {
77
+
background-color: var(--bg-color);
78
+
padding: 0;
79
+
margin-top: 0.1rem;
80
+
}
81
+
82
+
.comment-info {
83
+
margin-bottom: 0;
84
+
}
85
+
60
86
input, textarea, select, button {
61
87
border: none;
62
88
padding: 1rem;
···
93
119
hr {
94
120
border: none;
95
121
border-top: 1px solid var(--bg-color-muted);
96
-
padding: 1rem;
97
122
}
98
123
99
124
.post-form {
···
127
152
align-self: flex-end;
128
153
}
129
154
155
+
.post-link {
156
+
color: var(--text-color);
157
+
text-decoration: none;
158
+
}
159
+
.post-link:hover {
160
+
text-decoration: underline;
161
+
}
162
+
.post-link:visited {
163
+
color: var(--text-color-muted);
164
+
}
165
+
166
+
.post-info {
167
+
margin-top: 0;
168
+
}
169
+
170
+
.post-info, .comment-info {
171
+
color: var(--text-color-muted);
172
+
}
173
+
.post-info a, .comment-info a {
174
+
color: var(--text-color-muted);
175
+
text-decoration: none;
176
+
}
177
+
.post-info a:visited, .comment-info a:visited {
178
+
color: var(--text-color-muted);
179
+
}
180
+
.post-info a:hover, .comment-info a:hover {
181
+
text-decoration: underline;
182
+
}
183
+
130
184
.timeline, .comments {
131
185
display: flex;
132
186
flex-direction: column;
133
187
gap: 1rem;
188
+
padding-bottom: 1rem;
134
189
}
135
190
136
191
.login-input-title {
···
141
196
flex: 1
142
197
}
143
198
144
-
.header {
199
+
.header, .footer {
145
200
display: flex;
146
201
flex-direction: row;
147
202
justify-content: space-between;
148
203
align-items: center;
149
204
}
205
+
206
+
select {
207
+
-webkit-appearance: none;
208
+
-moz-appearance: none;
209
+
text-indent: 1px;
210
+
text-overflow: '';
211
+
}
212
+
213
+
.code-line {
214
+
display: flex;
215
+
}
216
+
217
+
.code-line-num {
218
+
white-space: pre;
219
+
-webkit-user-select: none;
220
+
user-select: none;
221
+
margin-right: 0.4em;
222
+
padding: 0 0.4em 0 0.4em;
223
+
color: var(--text-color-muted);
224
+
text-align: right;
225
+
}
226
+
227
+
.code-line-content {
228
+
color: var(--text-color);
229
+
}
230
+
231
+
.header, .footer {
232
+
color: var(--text-color);
233
+
}
234
+
235
+
.header a, .header a:visited,
236
+
.footer a, .footer a:visited {
237
+
color: var(--link-color);
238
+
text-decoration: none;
239
+
}
240
+
241
+
.header a:hover,
242
+
.footer a:hover {
243
+
text-decoration: underline;
244
+
}
245
+
246
+
.highlighted-code {
247
+
margin: 1rem 0;
248
+
counter-reset: step;
249
+
counter-increment: step calc(var(--start, 1) - 1);
250
+
}
251
+
252
+
.highlighted-code pre {
253
+
padding: 1rem;
254
+
overflow-x: auto;
255
+
border-radius: 4px;
256
+
}
257
+
258
+
code .line::before {
259
+
content: counter(step);
260
+
counter-increment: step;
261
+
width: 1rem;
262
+
margin-right: 1.5rem;
263
+
display: inline-block;
264
+
text-align: right;
265
+
color: rgba(115,138,148,.4)
266
+
}
267
+
268
+
#copy-btn {
269
+
background: none;
270
+
border: none;
271
+
padding: 0;
272
+
color: var(--text-color-muted);
273
+
cursor: pointer;
274
+
text-decoration: none;
275
+
font-family: inherit;
276
+
font-size: inherit;
277
+
}
278
+
279
+
#copy-btn:hover {
280
+
text-decoration: underline;
281
+
}
282
+
283
+
#copy-btn:focus {
284
+
outline: 1px solid var(--accent);
285
+
}
+152
-101
src/routes.ts
+152
-101
src/routes.ts
···
6
6
import { isValidHandle, AtUri } from "@atproto/syntax";
7
7
import { IncomingMessage, ServerResponse } from "node:http";
8
8
import { Agent } from "@atproto/api";
9
-
import { getPds, DidResolver } from "@atproto/identity";
10
9
import { TID } from "@atproto/common";
11
-
import { Agent } from "@atproto/api";
12
10
import { newShortUrl } from "#/db";
13
11
14
-
import * as Paste from "#/lexicons/types/ovh/plonk/paste";
15
-
import * as Comment from "#/lexicons/types/ovh/plonk/comment";
16
-
import { ComAtprotoRepoNS } from "#/lexicons";
12
+
import * as Paste from "#/lexicons/types/li/plonk/paste";
13
+
import * as Comment from "#/lexicons/types/li/plonk/comment";
14
+
import { highlightCode } from "#/lib/highlight";
17
15
18
16
type Session = {
19
17
did: string;
20
18
};
21
19
22
-
async function getSessionAgent(
20
+
async function getSession(
23
21
req: IncomingMessage,
24
22
res: ServerResponse<IncomingMessage>,
25
-
ctx: Ctx,
26
23
) {
27
-
const session = await getIronSession<Session>(req, res, {
24
+
return await getIronSession<Session>(req, res, {
28
25
cookieName: "plonk-id",
29
-
password: env.COOKIE_SECRET,
26
+
password: env.PLONK_COOKIE_SECRET,
27
+
cookieOptions: {
28
+
secure: env.PLONK_NODE_ENV === "production",
29
+
},
30
30
});
31
+
}
32
+
33
+
async function getSessionAgent(
34
+
req: IncomingMessage,
35
+
res: ServerResponse<IncomingMessage>,
36
+
ctx: Ctx,
37
+
) {
38
+
const session = await getSession(req, res);
31
39
if (!session.did) return null;
32
40
try {
33
41
const oauthSession = await ctx.oauthClient.restore(session.did);
···
42
50
export const createRouter = (ctx: Ctx) => {
43
51
const router = express.Router();
44
52
45
-
// Static assets
46
-
router.use(
47
-
"/public",
48
-
express.static(path.join(__dirname, "pages", "public")),
49
-
);
50
-
53
+
router.use("/assets", express.static(path.join(__dirname, "assets")));
51
54
// OAuth metadata
52
55
router.get("/client-metadata.json", async (_req, res) => {
53
56
return res.json(ctx.oauthClient.clientMetadata);
···
57
60
const params = new URLSearchParams(req.originalUrl.split("?")[1]);
58
61
try {
59
62
const { session } = await ctx.oauthClient.callback(params);
60
-
const clientSession = await getIronSession<Session>(req, res, {
61
-
cookieName: "plonk-id",
62
-
password: env.COOKIE_SECRET,
63
-
});
64
-
ctx.logger.info(clientSession.did, "client session did");
63
+
const clientSession = await getSession(req, res);
65
64
//assert(!clientSession.did, "session already exists");
66
65
clientSession.did = session.did;
67
66
await clientSession.save();
···
98
97
});
99
98
100
99
router.get("/logout", async (req, res) => {
101
-
const session = await getIronSession<Session>(req, res, {
102
-
cookieName: "plonk-id",
103
-
password: env.COOKIE_SECRET,
104
-
});
100
+
const session = await getSession(req, res);
105
101
session.destroy();
106
102
return res.redirect("/");
107
103
});
···
110
106
const agent = await getSessionAgent(req, res, ctx);
111
107
const pastes = await ctx.db
112
108
.selectFrom("paste")
113
-
.selectAll()
114
-
.orderBy("indexedAt", "desc")
109
+
.leftJoin("comment", "comment.pasteUri", "paste.uri")
110
+
.select([
111
+
"paste.uri",
112
+
"paste.shortUrl",
113
+
"paste.authorDid",
114
+
"paste.code",
115
+
"paste.lang",
116
+
"paste.title",
117
+
"paste.createdAt",
118
+
"paste.indexedAt as pasteIndexedAt",
119
+
ctx.db.fn.count("comment.uri").as("commentCount")
120
+
])
121
+
.groupBy("paste.uri")
122
+
.orderBy("pasteIndexedAt", "desc")
115
123
.limit(25)
116
124
.execute();
117
125
118
126
// Map user DIDs to their domain-name handles
119
127
const didHandleMap = await ctx.resolver.resolveDidsToHandles(
120
-
pastes.map((s) => s.authorDid).concat(agent? [agent.assertDid]:[]),
128
+
pastes.map((s) => s.authorDid).concat(agent ? [agent.assertDid] : []),
121
129
);
122
130
123
131
if (!agent) {
···
133
141
134
142
router.get("/u/:authorDid", async (req, res) => {
135
143
const { authorDid } = req.params;
136
-
const resolver = new DidResolver({});
137
-
const didDocument = await resolver.resolve(authorDid);
138
-
if (!didDocument) {
139
-
return res.status(404);
144
+
const pastes = await ctx.db
145
+
.selectFrom("paste")
146
+
.leftJoin("comment", "comment.pasteUri", "paste.uri")
147
+
.select([
148
+
"paste.uri",
149
+
"paste.shortUrl",
150
+
"paste.authorDid as pasteAuthorDid",
151
+
"paste.code",
152
+
"paste.lang",
153
+
"paste.title",
154
+
"paste.createdAt as pasteCreatedAt",
155
+
"paste.indexedAt as pasteIndexedAt",
156
+
ctx.db.fn.count("comment.uri").as("commentCount")
157
+
])
158
+
.groupBy("paste.uri")
159
+
.where("pasteAuthorDid", "=", authorDid)
160
+
.orderBy("pasteCreatedAt", "desc")
161
+
.execute();
162
+
let didHandleMap: Record<string, string> = {};
163
+
didHandleMap[authorDid] = await ctx.resolver.resolveDidToHandle(authorDid);
164
+
const ownAgent = await getSessionAgent(req, res, ctx);
165
+
if (!ownAgent) {
166
+
return res.render("user", { pastes, authorDid, didHandleMap });
167
+
} else {
168
+
const ownDid = ownAgent.assertDid;
169
+
didHandleMap[ownDid] = await ctx.resolver.resolveDidToHandle(ownDid);
170
+
return res.render("user", { pastes, authorDid, ownDid, didHandleMap });
140
171
}
141
-
const pds = getPds(didDocument);
142
-
if (!pds) {
143
-
return res.status(404);
144
-
}
145
-
const agent = new Agent(pds);
146
-
const response = await agent.com.atproto.repo.listRecords({
147
-
repo: authorDid,
148
-
collection: 'ovh.plonk.paste',
149
-
limit: 99,
150
-
});
151
-
const pastes = response.data.records;
152
-
let didHandleMap = {};
153
-
didHandleMap[authorDid] = await ctx.resolver.resolveDidToHandle(authorDid);
154
-
return res.render("user", { pastes, authorDid, didHandleMap });
155
172
});
156
173
157
174
router.get("/p/:shortUrl", async (req, res) => {
158
175
const { shortUrl } = req.params;
159
176
const ret = await ctx.db
160
177
.selectFrom("paste")
178
+
.leftJoin("comment", "comment.pasteUri", "paste.uri")
179
+
.select([
180
+
"paste.uri as pasteUri",
181
+
"comment.pasteCid as pasteCid",
182
+
"paste.authorDid as pasteAuthorDid",
183
+
"paste.code as pasteCode",
184
+
"paste.lang as pasteLang",
185
+
"paste.title as pasteTitle",
186
+
"paste.createdAt as pasteCreatedAt",
187
+
"comment.uri as commentUri",
188
+
"comment.authorDid as commentAuthorDid",
189
+
"comment.body as commentBody",
190
+
"comment.createdAt as commentCreatedAt",
191
+
])
161
192
.where("shortUrl", "=", shortUrl)
162
-
.select(["authorDid", "uri"])
163
-
.executeTakeFirst();
164
-
if (!ret) {
165
-
return res.status(404);
166
-
}
167
-
var comments = await ctx.db
168
-
.selectFrom("comment")
169
-
.selectAll()
170
-
.where("pasteUri", '=', ret.uri)
171
193
.execute();
172
-
const { authorDid: did, uri } = ret;
173
-
const didHandleMap = await ctx.resolver.resolveDidsToHandles(
174
-
comments.map((c) => c.authorDid).concat([did]),
175
-
)
176
-
const resolver = new DidResolver({});
177
-
const didDocument = await resolver.resolve(did);
178
-
if (!didDocument) {
179
-
return res.status(404);
180
-
}
181
-
const pds = getPds(didDocument);
182
-
if (!pds) {
194
+
if (ret.length === 0) {
183
195
return res.status(404);
184
196
}
185
-
const agent = new Agent(pds);
186
-
const aturi = new AtUri(uri);
187
-
const response = await agent.com.atproto.repo.getRecord({
188
-
repo: aturi.hostname,
189
-
collection: aturi.collection,
190
-
rkey: aturi.rkey
191
-
});
197
+
const {
198
+
pasteAuthorDid,
199
+
pasteUri,
200
+
pasteCode,
201
+
pasteLang,
202
+
pasteTitle,
203
+
pasteCreatedAt,
204
+
} = ret[0];
205
+
let didHandleMap = await ctx.resolver.resolveDidsToHandles(
206
+
[ret[0].pasteAuthorDid].concat(
207
+
ret.flatMap((row) =>
208
+
row.commentAuthorDid ? [row.commentAuthorDid] : [],
209
+
),
210
+
),
211
+
);
212
+
213
+
const highlightedCode = await highlightCode(pasteCode, pasteLang);
192
214
193
-
const paste =
194
-
Paste.isRecord(response.data.value) &&
195
-
Paste.validateRecord(response.data.value).success
196
-
? response.data.value
197
-
: {};
215
+
const paste = {
216
+
uri: pasteUri,
217
+
code: pasteCode,
218
+
highlightedCode,
219
+
title: pasteTitle,
220
+
lang: pasteLang,
221
+
shortUrl,
222
+
createdAt: pasteCreatedAt,
223
+
authorDid: pasteAuthorDid,
224
+
};
198
225
199
-
return res.render("paste", { paste, authorDid: did, uri: response.data.uri, didHandleMap, shortUrl, comments });
226
+
const comments = ret
227
+
.filter((row) => row.commentUri)
228
+
.map((row) => {
229
+
return {
230
+
uri: row.commentUri,
231
+
authorDid: row.commentAuthorDid,
232
+
body: row.commentBody,
233
+
createdAt: row.commentCreatedAt,
234
+
};
235
+
});
236
+
237
+
const ownAgent = await getSessionAgent(req, res, ctx);
238
+
if (!ownAgent) {
239
+
return res.render("paste", {
240
+
paste,
241
+
didHandleMap,
242
+
comments,
243
+
});
244
+
} else {
245
+
const ownDid = ownAgent.assertDid;
246
+
didHandleMap[ownDid] = await ctx.resolver.resolveDidToHandle(ownDid);
247
+
return res.render("paste", {
248
+
paste,
249
+
ownDid,
250
+
didHandleMap,
251
+
comments,
252
+
});
253
+
}
200
254
});
201
255
202
256
router.get("/p/:shortUrl/raw", async (req, res) => {
203
-
res.redirect(`/r/${req.params.shortUrl}`)
257
+
res.redirect(`/r/${req.params.shortUrl}`);
204
258
});
205
259
router.get("/r/:shortUrl", async (req, res) => {
206
260
const { shortUrl } = req.params;
···
220
274
router.get("/reset", async (req, res) => {
221
275
const agent = await getSessionAgent(req, res, ctx);
222
276
if (!agent) {
223
-
return res.redirect('/');
277
+
return res.redirect("/");
224
278
}
225
279
const response = await agent.com.atproto.repo.listRecords({
226
280
repo: agent.assertDid,
227
-
collection: 'ovh.plonk.paste',
281
+
collection: "li.plonk.paste",
228
282
limit: 10,
229
283
});
230
284
const vals = response.data.records;
···
236
290
rkey: aturl.rkey,
237
291
});
238
292
}
239
-
return res.redirect('/');
293
+
return res.redirect("/");
240
294
});
241
295
242
296
router.post("/paste", async (req, res) => {
···
251
305
const rkey = TID.nextStr();
252
306
const shortUrl = await newShortUrl(ctx.db);
253
307
const record = {
254
-
$type: "ovh.plonk.paste",
308
+
$type: "li.plonk.paste",
255
309
code: req.body?.code,
256
310
lang: req.body?.lang,
257
311
shortUrl,
···
270
324
try {
271
325
const res = await agent.com.atproto.repo.putRecord({
272
326
repo: agent.assertDid,
273
-
collection: "ovh.plonk.paste",
327
+
collection: "li.plonk.paste",
274
328
rkey,
275
329
record,
276
330
validate: false,
···
299
353
indexedAt: new Date().toISOString(),
300
354
})
301
355
.execute();
302
-
ctx.logger.info(res, "wrote back to db");
303
356
return res.redirect(`/p/${shortUrl}`);
304
357
} catch (err) {
305
358
ctx.logger.warn(
···
320
373
.type("html")
321
374
.send("<h1>Error: Session required</h1>");
322
375
}
323
-
376
+
324
377
const pasteUri = req.params.paste;
325
378
const aturi = new AtUri(pasteUri);
326
379
const pasteResponse = await agent.com.atproto.repo.getRecord({
327
380
repo: aturi.hostname,
328
381
collection: aturi.collection,
329
-
rkey: aturi.rkey
382
+
rkey: aturi.rkey,
330
383
});
331
384
const pasteCid = pasteResponse.data.cid;
332
385
if (!pasteCid) {
333
-
return res
334
-
.status(401)
335
-
.type("html")
336
-
.send("invalid paste");
386
+
return res.status(401).type("html").send("invalid paste");
337
387
}
338
388
339
389
const rkey = TID.nextStr();
340
390
const record = {
341
-
$type: "ovh.plonk.comment",
391
+
$type: "li.plonk.comment",
342
392
content: req.body?.comment,
343
393
post: {
344
394
uri: pasteUri,
345
-
cid: pasteCid
395
+
cid: pasteCid,
346
396
},
347
397
createdAt: new Date().toISOString(),
348
398
};
···
358
408
try {
359
409
const res = await agent.com.atproto.repo.putRecord({
360
410
repo: agent.assertDid,
361
-
collection: "ovh.plonk.comment",
411
+
collection: "li.plonk.comment",
362
412
rkey,
363
413
record,
364
414
validate: false,
···
385
435
indexedAt: new Date().toISOString(),
386
436
})
387
437
.execute();
388
-
ctx.logger.info(res, "wrote back to db");
389
-
const originalPaste = await ctx.db.selectFrom('paste').selectAll().where('uri', '=', pasteUri).executeTakeFirst();
390
-
return res.redirect(`/p/${originalPaste.shortUrl}#${encodeURIComponent(uri)}`);
438
+
const originalPaste = await ctx.db
439
+
.selectFrom("paste")
440
+
.selectAll()
441
+
.where("uri", "=", pasteUri)
442
+
.executeTakeFirst();
443
+
return res.redirect(
444
+
`/p/${originalPaste.shortUrl}#${encodeURIComponent(uri)}`,
445
+
);
391
446
} catch (err) {
392
447
ctx.logger.warn(
393
448
{ err },
···
400
455
401
456
return router;
402
457
};
403
-
404
-
// https://pds.icyphox.sh/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3A3ft67n4xnawzq4qi7mcksxj5
405
-
// at://did:plc:3ft67n4xnawzq4qi7mcksxj5/ovh.plonk.paste/3lcs3lnslbk2d
406
-
// https://pds.icyphox.sh/xrpc/com.atproto.repo.getRecord?repo=did%3Aplc%3A3ft67n4xnawzq4qi7mcksxj5&collection=ovh.plonk.paste&rkey=3lcqt7newvc2c
+8
-14
src/views/index.pug
+8
-14
src/views/index.pug
···
1
1
include ../mixins/mkPost
2
2
include ../mixins/head
3
+
include ../mixins/header
4
+
include ../mixins/footer
3
5
include ../mixins/utils
4
6
include ../mixins/post
5
7
···
7
9
-
8
10
var langs = ["plaintext"]
9
11
.concat([
12
+
"bash",
10
13
"javascript",
11
14
"typescript",
12
15
"java",
···
18
21
"c",
19
22
"c#",
20
23
"c++",
24
+
"cobol",
25
+
"nix",
21
26
].toSorted())
22
27
doctype html
23
28
html
24
29
+head("timeline")
25
30
body
26
31
main#content
27
-
div.header
28
-
div.left-side
29
-
if ownDid
30
-
| logged in as @#{didHandleMap[ownDid]}
31
-
div.right-side
32
-
if ownDid
33
-
p
34
-
a(href=`/u/${encodeURIComponent(ownDid)}`) my plonks
35
-
| ยท
36
-
a(href="/logout") logout
37
-
else
38
-
p
39
-
a(href="/login") login
40
-
| to get plonkin'
32
+
+header(ownDid, didHandleMap)
41
33
42
34
if ownDid
43
35
+mkPost()
···
46
38
each paste in pastes
47
39
- var handle = didHandleMap[paste.authorDid]
48
40
+post(paste, handle, paste.authorDid)
41
+
42
+
+footer()
+5
-8
src/views/login.pug
+5
-8
src/views/login.pug
···
1
+
include ../mixins/head
2
+
include ../mixins/footer
3
+
1
4
doctype html
2
5
html
3
-
head
4
-
meta(name="viewport" content="width=device-width, initial-scale=1.0")
5
-
meta(charset='UTF-8')
6
-
title login
7
-
link(rel="stylesheet", href="/styles.css")
8
-
link(rel="preconnect" href="https://rsms.me/")
9
-
link(rel="stylesheet" href="https://rsms.me/inter/inter.css")
10
-
script(src="https://cdn.dashjs.org/latest/dash.all.min.js")
6
+
+head("login")
11
7
body
12
8
main#content
13
9
h1 login
···
15
11
div.login-row
16
12
input(type="text" name="handle" placeholder="enter handle" required).login-input-title
17
13
button(type="submit").login-submit-button login
14
+
+footer()
+58
-20
src/views/paste.pug
+58
-20
src/views/paste.pug
···
1
1
- var now = new Date()
2
2
include ../mixins/head
3
+
include ../mixins/header
4
+
include ../mixins/footer
3
5
include ../mixins/utils
4
6
doctype html
5
7
html
6
-
+head(paste.title)
8
+
+head(`${paste.title} ยท ${didHandleMap[paste.authorDid]}`)
7
9
body
8
10
main#content
11
+
+header(ownDid, didHandleMap)
9
12
h1 #{paste.title}
10
-
p
11
-
| by @#{didHandleMap[authorDid]} ยท
13
+
p.post-info
14
+
| @#{didHandleMap[paste.authorDid]} ยท
12
15
| #{timeDifference(now, Date.parse(paste.createdAt))} ago ยท
13
16
| #{paste.lang} ยท
14
17
| #{paste.code.split('\n').length} loc ยท
15
-
a(href=`/r/${shortUrl}`) raw
16
-
pre
17
-
| #{paste.code}
18
+
a(href=`/r/${paste.shortUrl}`) raw
19
+
| ยท
20
+
button#copy-btn(type="button" onclick="copyToClipboard()" data-code=paste.code) copy
21
+
| ยท
22
+
| #{comments.length} #{pluralize(comments.length, 'comment')}
23
+
div.highlighted-code !{paste.highlightedCode}
18
24
hr
19
25
20
-
div.comments
21
-
each comment in comments
22
-
div.comment(id=`${encodeURIComponent(comment.uri)}`)
23
-
p
24
-
| by @#{didHandleMap[comment.authorDid]} ยท
25
-
| #{timeDifference(now, Date.parse(paste.createdAt))} ago
26
-
p
27
-
| #{comment.body}
28
-
hr
26
+
if comments.length != 0
27
+
h1(id="comments") comments
28
+
div.comments
29
+
each comment in comments
30
+
div.comment(id=`${encodeURIComponent(comment.uri)}`)
31
+
p.comment-info
32
+
a(href=`/u/${comment.authorDid}`)
33
+
| @#{didHandleMap[comment.authorDid]}
34
+
| ยท
35
+
| #{timeDifference(now, Date.parse(paste.createdAt))} ago
36
+
pre.comment-body #{comment.body}
29
37
30
-
form(action=`/${encodeURIComponent(uri)}/comment` method="post").post-form
31
-
div.post-row
32
-
textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code
38
+
if ownDid
39
+
form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form
40
+
div.post-row
41
+
textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code
33
42
34
-
div.post-submit-row
35
-
button(type="submit").post-input-submit zonk!
43
+
div.post-submit-row
44
+
button(type="submit").post-input-submit zonk!
45
+
else
46
+
p
47
+
a(href="/login") login
48
+
| to post a comment
49
+
50
+
+footer()
51
+
script.
52
+
async function copyToClipboard() {
53
+
const copyBtn = document.getElementById('copy-btn');
54
+
const originalText = copyBtn.textContent;
55
+
56
+
try {
57
+
const code = copyBtn.dataset.code;
58
+
await navigator.clipboard.writeText(code);
59
+
copyBtn.textContent = 'copied!';
60
+
copyBtn.style.color = 'var(--accent)';
61
+
62
+
setTimeout(() => {
63
+
copyBtn.textContent = originalText;
64
+
copyBtn.style.color = '';
65
+
}, 1500);
66
+
} catch (err) {
67
+
console.error('Failed to copy: ', err);
68
+
copyBtn.textContent = 'copy failed';
69
+
setTimeout(() => {
70
+
copyBtn.textContent = originalText;
71
+
}, 1500);
72
+
}
73
+
}
+6
-1
src/views/user.pug
+6
-1
src/views/user.pug
···
1
1
- var now = new Date()
2
2
- var handle = didHandleMap[authorDid]
3
3
include ../mixins/head
4
+
include ../mixins/header
5
+
include ../mixins/footer
4
6
include ../mixins/utils
5
7
include ../mixins/post
6
8
doctype html
···
8
10
+head(handle)
9
11
body
10
12
main#content
13
+
+header(ownDid, didHandleMap)
11
14
h1 plonks by @#{handle}
12
15
div.timeline
13
16
each paste in pastes
14
-
+post(paste.value, handle, authorDid)
17
+
+post(paste, handle, authorDid)
18
+
19
+
+footer()