WIP Hello world

This commit is contained in:
Râu Cao 2024-10-20 19:59:06 +02:00
commit b618c6a1a1
Signed by: raucao
GPG Key ID: 37036C356E56CC51
9 changed files with 657 additions and 0 deletions

12
deno.json Normal file
View File

@ -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"
}
}

387
deno.lock generated Normal file
View File

@ -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"
]
}
}

36
handlers/naddr.ts Normal file
View File

@ -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;

10
handlers/nprofile.ts Normal file
View File

@ -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;

145
html.ts Normal file
View File

@ -0,0 +1,145 @@
import { render as renderMarkdown } from "@deno/gfm";
import { log } from "./log.ts";
export function htmlLayout(title: string, body: string) {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>${title}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,400;0,700;0,900;1,400;1,700&display=swap" rel="stylesheet">
<style>
body {
background-color: #f5f2eb;
color: #3b3a38;
font-size: 18px;
font-family: "Merriweather", serif;
}
img {
max-width: 100%;
}
img.avatar {
height: 48px;
width: 48px;
border-radius: 50%;
}
a.anchor {
display: none;
}
h1 {
margin: 2em 0 0 0;
}
h2, h3, h4 {
margin-top: 2em;
margin-bottom: 2rem;
}
h1, h2, h3, h4 {
color: #191818;
}
p, pre, ul, ol, blockquote {
line-height: 1.6em;
margin-bottom: 1.6em;
}
a {
color: #023b77
}
a:visited {
color: #3b0277
}
code {
font-size: 1rem;
background-color: #e8e3da;
color: #027739;
padding: 0.1em 0.3em;
}
pre code {
display: block;
padding: 0.6rem 1rem;
background-color: #333;
color: #ccc;
}
main {
display: block;
max-width: 728px;
margin: 12rem auto;
}
main header {
display: block;
margin-bottom: 3rem;
}
main header h1 {
margin-bottom: 1.6rem;
}
p.meta {
display: flex;
column-gap: 1rem;
}
p.meta .content {
display: flex;
flex-direction: column;
font-size: 0.875rem;
line-height: 1.6rem;
}
p.meta .date {
color: #888;
}
</style>
</head>
<body>
${body}
</body>
</html>
`;
}
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 = `
<main>
<header>
<h1>${title}</h1>
<p class="meta">
<img class="avatar" src="${profile.picture}" alt="User Avatar" />
<span class="content">
<span class="name">${profile.name}</span>
<span class="date">${formattedDate}</span>
</span>
</p>
</header>
${content}
</main>
`;
return htmlLayout(title, body);
}

7
log.ts Normal file
View File

@ -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);
}
}

30
main.ts Normal file
View File

@ -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}`)

6
main_test.ts Normal file
View File

@ -0,0 +1,6 @@
import { assertEquals } from "@std/assert";
// import { add } from "./main.ts";
// Deno.test(function addTest() {
// assertEquals(add(2, 3), 5);
// });

24
nostr.ts Normal file
View File

@ -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;
}