atproto pastebin service: https://plonk.li

Compare changes

Choose any two refs to compare.

+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
··· 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
··· 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
··· 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 { 2 2 "lexicon": 1, 3 - "id": "ovh.plonk.paste", 3 + "id": "li.plonk.paste", 4 4 "defs": { 5 5 "main": { 6 6 "type": "record",
+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
··· 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

This is a binary file and will not be displayed.

+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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 });
+9
src/mixins/footer.pug
··· 1 + mixin footer() 2 + hr 3 + div.footer 4 + div.left-side 5 + div.right-side 6 + p 7 + | made by 8 + a(href="https://bsky.app/profile/oppi.li") @oppi.li 9 +
+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
··· 1 + mixin header(ownDid, didHandleMap) 2 + div.header 3 + div.left-side 4 + div.right-side 5 + p 6 + a(href="/") home 7 + | &nbsp;ยท&nbsp; 8 + if ownDid 9 + a(href=`/u/${encodeURIComponent(ownDid)}`) my plonks 10 + | &nbsp;ยท&nbsp; 11 + a(href="/logout") logout 12 + else 13 + a(href="/login") login 14 + |&nbsp;to get plonkin' 15 +
+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 + | ยท&nbsp; 17 + a(href=`/p/${paste.shortUrl}/#comments`) #{paste.commentCount} #{pluralize(paste.commentCount, 'comment')}
+4
src/mixins/utils.pug
··· 2 2 function randInt(min, max) { 3 3 return Math.floor(Math.random() * (max - min + 1)) + min; 4 4 } 5 + - 6 + function pluralize(count, noun) { 7 + return count==1?noun:`${noun}s`; 8 + } 5 9 - 6 10 function timeDifference(current, previous) { 7 11 if (!current || !previous) {
+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
··· 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
··· 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 - | &nbsp;ยท&nbsp; 36 - a(href="/logout") logout 37 - else 38 - p 39 - a(href="/login") login 40 - |&nbsp;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
··· 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
··· 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 + | &nbsp;ยท&nbsp; 20 + button#copy-btn(type="button" onclick="copyToClipboard()" data-code=paste.code) copy 21 + | &nbsp;ยท 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 + | &nbsp;ยท 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 + |&nbsp;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
··· 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()
-12
tsup.config.ts
··· 1 - import { defineConfig } from 'tsup'; 2 - 3 - export default defineConfig({ 4 - entry: ['src/index.ts'], 5 - outDir: 'dist', 6 - clean: true, 7 - format: 'esm', 8 - target: 'node18', 9 - dts: true, 10 - minify: true, 11 - sourcemap: true, 12 - });