From b618c6a1a10bd47245430a614ac695a2532eabaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Sun, 20 Oct 2024 19:59:06 +0200 Subject: [PATCH] WIP Hello world --- deno.json | 12 ++ deno.lock | 387 +++++++++++++++++++++++++++++++++++++++++++ handlers/naddr.ts | 36 ++++ handlers/nprofile.ts | 10 ++ html.ts | 145 ++++++++++++++++ log.ts | 7 + main.ts | 30 ++++ main_test.ts | 6 + nostr.ts | 24 +++ 9 files changed, 657 insertions(+) create mode 100644 deno.json create mode 100644 deno.lock create mode 100644 handlers/naddr.ts create mode 100644 handlers/nprofile.ts create mode 100644 html.ts create mode 100644 log.ts create mode 100644 main.ts create mode 100644 main_test.ts create mode 100644 nostr.ts diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..85ed625 --- /dev/null +++ b/deno.json @@ -0,0 +1,12 @@ +{ + "tasks": { + "dev": "deno run --allow-net --deny-env --watch main.ts" + }, + "imports": { + "@deno/gfm": "jsr:@deno/gfm@^0.9.0", + "@nostr/tools": "jsr:@nostr/tools@^2.3.1", + "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.36.1", + "@oak/oak": "jsr:@oak/oak@^17.1.0", + "@std/assert": "jsr:@std/assert@1" + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..2e1cf6b --- /dev/null +++ b/deno.lock @@ -0,0 +1,387 @@ +{ + "version": "4", + "specifiers": { + "jsr:@deno/gfm@0.9": "0.9.0", + "jsr:@denosaurs/emoji@0.3": "0.3.1", + "jsr:@nostr/tools@^2.3.1": "2.3.1", + "jsr:@nostrify/nostrify@~0.36.1": "0.36.1", + "jsr:@nostrify/types@0.35": "0.35.0", + "jsr:@oak/commons@1": "1.0.0", + "jsr:@oak/oak@^17.1.0": "17.1.0", + "jsr:@std/assert@1": "1.0.6", + "jsr:@std/bytes@1": "1.0.2", + "jsr:@std/bytes@^1.0.2": "1.0.2", + "jsr:@std/crypto@1": "1.0.3", + "jsr:@std/encoding@1": "1.0.5", + "jsr:@std/encoding@^1.0.5": "1.0.5", + "jsr:@std/encoding@~0.224.1": "0.224.3", + "jsr:@std/http@1": "1.0.8", + "jsr:@std/internal@^1.0.4": "1.0.4", + "jsr:@std/io@0.224": "0.224.9", + "jsr:@std/media-types@1": "1.0.3", + "jsr:@std/path@1": "1.0.6", + "npm:@noble/ciphers@~0.5.1": "0.5.3", + "npm:@noble/curves@1.2.0": "1.2.0", + "npm:@noble/hashes@1.3.1": "1.3.1", + "npm:@scure/base@1.1.1": "1.1.1", + "npm:@scure/bip32@^1.4.0": "1.4.0", + "npm:@scure/bip39@^1.3.0": "1.3.0", + "npm:github-slugger@2": "2.0.0", + "npm:he@^1.2.0": "1.2.0", + "npm:katex@0.16": "0.16.11", + "npm:lru-cache@^10.2.0": "10.2.2", + "npm:marked-alert@2": "2.1.0_marked@12.0.2", + "npm:marked-footnote@^1.2.0": "1.2.4_marked@12.0.2", + "npm:marked-gfm-heading-id@^3.1.0": "3.2.0_marked@12.0.2", + "npm:marked@12": "12.0.2", + "npm:nostr-tools@^2.7.0": "2.7.0", + "npm:path-to-regexp@*": "6.2.1", + "npm:path-to-regexp@6.2.1": "6.2.1", + "npm:prismjs@^1.29.0": "1.29.0", + "npm:sanitize-html@^2.11.0": "2.13.1", + "npm:websocket-ts@^2.1.5": "2.1.5", + "npm:zod@^3.23.8": "3.23.8" + }, + "jsr": { + "@deno/gfm@0.9.0": { + "integrity": "9002dbdb6e382e247509edfeae3afdb9232f5ca98a8210ef186d42084e9ded30", + "dependencies": [ + "jsr:@denosaurs/emoji", + "npm:github-slugger", + "npm:he", + "npm:katex", + "npm:marked", + "npm:marked-alert", + "npm:marked-footnote", + "npm:marked-gfm-heading-id", + "npm:prismjs", + "npm:sanitize-html" + ] + }, + "@denosaurs/emoji@0.3.1": { + "integrity": "b0aed5f55dec99e83da7c9637fe0a36d1d6252b7c99deaaa3fc5dea3fcf3da8b" + }, + "@nostr/tools@2.3.1": { + "integrity": "af01dc45cb28784c584d7a0699707196f397bcc53946efa582a01b11ddde4d61", + "dependencies": [ + "npm:@noble/ciphers", + "npm:@noble/curves", + "npm:@noble/hashes", + "npm:@scure/base" + ] + }, + "@nostrify/nostrify@0.36.1": { + "integrity": "f76c803c0bda5df1c172f25d2313980344b0431df2a973ab3e1dd61e9e7b4b1a", + "dependencies": [ + "jsr:@nostrify/types", + "jsr:@std/encoding@~0.224.1", + "npm:@scure/bip32", + "npm:@scure/bip39", + "npm:lru-cache", + "npm:nostr-tools", + "npm:websocket-ts", + "npm:zod" + ] + }, + "@nostrify/types@0.35.0": { + "integrity": "b8d515563d467072694557d5626fa1600f74e83197eef45dd86a9a99c64f7fe6" + }, + "@oak/commons@1.0.0": { + "integrity": "49805b55603c3627a9d6235c0655aa2b6222d3036b3a13ff0380c16368f607ac", + "dependencies": [ + "jsr:@std/assert", + "jsr:@std/bytes@1", + "jsr:@std/crypto", + "jsr:@std/encoding@1", + "jsr:@std/http", + "jsr:@std/media-types" + ] + }, + "@oak/oak@17.1.0": { + "integrity": "14ffb400c3c268bdc7b3a838664fab782b4ed35bb0dfe7669013c95bb12a9503", + "dependencies": [ + "jsr:@oak/commons", + "jsr:@std/assert", + "jsr:@std/bytes@1", + "jsr:@std/crypto", + "jsr:@std/http", + "jsr:@std/io", + "jsr:@std/media-types", + "jsr:@std/path", + "npm:path-to-regexp@6.2.1" + ] + }, + "@std/assert@1.0.6": { + "integrity": "1904c05806a25d94fe791d6d883b685c9e2dcd60e4f9fc30f4fc5cf010c72207", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/bytes@1.0.2": { + "integrity": "fbdee322bbd8c599a6af186a1603b3355e59a5fb1baa139f8f4c3c9a1b3e3d57" + }, + "@std/crypto@1.0.3": { + "integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f" + }, + "@std/encoding@0.224.3": { + "integrity": "5e861b6d81be5359fad4155e591acf17c0207b595112d1840998bb9f476dbdaf" + }, + "@std/encoding@1.0.5": { + "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04" + }, + "@std/http@1.0.8": { + "integrity": "6ea1b2e8d33929967754a3b6d6c6f399ad6647d7bbb5a466c1eaf9b294a6ebcd", + "dependencies": [ + "jsr:@std/encoding@^1.0.5" + ] + }, + "@std/internal@1.0.4": { + "integrity": "62e8e4911527e5e4f307741a795c0b0a9e6958d0b3790716ae71ce085f755422" + }, + "@std/io@0.224.9": { + "integrity": "4414664b6926f665102e73c969cfda06d2c4c59bd5d0c603fd4f1b1c840d6ee3", + "dependencies": [ + "jsr:@std/bytes@^1.0.2" + ] + }, + "@std/media-types@1.0.3": { + "integrity": "b12d30a7852f7578f4d210622df713bbfd1cbdd9b4ec2eaf5c1845ab70bab159" + }, + "@std/path@1.0.6": { + "integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed" + } + }, + "npm": { + "@noble/ciphers@0.5.3": { + "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==" + }, + "@noble/curves@1.1.0": { + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": [ + "@noble/hashes@1.3.1" + ] + }, + "@noble/curves@1.2.0": { + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": [ + "@noble/hashes@1.3.2" + ] + }, + "@noble/curves@1.4.0": { + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "dependencies": [ + "@noble/hashes@1.4.0" + ] + }, + "@noble/hashes@1.3.1": { + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==" + }, + "@noble/hashes@1.3.2": { + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==" + }, + "@noble/hashes@1.4.0": { + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==" + }, + "@scure/base@1.1.1": { + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==" + }, + "@scure/base@1.1.7": { + "integrity": "sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==" + }, + "@scure/bip32@1.3.1": { + "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", + "dependencies": [ + "@noble/curves@1.1.0", + "@noble/hashes@1.3.2", + "@scure/base@1.1.7" + ] + }, + "@scure/bip32@1.4.0": { + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "dependencies": [ + "@noble/curves@1.4.0", + "@noble/hashes@1.4.0", + "@scure/base@1.1.7" + ] + }, + "@scure/bip39@1.2.1": { + "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "dependencies": [ + "@noble/hashes@1.3.2", + "@scure/base@1.1.7" + ] + }, + "@scure/bip39@1.3.0": { + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", + "dependencies": [ + "@noble/hashes@1.4.0", + "@scure/base@1.1.7" + ] + }, + "commander@8.3.0": { + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" + }, + "deepmerge@4.3.1": { + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, + "dom-serializer@2.0.0": { + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": [ + "domelementtype", + "domhandler", + "entities" + ] + }, + "domelementtype@2.3.0": { + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domhandler@5.0.3": { + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": [ + "domelementtype" + ] + }, + "domutils@3.1.0": { + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": [ + "dom-serializer", + "domelementtype", + "domhandler" + ] + }, + "entities@4.5.0": { + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, + "escape-string-regexp@4.0.0": { + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "github-slugger@2.0.0": { + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" + }, + "he@1.2.0": { + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "htmlparser2@8.0.2": { + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dependencies": [ + "domelementtype", + "domhandler", + "domutils", + "entities" + ] + }, + "is-plain-object@5.0.0": { + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, + "katex@0.16.11": { + "integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==", + "dependencies": [ + "commander" + ] + }, + "lru-cache@10.2.2": { + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==" + }, + "marked-alert@2.1.0_marked@12.0.2": { + "integrity": "sha512-X95Z8PCDgWa0bBfM70GxZG3LD/leUrhXc3cx3w1eFExBhswd1oXn/S4S+9H8ypPdCY7okREb4dItUOc+VJq4jQ==", + "dependencies": [ + "marked" + ] + }, + "marked-footnote@1.2.4_marked@12.0.2": { + "integrity": "sha512-DB2Kl+wFh6YwZd70qABMY6WUkG1UuyqoNTFoDfGyG79Pz24neYtLBkB+45a7o72V7gkfvbC3CGzIYFobxfMT1Q==", + "dependencies": [ + "marked" + ] + }, + "marked-gfm-heading-id@3.2.0_marked@12.0.2": { + "integrity": "sha512-Xfxpr5lXLDLY10XqzSCA9l2dDaiabQUgtYM9hw8yunyVsB/xYBRpiic6BOiY/EAJw1ik1eWr1ET1HKOAPZBhXg==", + "dependencies": [ + "github-slugger", + "marked" + ] + }, + "marked@12.0.2": { + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==" + }, + "nanoid@3.3.7": { + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" + }, + "nostr-tools@2.7.0": { + "integrity": "sha512-jJoL2J1CBiKDxaXZww27nY/Wsuxzx7AULxmGKFce4sskDu1tohNyfnzYQ8BvDyvkstU8kNZUAXPL32tre33uig==", + "dependencies": [ + "@noble/ciphers", + "@noble/curves@1.2.0", + "@noble/hashes@1.3.1", + "@scure/base@1.1.1", + "@scure/bip32@1.3.1", + "@scure/bip39@1.2.1", + "nostr-wasm" + ] + }, + "nostr-wasm@0.1.0": { + "integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==" + }, + "parse-srcset@1.0.2": { + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" + }, + "path-to-regexp@6.2.1": { + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" + }, + "picocolors@1.1.1": { + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "postcss@8.4.47": { + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dependencies": [ + "nanoid", + "picocolors", + "source-map-js" + ] + }, + "prismjs@1.29.0": { + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==" + }, + "sanitize-html@2.13.1": { + "integrity": "sha512-ZXtKq89oue4RP7abL9wp/9URJcqQNABB5GGJ2acW1sdO8JTVl92f4ygD7Yc9Ze09VAZhnt2zegeU0tbNsdcLYg==", + "dependencies": [ + "deepmerge", + "escape-string-regexp", + "htmlparser2", + "is-plain-object", + "parse-srcset", + "postcss" + ] + }, + "source-map-js@1.2.1": { + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" + }, + "websocket-ts@2.1.5": { + "integrity": "sha512-rCNl9w6Hsir1azFm/pbjBEFzLD/gi7Th5ZgOxMifB6STUfTSovYAzryWw0TRvSZ1+Qu1Z5Plw4z42UfTNA9idA==" + }, + "zod@3.23.8": { + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==" + } + }, + "redirects": { + "https://esm.sh/preact/jsx-runtime": "https://esm.sh/preact@10.24.3/jsx-runtime" + }, + "remote": { + "https://esm.sh/preact-render-to-string@5.2.6": "7de74b492ac57386231a0934edc459940ebeec07ee71ac4bf9b89b91ca6d1c0d", + "https://esm.sh/preact@10.24.3/jsx-runtime": "b9e4a84f55cb84ddb400600787de5b6a0ee66d879d8cf920dc0cdc94d00ae847", + "https://esm.sh/stable/preact@10.19.2/denonext/preact.mjs": "dc999b6432dc04d74ad7b692a8801f346294d815266dff915d0f222c120fd0b0", + "https://esm.sh/stable/preact@10.20.2/denonext/preact.mjs": "f418bc70c24b785703afb9d4dea8cdc1e315e43c8df620a0c52fd27ad9bd70eb", + "https://esm.sh/stable/preact@10.24.3/denonext/jsx-runtime.js": "562a3adc7f89394c5fc430323f2875e9ec954bf138572227e5a26f9b42a8d179", + "https://esm.sh/stable/preact@10.24.3/denonext/preact.mjs": "adda7b905b40f0bab844d3ee900378e59da2b16f0f8e716ba9a8d67c15b78c87", + "https://esm.sh/v135/preact-render-to-string@5.2.6/denonext/preact-render-to-string.mjs": "571ed1a39a0a503606419614ab6533e068891be3fa1c5397f44266e3104d59f2" + }, + "workspace": { + "dependencies": [ + "jsr:@deno/gfm@0.9", + "jsr:@nostr/tools@^2.3.1", + "jsr:@nostrify/nostrify@~0.36.1", + "jsr:@oak/oak@^17.1.0", + "jsr:@std/assert@1" + ] + } +} diff --git a/handlers/naddr.ts b/handlers/naddr.ts new file mode 100644 index 0000000..ee0d6c8 --- /dev/null +++ b/handlers/naddr.ts @@ -0,0 +1,36 @@ +import { ctx } from "@oak/oak"; +import { nip19 } from "@nostr/tools"; +import { log } from "../log.ts"; +import { articleHtml } from "../html.ts" +import { + fetchReplaceableEvent, + fetchProfileEvent +} from "../nostr.ts"; + +const naddrHandler = async function (ctx: ctx) { + const { request } = ctx; + const { path } = ctx.params; + + try { + const r = nip19.decode(path); + const articleEvent = await fetchReplaceableEvent(r.data.pubkey, r.data.identifier); + const profileEvent = await fetchProfileEvent(r.data.pubkey); + let profile; + + if (articleEvent && profileEvent) { + const profile = JSON.parse(profileEvent.content); + const html = articleHtml(articleEvent, profile); + + ctx.response.body = html; + } else { + ctx.response.status = 404; + ctx.response.body = "Not Found"; + } + } catch (e) { + log(e, "yellow"); + ctx.response.status = 404; + ctx.response.body = "Not Found"; + } +}; + +export default naddrHandler; diff --git a/handlers/nprofile.ts b/handlers/nprofile.ts new file mode 100644 index 0000000..d0d5ac4 --- /dev/null +++ b/handlers/nprofile.ts @@ -0,0 +1,10 @@ +import { Context } from "@oak/oak"; + +const nprofileHandler = function (context: Context) { + const { request, response } = context; + const fullPath = request.url.pathname; + + response.body = `You are viewing an nprofile with address: ${fullPath}`; +}; + +export default nprofileHandler; diff --git a/html.ts b/html.ts new file mode 100644 index 0000000..2840e5a --- /dev/null +++ b/html.ts @@ -0,0 +1,145 @@ +import { render as renderMarkdown } from "@deno/gfm"; +import { log } from "./log.ts"; + +export function htmlLayout(title: string, body: string) { + return ` + + + + + + + ${title} + + + + + + + ${body} + + + `; +} + +export function articleHtml(articleEvent: object, profile: object) { + const titleTag = articleEvent.tags.find(t => t[0] === "title"); + const title = titleTag ? titleTag[1] : "Untitled"; + const content = renderMarkdown(articleEvent.content); + const date = new Date(articleEvent.created_at * 1000); + const formattedDate = date.toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }); + + const body = ` +
+
+

${title}

+

+ User Avatar + + ${profile.name} + ${formattedDate} + +

+
+ ${content} +
+ `; + + return htmlLayout(title, body); +} diff --git a/log.ts b/log.ts new file mode 100644 index 0000000..1f27c33 --- /dev/null +++ b/log.ts @@ -0,0 +1,7 @@ +export function log(msg: string, color?: string) { + if (typeof color !== "undefined") { + console.debug(`%c${msg}`, `color: ${color}`); + } else { + console.debug(msg); + } +} diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..f0a539e --- /dev/null +++ b/main.ts @@ -0,0 +1,30 @@ +import { Application, Router } from "@oak/oak"; +import { log } from "./log.ts"; +import naddrHandler from "./handlers/naddr.ts"; +import nprofileHandler from "./handlers/nprofile.ts"; + +const router = new Router(); + +router.get("/:path", async (ctx: ctx) => { + const { path } = ctx.params; + + if (path && path.startsWith("naddr")) { + await naddrHandler(ctx); + } else if (prefix && prefix.startsWith("nprofile")) { + await nprofileHandler(ctx); + } else { + ctx.response.status = 404; + ctx.response.body = "Not Found"; + } + + log(`${ctx.request.method} ${ctx.request.url} - ${ctx.response.status}`, "gray"); +}); + +const app = new Application(); +app.use(router.routes()); +app.use(router.allowedMethods()); + +const PORT = 8000; +app.listen({ port: PORT }); + +console.log(`App listening on http://localhost:${PORT}`) diff --git a/main_test.ts b/main_test.ts new file mode 100644 index 0000000..2d83169 --- /dev/null +++ b/main_test.ts @@ -0,0 +1,6 @@ +import { assertEquals } from "@std/assert"; +// import { add } from "./main.ts"; + +// Deno.test(function addTest() { +// assertEquals(add(2, 3), 5); +// }); diff --git a/nostr.ts b/nostr.ts new file mode 100644 index 0000000..3480237 --- /dev/null +++ b/nostr.ts @@ -0,0 +1,24 @@ +import { NRelay1 } from "@nostrify/nostrify"; + +export const relay = new NRelay1("wss://nostr.kosmos.org"); + +export async function fetchReplaceableEvent(pubkey: string, identifier: string) { + const events = await relay.query([{ + authors: [pubkey], + kinds: [30023], + "#d": [identifier], + limit: 1, + }]); + + return events.length > 0 ? events[0] : null; +} + +export async function fetchProfileEvent(pubkey: string) { + const events = await relay.query([{ + authors: [pubkey], + kinds: [0], + limit: 1, + }]); + + return events.length > 0 ? events[0] : null; +}