mirror of
https://github.com/fiatjaf/nak.git
synced 2026-08-01 12:16:14 +00:00
Compare commits
10
Commits
v0.0.3
..
javascript
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08015ed306 | ||
|
|
93359bc1e8 | ||
|
|
7debc6fd2d | ||
|
|
143c7fe000 | ||
|
|
1d26118430 | ||
|
|
e7027c169a | ||
|
|
78b5cccfc2 | ||
|
|
20ea54a282 | ||
|
|
76c8caa2de | ||
|
|
8312d9ece7 |
+147
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"sourceType": "module",
|
||||
"allowImportExportEverywhere": false
|
||||
},
|
||||
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
|
||||
"plugins": [
|
||||
"react",
|
||||
"babel"
|
||||
],
|
||||
|
||||
"globals": {
|
||||
"document": false,
|
||||
"navigator": false,
|
||||
"window": false,
|
||||
"location": false,
|
||||
"URL": false,
|
||||
"URLSearchParams": false,
|
||||
"fetch": false,
|
||||
"EventSource": false,
|
||||
"localStorage": false,
|
||||
"sessionStorage": false
|
||||
},
|
||||
|
||||
"rules": {
|
||||
"react/jsx-uses-vars": 2,
|
||||
"react/jsx-no-undef": 2,
|
||||
"react/jsx-uses-react": 2,
|
||||
"accessor-pairs": 2,
|
||||
"arrow-spacing": [2, { "before": true, "after": true }],
|
||||
"block-spacing": [2, "always"],
|
||||
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
|
||||
"comma-dangle": 0,
|
||||
"comma-spacing": [2, { "before": false, "after": true }],
|
||||
"comma-style": [2, "last"],
|
||||
"constructor-super": 2,
|
||||
"curly": [0, "multi-line"],
|
||||
"dot-location": [2, "property"],
|
||||
"eol-last": 2,
|
||||
"eqeqeq": [2, "allow-null"],
|
||||
"generator-star-spacing": [2, { "before": true, "after": true }],
|
||||
"handle-callback-err": [2, "^(err|error)$" ],
|
||||
"indent": 0,
|
||||
"jsx-quotes": [2, "prefer-double"],
|
||||
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
|
||||
"keyword-spacing": [2, { "before": true, "after": true }],
|
||||
"new-cap": 0,
|
||||
"new-parens": 0,
|
||||
"no-array-constructor": 2,
|
||||
"no-caller": 2,
|
||||
"no-class-assign": 2,
|
||||
"no-cond-assign": 2,
|
||||
"no-const-assign": 2,
|
||||
"no-control-regex": 0,
|
||||
"no-debugger": 0,
|
||||
"no-delete-var": 2,
|
||||
"no-dupe-args": 2,
|
||||
"no-dupe-class-members": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-empty-character-class": 2,
|
||||
"no-empty-pattern": 2,
|
||||
"no-eval": 0,
|
||||
"no-ex-assign": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-parens": [2, "functions"],
|
||||
"no-fallthrough": 2,
|
||||
"no-floating-decimal": 2,
|
||||
"no-func-assign": 2,
|
||||
"no-implied-eval": 2,
|
||||
"no-inner-declarations": [0, "functions"],
|
||||
"no-invalid-regexp": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-iterator": 2,
|
||||
"no-label-var": 2,
|
||||
"no-labels": [2, { "allowLoop": false, "allowSwitch": false }],
|
||||
"no-lone-blocks": 2,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-multi-spaces": 2,
|
||||
"no-multi-str": 2,
|
||||
"no-multiple-empty-lines": [2, { "max": 2 }],
|
||||
"no-native-reassign": 2,
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-new": 0,
|
||||
"no-new-func": 2,
|
||||
"no-new-object": 2,
|
||||
"no-new-require": 2,
|
||||
"no-new-symbol": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-obj-calls": 2,
|
||||
"no-octal": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-path-concat": 0,
|
||||
"no-proto": 2,
|
||||
"no-redeclare": 2,
|
||||
"no-regex-spaces": 2,
|
||||
"no-return-assign": 0,
|
||||
"no-self-assign": 2,
|
||||
"no-self-compare": 2,
|
||||
"no-sequences": 2,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-sparse-arrays": 2,
|
||||
"no-this-before-super": 2,
|
||||
"no-throw-literal": 2,
|
||||
"no-trailing-spaces": 2,
|
||||
"no-undef": 2,
|
||||
"no-undef-init": 2,
|
||||
"no-unexpected-multiline": 2,
|
||||
"no-unneeded-ternary": [2, { "defaultAssignment": false }],
|
||||
"no-unreachable": 2,
|
||||
"no-unused-vars": [2, { "vars": "local", "args": "none", "varsIgnorePattern": "^_"}],
|
||||
"no-useless-call": 2,
|
||||
"no-useless-constructor": 2,
|
||||
"no-with": 2,
|
||||
"one-var": [0, { "initialized": "never" }],
|
||||
"operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }],
|
||||
"padded-blocks": [2, "never"],
|
||||
"quotes": [2, "single", { "avoidEscape": true, "allowTemplateLiterals": true }],
|
||||
"semi": [2, "never"],
|
||||
"semi-spacing": [2, { "before": false, "after": true }],
|
||||
"space-before-blocks": [2, "always"],
|
||||
"space-before-function-paren": 0,
|
||||
"space-in-parens": [2, "never"],
|
||||
"space-infix-ops": 2,
|
||||
"space-unary-ops": [2, { "words": true, "nonwords": false }],
|
||||
"spaced-comment": 0,
|
||||
"template-curly-spacing": [2, "never"],
|
||||
"use-isnan": 2,
|
||||
"valid-typeof": 2,
|
||||
"wrap-iife": [2, "any"],
|
||||
"yield-star-spacing": [2, "both"],
|
||||
"yoda": 0,
|
||||
"no-unsafe-optional-chaning": 0
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
name: build page and publish to cloudflare
|
||||
on:
|
||||
push:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: olafurpg/setup-scala@v11
|
||||
- name: build page / compile scalajs
|
||||
run: sbt fullLinkJS/esBuild
|
||||
- name: publish to cloudflare
|
||||
uses: cloudflare/pages-action@v1
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: 60325047cc7d0811c6b337717918cbc1
|
||||
projectName: nostr-army-knife
|
||||
directory: .
|
||||
+2
-8
@@ -1,9 +1,3 @@
|
||||
target
|
||||
.bsp
|
||||
globals.bundle.js
|
||||
yarn.lock
|
||||
node_modules
|
||||
project/project
|
||||
.metals
|
||||
.bloop
|
||||
project/metals.sbt
|
||||
public/app.js
|
||||
yarn.lock
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
arrowParens: avoid
|
||||
bracketSpacing: false
|
||||
jsxBracketSameLine: false
|
||||
printWidth: 80
|
||||
proseWrap: preserve
|
||||
semi: false
|
||||
singleQuote: true
|
||||
trailingComma: none
|
||||
useTabs: false
|
||||
@@ -1,2 +0,0 @@
|
||||
version = 3.5.8
|
||||
runner.dialect = scala3
|
||||
@@ -1,165 +1 @@
|
||||
# nostr army knife
|
||||
|
||||
this repository contains two things:
|
||||
|
||||
## a command-line tool for decoding and encoding nostr entities and talking to relays
|
||||
|
||||
Install with `go install github.com/fiatjaf/nak`.
|
||||
|
||||
It pairs nicely with https://github.com/blakejakopovic/nostcat using unix pipes.
|
||||
|
||||
### examples
|
||||
|
||||
```
|
||||
~> nak decode nsec1aqc5q5l8da0l7u6gra4p5xhleclngezlpsgd7z5dx07cpu8sxf2shqgn6y
|
||||
{
|
||||
"pubkey": "5b36b874b2b983197ba4be80553b2e4b6db2895a04567cea0aa47585b2e0c620",
|
||||
"private_key": "e8314053e76f5fff73481f6a1a1affce3f34645f0c10df0a8d33fd80f0f03255"
|
||||
}
|
||||
|
||||
~> nak event -c hello --sec e8314053e76f5fff73481f6a1a1affce3f34645f0c10df0a8d33fd80f0f03255
|
||||
{"id":"ed840ef37a40cce4f4b8c361e5df13457ad664209cf4a297fd7df7e84fdd32e0","pubkey":"5b36b874b2b983197ba4be80553b2e4b6db2895a04567cea0aa47585b2e0c620","created_at":1683201092,"kind":1,"tags":[],"content":"hello","sig":"304a87dbbdf986a187eb9417316cfe3d6f8f31793ba20c9c6d7e4ebeeefe950d6ecba6098c201b7170c04e27c2f920d607a90f5c8763c35ac806dce37df1d05d"}
|
||||
~> nak event -c hello --sec e8314053e76f5fff73481f6a1a1affce3f34645f0c10df0a8d33fd80f0f03255 wss://relay.stoner.com wss://nos.lol wss://nostr.wine wss://atlas.nostr.land wss://relay.damus.io
|
||||
{"id":"54a534647bdcd2751d743fea4fc9eee5dba613887d69425f0891d9c2f82772a5","pubkey":"5b36b874b2b983197ba4be80553b2e4b6db2895a04567cea0aa47585b2e0c620","created_at":1684895417,"kind":1,"tags":[],"content":"hello","sig":"81a14cfe628fab6cd6135bb66f6e8b3bb4bfce4f666462a1303fdfbc9038fd141e73db3fe7e774a8f023fc70622c50a67d4fa41d3d09806c78f051985c11e0bd"}
|
||||
publishing to wss://relay.stoner.com... failed: msg: blocked: pubkey is not allowed to publish to this relay
|
||||
publishing to wss://nos.lol... success.
|
||||
publishing to wss://nostr.wine... failed: msg: blocked: not an active paid member
|
||||
publishing to wss://atlas.nostr.land... failed: msg: blocked: pubkey not admitted
|
||||
publishing to wss://relay.damus.io... success.
|
||||
|
||||
~> nak decode nevent1qqs29yet5tp0qq5xu5qgkeehkzqh5qu46739axzezcxpj4tjlkx9j7gpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5sh59ud
|
||||
{
|
||||
"id": "a2932ba2c2f00286e5008b6737b0817a0395d7a25e9859160c195572fd8c5979",
|
||||
"relays": [
|
||||
"wss://nostr-pub.wellorder.net"
|
||||
]
|
||||
}
|
||||
|
||||
~> nak req -a a2932ba2c2f00286e5008b6737b0817a0395d7a25e9859160c195572fd8c5979 -k 1 -a e8b487c079b0f67c695ae6c4c2552a47f38adfa2533cc5926bd2c102942fdcb7
|
||||
["REQ","nak",{"kinds":[1],"authors":["a2932ba2c2f00286e5008b6737b0817a0395d7a25e9859160c195572fd8c5979","e8b487c079b0f67c695ae6c4c2552a47f38adfa2533cc5926bd2c102942fdcb7"]}]
|
||||
|
||||
~> nak req -k 1 -l 1 --stream wss://relay.stoner.com
|
||||
{"id":"1d73832917bf5a72276c53e9246c28b97225b51cd5735843434f7756fc0ddead","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1684894689,"kind":1,"tags":[["p","bcbeb5a2e6b547f6d0c3d8c16145f7bb94f3639ec7ecbcfe50045dbb2eede70b","wss://nos.lol","artk42"],["e","b5af6815c8d89a7d5b6201b9e624fbd5389fca3337ba2dc05c6187234a7c1bd5","wss://nos.lol","root"],["e","5795e27aff0a459a30c64a61a32c43d968cd19c8f1926cf01fc02e9da7c56f2b","wss://nos.lol","reply"],["client","coracle"]],"content":"Because that makes no sense.","sig":"3ee5b2b26ec6b116ef1a6b1c10bc7e56674a3c36841814f68b57f63259f3d78e23629d4599afe67e72c220e27b4b0966cc51adc1da808c8c6111dedb531ac0c3"}
|
||||
```
|
||||
|
||||
### documentation
|
||||
|
||||
```
|
||||
~> nak --help
|
||||
NAME:
|
||||
nak - the nostr army knife command-line tool
|
||||
|
||||
USAGE:
|
||||
nak [global options] command [command options] [arguments...]
|
||||
|
||||
COMMANDS:
|
||||
req generates encoded REQ messages and optionally use them to talk to relays
|
||||
event generates an encoded event and either prints it or sends it to a set of relays
|
||||
decode decodes nip19, nip21, nip05 or hex entities
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--help, -h show help
|
||||
|
||||
~> nak event --help
|
||||
NAME:
|
||||
nak event - generates an encoded event and either prints it or sends it to a set of relays
|
||||
|
||||
USAGE:
|
||||
nak event [command options] [arguments...]
|
||||
|
||||
DESCRIPTION:
|
||||
example usage (for sending directly to a relay with 'nostcat'):
|
||||
nak event -k 1 -c hello --envelope | nostcat wss://nos.lol
|
||||
standalone:
|
||||
nak event -k 1 -c hello wss://nos.lol`,
|
||||
|
||||
OPTIONS:
|
||||
--envelope print the event enveloped in a ["EVENT", ...] message ready to be sent to a relay (default: false)
|
||||
--sec value secret key to sign the event (default: the key '1')
|
||||
|
||||
EVENT FIELDS
|
||||
|
||||
--content value, -c value event content (default: hello from the nostr army knife)
|
||||
--created-at value, --time value, --ts value unix timestamp value for the created_at field (default: now)
|
||||
--kind value, -k value event kind (default: 1)
|
||||
--tag value, -t value [ --tag value, -t value ] sets a tag field on the event, takes a value like -t e=<id>
|
||||
-e value [ -e value ] shortcut for --tag e=<value>
|
||||
-p value [ -p value ] shortcut for --tag p=<value>
|
||||
|
||||
~> nak req --help
|
||||
NAME:
|
||||
nak req - generates encoded REQ messages and optionally use them to talk to relays
|
||||
|
||||
USAGE:
|
||||
nak req [command options] [relay...]
|
||||
|
||||
DESCRIPTION:
|
||||
outputs a NIP-01 Nostr filter. when a relay is not given, will print the filter, otherwise will connect to the given relay and send the filter.
|
||||
|
||||
example usage (with 'nostcat'):
|
||||
nak req -k 1 -a 3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d | nostcat wss://nos.lol
|
||||
standalone:
|
||||
nak req -k 1 wss://nos.lol
|
||||
|
||||
OPTIONS:
|
||||
--bare when printing the filter, print just the filter, not enveloped in a ["REQ", ...] array (default: false)
|
||||
--stream keep the subscription open, printing all events as they are returned (default: false, will close on EOSE)
|
||||
|
||||
FILTER ATTRIBUTES
|
||||
|
||||
--author value, -a value [ --author value, -a value ] only accept events from these authors (pubkey as hex)
|
||||
--id value, -i value [ --id value, -i value ] only accept events with these ids (hex)
|
||||
--kind value, -k value [ --kind value, -k value ] only accept events with these kind numbers
|
||||
--limit value, -l value only accept up to this number of events (default: 0)
|
||||
--since value, -s value only accept events newer than this (unix timestamp) (default: 0)
|
||||
--tag value, -t value [ --tag value, -t value ] takes a tag like -t e=<id>, only accept events with these tags
|
||||
--until value, -u value only accept events older than this (unix timestamp) (default: 0)
|
||||
-e value [ -e value ] shortcut for --tag e=<value>
|
||||
-p value [ -p value ] shortcut for --tag p=<value>
|
||||
|
||||
|
||||
OPTIONS:
|
||||
--bare when printing the filter, print just the filter, not enveloped in a ["REQ", ...] array (default: false)
|
||||
--stream keep the subscription open, printing all events as they are returned (default: false, will close on EOSE)
|
||||
|
||||
FILTER ATTRIBUTES
|
||||
|
||||
--author value, -a value [ --author value, -a value ] only accept events from these authors (pubkey as hex)
|
||||
--id value, -i value [ --id value, -i value ] only accept events with these ids (hex)
|
||||
--kind value, -k value [ --kind value, -k value ] only accept events with these kind numbers
|
||||
--limit value, -l value only accept up to this number of events (default: 0)
|
||||
--since value, -s value only accept events newer than this (unix timestamp) (default: 0)
|
||||
--tag value, -t value [ --tag value, -t value ] takes a tag like -t e=<id>, only accept events with these tags
|
||||
--until value, -u value only accept events older than this (unix timestamp) (default: 0)
|
||||
-e value [ -e value ] shortcut for --tag e=<value>
|
||||
-p value [ -p value ] shortcut for --tag p=<value>
|
||||
|
||||
|
||||
~> nak decode --help
|
||||
NAME:
|
||||
nak decode - decodes nip19, nip21, nip05 or hex entities
|
||||
|
||||
USAGE:
|
||||
nak decode [command options] <npub | nprofile | nip05 | nevent | naddr | nsec>
|
||||
|
||||
DESCRIPTION:
|
||||
example usage:
|
||||
nak decode npub1uescmd5krhrmj9rcura833xpke5eqzvcz5nxjw74ufeewf2sscxq4g7chm
|
||||
nak decode nevent1qqs29yet5tp0qq5xu5qgkeehkzqh5qu46739axzezcxpj4tjlkx9j7gpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5sh59ud
|
||||
nak decode nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpz4mhxue69uhk2er9dchxummnw3ezumrpdejqz8thwden5te0dehhxarj94c82c3wwajkcmr0wfjx2u3wdejhgqgcwaehxw309aex2mrp0yhxummnw3exzarf9e3k7mgnp0sh5
|
||||
nak decode nsec1jrmyhtjhgd9yqalps8hf9mayvd58852gtz66m7tqpacjedkp6kxq4dyxsr
|
||||
|
||||
OPTIONS:
|
||||
--id, -e return just the event id, if applicable (default: false)
|
||||
--pubkey, -p return just the pubkey, if applicable (default: false)
|
||||
--help, -h show help
|
||||
```
|
||||
|
||||
written in go using [go-nostr](https://github.com/nbd-wtf/go-nostr), heavily inspired by [nostril](http://git.jb55.com/nostril/).
|
||||
|
||||
## a toolkit for debugging all things nostr as a webpage:
|
||||
|
||||

|
||||
|
||||
written in [scala](https://scala-lang.org/) with [calico](https://www.armanbilge.com/calico/) and [snow](https://github.com/fiatjaf/snow)
|
||||
[](https://app.netlify.com/sites/nostr-army-knife/deploys)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import {render} from 'react-dom'
|
||||
|
||||
import Nothing from './handlers/Nothing'
|
||||
import EventSigning from './handlers/EventSigning'
|
||||
import KeyHandling from './handlers/KeyHandling'
|
||||
|
||||
const handlers = [EventSigning, KeyHandling, Nothing]
|
||||
|
||||
function App() {
|
||||
let [value, setValue] = useState(localStorage.getItem('value'))
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('value', value)
|
||||
}, [value])
|
||||
|
||||
let Result
|
||||
for (let i = 0; i < handlers.length; i++) {
|
||||
let alt = handlers[i]
|
||||
if (alt.match(value)) {
|
||||
Result = alt
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<main style={{fontFamily: 'monospace'}}>
|
||||
<div
|
||||
style={{
|
||||
width: '90%',
|
||||
margin: 'auto'
|
||||
}}
|
||||
>
|
||||
<h1>nostr army knife</h1>
|
||||
paste something nostric
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
style={{
|
||||
padding: '7px',
|
||||
width: '100%',
|
||||
minHeight: '200px'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<hr style={{margin: '18px 0'}} />
|
||||
<div
|
||||
style={{
|
||||
width: '90%',
|
||||
margin: 'auto'
|
||||
}}
|
||||
>
|
||||
<Result value={value} />
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
render(<App />, document.getElementById('app'))
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const esbuild = require('esbuild')
|
||||
const alias = require('esbuild-plugin-alias')
|
||||
const nodeGlobals = require('@esbuild-plugins/node-globals-polyfill').default
|
||||
|
||||
const prod = process.argv.indexOf('prod') !== -1
|
||||
|
||||
esbuild
|
||||
.build({
|
||||
bundle: true,
|
||||
entryPoints: ['app.jsx'],
|
||||
outdir: 'public',
|
||||
plugins: [
|
||||
alias({
|
||||
stream: require.resolve('readable-stream')
|
||||
}),
|
||||
nodeGlobals({buffer: true})
|
||||
],
|
||||
sourcemap: prod ? false : 'inline',
|
||||
define: {
|
||||
window: 'self',
|
||||
global: 'self'
|
||||
}
|
||||
})
|
||||
.then(() => console.log('build success.'))
|
||||
@@ -1,14 +0,0 @@
|
||||
enablePlugins(ScalaJSPlugin, EsbuildPlugin)
|
||||
|
||||
name := "nostr-army-knife"
|
||||
scalaVersion := "3.3.0-RC4"
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
"com.armanbilge" %%% "calico" % "0.2.0-RC2",
|
||||
"com.fiatjaf" %%% "snow" % "0.0.1"
|
||||
),
|
||||
scalaJSUseMainModuleInitializer := true,
|
||||
scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function Item({label, hint, children}) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
marginBottom: '9px',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordWrap: 'break-word',
|
||||
wordBreak: 'break-all'
|
||||
}}
|
||||
>
|
||||
<b data-wenk={hint} data-wenk-pos="right">
|
||||
{label}:{' '}
|
||||
</b>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/sdk"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var decode = &cli.Command{
|
||||
Name: "decode",
|
||||
Usage: "decodes nip19, nip21, nip05 or hex entities",
|
||||
Description: `example usage:
|
||||
nak decode npub1uescmd5krhrmj9rcura833xpke5eqzvcz5nxjw74ufeewf2sscxq4g7chm
|
||||
nak decode nevent1qqs29yet5tp0qq5xu5qgkeehkzqh5qu46739axzezcxpj4tjlkx9j7gpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5sh59ud
|
||||
nak decode nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpz4mhxue69uhk2er9dchxummnw3ezumrpdejqz8thwden5te0dehhxarj94c82c3wwajkcmr0wfjx2u3wdejhgqgcwaehxw309aex2mrp0yhxummnw3exzarf9e3k7mgnp0sh5
|
||||
nak decode nsec1jrmyhtjhgd9yqalps8hf9mayvd58852gtz66m7tqpacjedkp6kxq4dyxsr`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "id",
|
||||
Aliases: []string{"e"},
|
||||
Usage: "return just the event id, if applicable",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "pubkey",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "return just the pubkey, if applicable",
|
||||
},
|
||||
},
|
||||
ArgsUsage: "<npub | nprofile | nip05 | nevent | naddr | nsec>",
|
||||
Action: func(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if args.Len() != 1 {
|
||||
return fmt.Errorf("invalid number of arguments, need just one")
|
||||
}
|
||||
input := args.First()
|
||||
if strings.HasPrefix(input, "nostr:") {
|
||||
input = input[6:]
|
||||
}
|
||||
|
||||
var decodeResult DecodeResult
|
||||
if b, err := hex.DecodeString(input); err == nil {
|
||||
if len(b) == 64 {
|
||||
decodeResult.HexResult.PossibleTypes = []string{"sig"}
|
||||
decodeResult.HexResult.Signature = hex.EncodeToString(b)
|
||||
} else if len(b) == 32 {
|
||||
decodeResult.HexResult.PossibleTypes = []string{"pubkey", "private_key", "event_id"}
|
||||
decodeResult.HexResult.ID = hex.EncodeToString(b)
|
||||
decodeResult.HexResult.PrivateKey = hex.EncodeToString(b)
|
||||
decodeResult.HexResult.PublicKey = hex.EncodeToString(b)
|
||||
} else {
|
||||
return fmt.Errorf("hex string with invalid number of bytes: %d", len(b))
|
||||
}
|
||||
} else if evp := sdk.InputToEventPointer(input); evp != nil {
|
||||
decodeResult = DecodeResult{EventPointer: evp}
|
||||
} else if pp := sdk.InputToProfile(c.Context, input); pp != nil {
|
||||
decodeResult = DecodeResult{ProfilePointer: pp}
|
||||
} else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "naddr" {
|
||||
ep := value.(nostr.EntityPointer)
|
||||
decodeResult = DecodeResult{EntityPointer: &ep}
|
||||
} else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "nsec" {
|
||||
decodeResult.PrivateKey.PrivateKey = value.(string)
|
||||
decodeResult.PrivateKey.PublicKey, _ = nostr.GetPublicKey(value.(string))
|
||||
} else {
|
||||
return fmt.Errorf("couldn't decode input")
|
||||
}
|
||||
|
||||
fmt.Println(decodeResult.JSON())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
type DecodeResult struct {
|
||||
*nostr.EventPointer
|
||||
*nostr.ProfilePointer
|
||||
*nostr.EntityPointer
|
||||
HexResult struct {
|
||||
PossibleTypes []string `json:"possible_types"`
|
||||
PublicKey string `json:"pubkey,omitempty"`
|
||||
ID string `json:"event_id,omitempty"`
|
||||
PrivateKey string `json:"private_key,omitempty"`
|
||||
Signature string `json:"sig,omitempty"`
|
||||
}
|
||||
PrivateKey struct {
|
||||
nostr.ProfilePointer
|
||||
PrivateKey string `json:"private_key"`
|
||||
}
|
||||
}
|
||||
|
||||
func (d DecodeResult) JSON() string {
|
||||
var j []byte
|
||||
if d.EventPointer != nil {
|
||||
j, _ = json.MarshalIndent(d.EventPointer, "", " ")
|
||||
} else if d.ProfilePointer != nil {
|
||||
j, _ = json.MarshalIndent(d.ProfilePointer, "", " ")
|
||||
} else if d.EntityPointer != nil {
|
||||
j, _ = json.MarshalIndent(d.EntityPointer, "", " ")
|
||||
} else if len(d.HexResult.PossibleTypes) > 0 {
|
||||
j, _ = json.MarshalIndent(d.HexResult, "", " ")
|
||||
} else if d.PrivateKey.PrivateKey != "" {
|
||||
j, _ = json.MarshalIndent(d.PrivateKey, "", " ")
|
||||
}
|
||||
return string(j)
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const CATEGORY_EVENT_FIELDS = "EVENT FIELDS"
|
||||
|
||||
var event = &cli.Command{
|
||||
Name: "event",
|
||||
Usage: "generates an encoded event and either prints it or sends it to a set of relays",
|
||||
Description: `example usage (for sending directly to a relay with 'nostcat'):
|
||||
nak event -k 1 -c hello --envelope | nostcat wss://nos.lol
|
||||
standalone:
|
||||
nak event -k 1 -c hello wss://nos.lol`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "sec",
|
||||
Usage: "secret key to sign the event",
|
||||
DefaultText: "the key '1'",
|
||||
Value: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "envelope",
|
||||
Usage: "print the event enveloped in a [\"EVENT\", ...] message ready to be sent to a relay",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "kind",
|
||||
Aliases: []string{"k"},
|
||||
Usage: "event kind",
|
||||
DefaultText: "1",
|
||||
Value: 1,
|
||||
Category: CATEGORY_EVENT_FIELDS,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "content",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "event content",
|
||||
DefaultText: "hello from the nostr army knife",
|
||||
Value: "hello from the nostr army knife",
|
||||
Category: CATEGORY_EVENT_FIELDS,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "tag",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "sets a tag field on the event, takes a value like -t e=<id>",
|
||||
Category: CATEGORY_EVENT_FIELDS,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "e",
|
||||
Usage: "shortcut for --tag e=<value>",
|
||||
Category: CATEGORY_EVENT_FIELDS,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "p",
|
||||
Usage: "shortcut for --tag p=<value>",
|
||||
Category: CATEGORY_EVENT_FIELDS,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "created-at",
|
||||
Aliases: []string{"time", "ts"},
|
||||
Usage: "unix timestamp value for the created_at field",
|
||||
DefaultText: "now",
|
||||
Value: "now",
|
||||
Category: CATEGORY_EVENT_FIELDS,
|
||||
},
|
||||
},
|
||||
ArgsUsage: "[relay...]",
|
||||
Action: func(c *cli.Context) error {
|
||||
evt := nostr.Event{
|
||||
Kind: c.Int("kind"),
|
||||
Content: c.String("content"),
|
||||
Tags: make(nostr.Tags, 0, 3),
|
||||
}
|
||||
|
||||
tags := make([][]string, 0, 5)
|
||||
for _, tagFlag := range c.StringSlice("tag") {
|
||||
spl := strings.Split(tagFlag, "=")
|
||||
if len(spl) == 2 && len(spl[0]) == 1 {
|
||||
tags = append(tags, spl)
|
||||
}
|
||||
}
|
||||
for _, etag := range c.StringSlice("e") {
|
||||
tags = append(tags, []string{"e", etag})
|
||||
}
|
||||
for _, ptag := range c.StringSlice("p") {
|
||||
tags = append(tags, []string{"p", ptag})
|
||||
}
|
||||
if len(tags) > 0 {
|
||||
for _, tag := range tags {
|
||||
evt.Tags = append(evt.Tags, tag)
|
||||
}
|
||||
}
|
||||
|
||||
createdAt := c.String("created-at")
|
||||
ts := time.Now()
|
||||
if createdAt != "now" {
|
||||
if v, err := strconv.ParseInt(createdAt, 10, 64); err != nil {
|
||||
return fmt.Errorf("failed to parse timestamp '%s': %w", createdAt, err)
|
||||
} else {
|
||||
ts = time.Unix(v, 0)
|
||||
}
|
||||
}
|
||||
evt.CreatedAt = nostr.Timestamp(ts.Unix())
|
||||
|
||||
if err := evt.Sign(c.String("sec")); err != nil {
|
||||
return fmt.Errorf("error signing with provided key: %w", err)
|
||||
}
|
||||
|
||||
relays := c.Args().Slice()
|
||||
if len(relays) > 0 {
|
||||
fmt.Println(evt.String())
|
||||
for _, url := range relays {
|
||||
fmt.Fprintf(os.Stderr, "publishing to %s... ", url)
|
||||
if relay, err := nostr.RelayConnect(c.Context, url); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to connect: %s\n", err)
|
||||
} else {
|
||||
if status, err := relay.Publish(c.Context, evt); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed: %s\n", err)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "%s.\n", status)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var result string
|
||||
if c.Bool("envelope") {
|
||||
j, _ := json.Marshal([]any{"EVENT", evt})
|
||||
result = string(j)
|
||||
} else {
|
||||
result = evt.String()
|
||||
}
|
||||
fmt.Println(result)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M17 13.5v6H5v-12h6m3-3h6v6m0-6-9 9" class="icon_svg-stroke" stroke="#666" stroke-width="1.5" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
||||
|
Before Width: | Height: | Size: 278 B |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 37 KiB |
@@ -1,30 +0,0 @@
|
||||
module github.com/fiatjaf/nak
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/nbd-wtf/go-nostr v0.18.3
|
||||
github.com/urfave/cli/v2 v2.25.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20220414055132-a37292614db8 // indirect
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.2.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/tidwall/gjson v1.14.4 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
)
|
||||
@@ -1,134 +0,0 @@
|
||||
github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20220414055132-a37292614db8 h1:Xa6tp8DPDhdV+k23uiTC/GrAYOe4IdyJVKtob4KW3GA=
|
||||
github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20220414055132-a37292614db8/go.mod h1:ihkm1viTbO/LOsgdGoFPBSvzqvx7ibvkMzYp3CgtHik=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
|
||||
github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
|
||||
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.2.0 h1:u0p9s3xLYpZCA1z5JgCkMeB34CKCMMQbM+G8Ii7YD0I=
|
||||
github.com/gobwas/ws v1.2.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/nbd-wtf/go-nostr v0.18.3 h1:ofMYxlFAptyoErlOGOCUk7zGHQNJ8/ZkIXXOsveFZ+c=
|
||||
github.com/nbd-wtf/go-nostr v0.18.3/go.mod h1:GPJOOK8US38kz+bfb9nWe873Xu0e6bXlThejOs1LTkc=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/urfave/cli/v2 v2.25.3 h1:VJkt6wvEBOoSjPFQvOkv6iWIrsJyCrKGtCtxXWwmGeY=
|
||||
github.com/urfave/cli/v2 v2.25.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136 h1:Fq7F/w7MAa1KJ5bt2aJ62ihqp9HDcRuyILskkpIAurw=
|
||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -0,0 +1,103 @@
|
||||
import React, {useState} from 'react'
|
||||
import useComputedState from 'use-computed-state'
|
||||
import {
|
||||
getPublicKey,
|
||||
getEventHash,
|
||||
serializeEvent,
|
||||
verifySignature,
|
||||
signEvent
|
||||
} from 'nostr-tools'
|
||||
|
||||
import Item from '../components/item'
|
||||
|
||||
export default function EventSigning({value}) {
|
||||
let evt = JSON.parse(value)
|
||||
let [privateKey, setPrivateKey] = useState(
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
)
|
||||
let privateKeyIsValid = useComputedState(
|
||||
() => privateKey.match(/^[a-f0-9]{64}$/),
|
||||
[privateKey]
|
||||
)
|
||||
let publicKey = useComputedState(
|
||||
() => (privateKeyIsValid ? getPublicKey(privateKey) : null),
|
||||
[privateKeyIsValid]
|
||||
)
|
||||
let signature = useComputedState(async () => {
|
||||
if (evt.sig) return null
|
||||
|
||||
try {
|
||||
evt.pubkey = publicKey
|
||||
return await signEvent(evt, privateKey)
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
}, [value, privateKey])
|
||||
let eventHash = useComputedState(() => getEventHash(evt), [value])
|
||||
let isValidHash = useComputedState(
|
||||
() => evt.id === eventHash,
|
||||
[value, eventHash]
|
||||
)
|
||||
let isValidSignature = useComputedState(async () => {
|
||||
if (evt.id && evt.sig) {
|
||||
try {
|
||||
return await verifySignature(evt)
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}, [value])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Item
|
||||
label="serialized event"
|
||||
hint="according to nip-01 signature algorithm"
|
||||
>
|
||||
{serializeEvent(evt)}
|
||||
</Item>
|
||||
<Item label="event id" hint="sha256 hash of serialized event">
|
||||
{eventHash}
|
||||
</Item>
|
||||
<Item
|
||||
label="event id matches hash"
|
||||
hint="the calculated hash from event data matches the 'id' in the body?"
|
||||
>
|
||||
{isValidHash?.toString()}
|
||||
</Item>
|
||||
{isValidHash &&
|
||||
(evt.sig ? (
|
||||
<Item label="signature valid">
|
||||
{isValidSignature?.toString() || ''}
|
||||
</Item>
|
||||
) : (
|
||||
<>
|
||||
<Item
|
||||
label="private key"
|
||||
hint="paste any private key here (32 bytes hex-encoded)"
|
||||
>
|
||||
<input
|
||||
value={privateKey}
|
||||
onChange={e => setPrivateKey(e.target.value.toLowerCase())}
|
||||
/>{' '}
|
||||
{privateKeyIsValid ? 'valid' : 'invalid'}
|
||||
</Item>
|
||||
<Item label="public key">{publicKey}</Item>
|
||||
<Item label="signature">{privateKeyIsValid ? signature : ''}</Item>
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
EventSigning.match = value => {
|
||||
try {
|
||||
let evt = JSON.parse(value)
|
||||
return evt.kind && evt.content && evt.tags
|
||||
} catch (err) {
|
||||
/**/
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react'
|
||||
import useBooleanState from 'use-boolean-state'
|
||||
|
||||
import {getPublicKey} from 'nostr-tools'
|
||||
|
||||
import Item from '../components/item'
|
||||
|
||||
export default function KeyHandling({value}) {
|
||||
let privateKey = value
|
||||
let publicKey = getPublicKey(privateKey)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Item label="private key">{privateKey}</Item>
|
||||
<Item label="public key">{publicKey}</Item>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
KeyHandling.match = value => {
|
||||
try {
|
||||
if (value.toLowerCase().match(/^[a-f0-9]{64}$/)) return true
|
||||
} catch (err) {
|
||||
/**/
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function Nothing() {
|
||||
return (
|
||||
<>
|
||||
you can paste
|
||||
<ul>
|
||||
<li>an unsigned event to be hashed and signed</li>
|
||||
<li>a signed event to have its signature checked</li>
|
||||
<li>a nostr relay URL to be inspected</li>
|
||||
<li>a nostr event id we'll try to fetch</li>
|
||||
<li>a nip05 identifier to be checked</li>
|
||||
<li>
|
||||
contribute a new function:{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
style={{color: 'inherit'}}
|
||||
href="https://github.com/fiatjaf/nostr-army-knife"
|
||||
>
|
||||
_______
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Nothing.match = () => true
|
||||
@@ -1,7 +0,0 @@
|
||||
<meta charset=utf-8>
|
||||
<title>nostr army knife</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<body class="bg-emerald-200 text-black m-0 w-full h-full">
|
||||
<div id="app" class="w-full h-full"></div>
|
||||
<script type="module" src="/target/esbuild/bundle.js"></script>
|
||||
</body>
|
||||
@@ -1,13 +0,0 @@
|
||||
build-prod:
|
||||
sbt fullLinkJS/esBuild
|
||||
|
||||
cloudflare:
|
||||
rm -fr cf
|
||||
mkdir -p cf/target/esbuild
|
||||
cp index.html cf/
|
||||
cp favicon.ico cf/
|
||||
cp target/esbuild/bundle.js cf/target/esbuild
|
||||
wrangler pages publish cf --project-name nostr-army-knife --branch master
|
||||
rm -fr cf
|
||||
|
||||
build-and-deploy: build-prod cloudflare
|
||||
@@ -1,25 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := &cli.App{
|
||||
Name: "nak",
|
||||
Usage: "the nostr army knife command-line tool",
|
||||
Commands: []*cli.Command{
|
||||
req,
|
||||
event,
|
||||
decode,
|
||||
},
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.1.1",
|
||||
"esbuild": "^0.14.21",
|
||||
"esbuild-plugin-alias": "^0.2.1",
|
||||
"events": "^3.3.0",
|
||||
"nostr-tools": "^0.22.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"use-boolean-state": "^1.0.2",
|
||||
"use-computed-state": "1.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "./build.js prod",
|
||||
"watch": "ag -l --js | entr ./build.js"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
sbt.version=1.7.1
|
||||
@@ -1,2 +0,0 @@
|
||||
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.0")
|
||||
addSbtPlugin("com.fiatjaf" % "sbt-esbuild" % "0.1.1")
|
||||
@@ -0,0 +1,8 @@
|
||||
<meta charset="utf-8" />
|
||||
<title>nostr army knife</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/wenk/1.0.8/wenk.min.css"
|
||||
/>
|
||||
<div id="app" />
|
||||
<script src="app.js"></script>
|
||||
@@ -1,162 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const CATEGORY_FILTER_ATTRIBUTES = "FILTER ATTRIBUTES"
|
||||
|
||||
var req = &cli.Command{
|
||||
Name: "req",
|
||||
Usage: "generates encoded REQ messages and optionally use them to talk to relays",
|
||||
Description: `outputs a NIP-01 Nostr filter. when a relay is not given, will print the filter, otherwise will connect to the given relay and send the filter.
|
||||
|
||||
example usage (with 'nostcat'):
|
||||
nak req -k 1 -a 3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d | nostcat wss://nos.lol
|
||||
standalone:
|
||||
nak req -k 1 wss://nos.lol`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringSliceFlag{
|
||||
Name: "author",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "only accept events from these authors (pubkey as hex)",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "id",
|
||||
Aliases: []string{"i"},
|
||||
Usage: "only accept events with these ids (hex)",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.IntSliceFlag{
|
||||
Name: "kind",
|
||||
Aliases: []string{"k"},
|
||||
Usage: "only accept events with these kind numbers",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "tag",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "takes a tag like -t e=<id>, only accept events with these tags",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "e",
|
||||
Usage: "shortcut for --tag e=<value>",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "p",
|
||||
Usage: "shortcut for --tag p=<value>",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "since",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "only accept events newer than this (unix timestamp)",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "until",
|
||||
Aliases: []string{"u"},
|
||||
Usage: "only accept events older than this (unix timestamp)",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "limit",
|
||||
Aliases: []string{"l"},
|
||||
Usage: "only accept up to this number of events",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "bare",
|
||||
Usage: "when printing the filter, print just the filter, not enveloped in a [\"REQ\", ...] array",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "stream",
|
||||
Usage: "keep the subscription open, printing all events as they are returned",
|
||||
DefaultText: "false, will close on EOSE",
|
||||
},
|
||||
},
|
||||
ArgsUsage: "[relay...]",
|
||||
Action: func(c *cli.Context) error {
|
||||
filter := nostr.Filter{}
|
||||
|
||||
if authors := c.StringSlice("author"); len(authors) > 0 {
|
||||
filter.Authors = authors
|
||||
}
|
||||
if ids := c.StringSlice("id"); len(ids) > 0 {
|
||||
filter.IDs = ids
|
||||
}
|
||||
if kinds := c.IntSlice("kind"); len(kinds) > 0 {
|
||||
filter.Kinds = kinds
|
||||
}
|
||||
|
||||
tags := make([][]string, 0, 5)
|
||||
for _, tagFlag := range c.StringSlice("tag") {
|
||||
spl := strings.Split(tagFlag, "=")
|
||||
if len(spl) == 2 && len(spl[0]) == 1 {
|
||||
tags = append(tags, spl)
|
||||
} else {
|
||||
return fmt.Errorf("invalid --tag '%s'", tagFlag)
|
||||
}
|
||||
}
|
||||
for _, etag := range c.StringSlice("e") {
|
||||
tags = append(tags, []string{"e", etag})
|
||||
}
|
||||
for _, ptag := range c.StringSlice("p") {
|
||||
tags = append(tags, []string{"p", ptag})
|
||||
}
|
||||
if len(tags) > 0 {
|
||||
filter.Tags = make(nostr.TagMap)
|
||||
for _, tag := range tags {
|
||||
if _, ok := filter.Tags[tag[0]]; !ok {
|
||||
filter.Tags[tag[0]] = make([]string, 0, 3)
|
||||
}
|
||||
filter.Tags[tag[0]] = append(filter.Tags[tag[0]], tag[1])
|
||||
}
|
||||
}
|
||||
|
||||
if since := c.Int("since"); since != 0 {
|
||||
ts := nostr.Timestamp(since)
|
||||
filter.Since = &ts
|
||||
}
|
||||
if until := c.Int("until"); until != 0 {
|
||||
ts := nostr.Timestamp(until)
|
||||
filter.Until = &ts
|
||||
}
|
||||
if limit := c.Int("limit"); limit != 0 {
|
||||
filter.Limit = limit
|
||||
}
|
||||
|
||||
relays := c.Args().Slice()
|
||||
if len(relays) > 0 {
|
||||
pool := nostr.NewSimplePool(c.Context)
|
||||
fn := pool.SubManyEose
|
||||
if c.Bool("stream") {
|
||||
fn = pool.SubMany
|
||||
}
|
||||
for evt := range fn(c.Context, relays, nostr.Filters{filter}) {
|
||||
fmt.Println(evt)
|
||||
}
|
||||
} else {
|
||||
// no relays given, will just print the filter
|
||||
var result string
|
||||
if c.Bool("bare") {
|
||||
result = filter.String()
|
||||
} else {
|
||||
j, _ := json.Marshal([]any{"REQ", "nak", filter})
|
||||
result = string(j)
|
||||
}
|
||||
|
||||
fmt.Println(result)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -1,315 +0,0 @@
|
||||
import cats.data.{Store => *, *}
|
||||
import cats.effect.*
|
||||
import cats.effect.syntax.all.*
|
||||
import cats.syntax.all.*
|
||||
import fs2.concurrent.*
|
||||
import fs2.dom.{Event => _, *}
|
||||
import io.circe.parser.*
|
||||
import io.circe.syntax.*
|
||||
import calico.*
|
||||
import calico.html.io.{*, given}
|
||||
import calico.syntax.*
|
||||
import scodec.bits.ByteVector
|
||||
import scoin.*
|
||||
import snow.*
|
||||
|
||||
import Utils.*
|
||||
|
||||
object Components {
|
||||
def render32Bytes(bytes32: ByteVector32): Resource[IO, HtmlDivElement[IO]] =
|
||||
div(
|
||||
cls := "text-md",
|
||||
entry("canonical hex", bytes32.toHex),
|
||||
"if this is a public key:",
|
||||
div(
|
||||
cls := "mt-2 pl-2 mb-2",
|
||||
entry(
|
||||
"npub",
|
||||
NIP19.encode(XOnlyPublicKey(bytes32))
|
||||
),
|
||||
nip19_21(
|
||||
"nprofile",
|
||||
NIP19.encode(ProfilePointer(XOnlyPublicKey(bytes32)))
|
||||
)
|
||||
),
|
||||
"if this is a private key:",
|
||||
div(
|
||||
cls := "pl-2 mb-2",
|
||||
entry(
|
||||
"nsec",
|
||||
NIP19.encode(PrivateKey(bytes32))
|
||||
),
|
||||
entry(
|
||||
"npub",
|
||||
NIP19.encode(XOnlyPublicKey(bytes32))
|
||||
),
|
||||
nip19_21(
|
||||
"nprofile",
|
||||
NIP19.encode(ProfilePointer(XOnlyPublicKey(bytes32)))
|
||||
)
|
||||
),
|
||||
"if this is an event id:",
|
||||
div(
|
||||
cls := "pl-2 mb-2",
|
||||
nip19_21(
|
||||
"nevent",
|
||||
NIP19.encode(EventPointer(bytes32.toHex))
|
||||
)
|
||||
),
|
||||
div(
|
||||
cls := "pl-2 mb-2",
|
||||
entry(
|
||||
"note",
|
||||
NIP19.encode(bytes32)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def renderEventPointer(
|
||||
store: Store,
|
||||
evp: snow.EventPointer
|
||||
): Resource[IO, HtmlDivElement[IO]] =
|
||||
div(
|
||||
cls := "text-md",
|
||||
entry("event id (hex)", evp.id),
|
||||
relayHints(store, evp.relays),
|
||||
evp.author.map { pk =>
|
||||
entry("author hint (pubkey hex)", pk.value.toHex)
|
||||
},
|
||||
nip19_21("nevent", NIP19.encode(evp)),
|
||||
entry("note", NIP19.encode(ByteVector32.fromValidHex(evp.id)))
|
||||
)
|
||||
|
||||
def renderProfilePointer(
|
||||
store: Store,
|
||||
pp: snow.ProfilePointer,
|
||||
sk: Option[PrivateKey] = None
|
||||
): Resource[IO, HtmlDivElement[IO]] =
|
||||
div(
|
||||
cls := "text-md",
|
||||
sk.map { k => entry("private key (hex)", k.value.toHex) },
|
||||
sk.map { k => entry("nsec", NIP19.encode(k)) },
|
||||
entry("public key (hex)", pp.pubkey.value.toHex),
|
||||
relayHints(
|
||||
store,
|
||||
pp.relays,
|
||||
dynamic = if sk.isDefined then false else true
|
||||
),
|
||||
entry("npub", NIP19.encode(pp.pubkey)),
|
||||
nip19_21("nprofile", NIP19.encode(pp))
|
||||
)
|
||||
|
||||
def renderAddressPointer(
|
||||
store: Store,
|
||||
addr: snow.AddressPointer
|
||||
): Resource[IO, HtmlDivElement[IO]] =
|
||||
div(
|
||||
cls := "text-md",
|
||||
entry("author (pubkey hex)", addr.author.value.toHex),
|
||||
entry("identifier", addr.d),
|
||||
entry("kind", addr.kind.toString),
|
||||
relayHints(store, addr.relays),
|
||||
nip19_21("naddr", NIP19.encode(addr))
|
||||
)
|
||||
|
||||
def renderEvent(
|
||||
store: Store,
|
||||
event: Event
|
||||
): Resource[IO, HtmlDivElement[IO]] =
|
||||
div(
|
||||
cls := "text-md",
|
||||
if event.pubkey.isEmpty then
|
||||
Some(
|
||||
div(
|
||||
cls := "flex items-center",
|
||||
entry("missing", "pubkey"),
|
||||
button(
|
||||
Styles.buttonSmall,
|
||||
"fill with a debugging key",
|
||||
onClick --> (_.foreach { _ =>
|
||||
store.input.set(
|
||||
event
|
||||
.copy(pubkey = Some(keyOne.publicKey.xonly))
|
||||
.asJson
|
||||
.printWith(jsonPrinter)
|
||||
)
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
else None,
|
||||
if event.id.isEmpty then
|
||||
Some(
|
||||
div(
|
||||
cls := "flex items-center",
|
||||
entry("missing", "id"),
|
||||
if event.pubkey.isDefined then
|
||||
Some(
|
||||
button(
|
||||
Styles.buttonSmall,
|
||||
"fill id",
|
||||
onClick --> (_.foreach(_ =>
|
||||
store.input.set(
|
||||
event
|
||||
.copy(id = Some(event.hash.toHex))
|
||||
.asJson
|
||||
.printWith(jsonPrinter)
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
else None
|
||||
)
|
||||
)
|
||||
else None,
|
||||
if event.sig.isEmpty then
|
||||
Some(
|
||||
div(
|
||||
cls := "flex items-center",
|
||||
entry("missing", "sig"),
|
||||
if event.id.isDefined && event.pubkey == Some(
|
||||
keyOne.publicKey.xonly
|
||||
)
|
||||
then
|
||||
Some(
|
||||
button(
|
||||
Styles.buttonSmall,
|
||||
"sign",
|
||||
onClick --> (_.foreach(_ =>
|
||||
store.input.set(
|
||||
event
|
||||
.sign(keyOne)
|
||||
.asJson
|
||||
.printWith(jsonPrinter)
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
else None
|
||||
)
|
||||
)
|
||||
else None,
|
||||
entry("serialized event", event.serialized),
|
||||
entry("implied event id", event.hash.toHex),
|
||||
entry(
|
||||
"does the implied event id match the given event id?",
|
||||
event.id == Some(event.hash.toHex) match {
|
||||
case true => "yes"; case false => "no"
|
||||
}
|
||||
),
|
||||
entry(
|
||||
"is signature valid?",
|
||||
event.isValid match {
|
||||
case true => "yes"; case false => "no"
|
||||
}
|
||||
),
|
||||
event.id.map(id =>
|
||||
nip19_21(
|
||||
"nevent",
|
||||
NIP19.encode(EventPointer(id, author = event.pubkey))
|
||||
)
|
||||
),
|
||||
event.id.map(id =>
|
||||
entry(
|
||||
"note",
|
||||
NIP19.encode(ByteVector32.fromValidHex(id))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
private def entry(
|
||||
key: String,
|
||||
value: String
|
||||
): Resource[IO, HtmlDivElement[IO]] =
|
||||
div(
|
||||
cls := "flex items-center space-x-3",
|
||||
span(cls := "font-bold", key + " "),
|
||||
span(Styles.mono, cls := "max-w-xl", value)
|
||||
)
|
||||
|
||||
private def nip19_21(
|
||||
key: String,
|
||||
code: String
|
||||
): Resource[IO, HtmlDivElement[IO]] =
|
||||
div(
|
||||
span(cls := "font-bold", key + " "),
|
||||
span(Styles.mono, cls := "break-all", code),
|
||||
a(
|
||||
href := "nostr:" + code,
|
||||
external
|
||||
)
|
||||
)
|
||||
|
||||
private def relayHints(
|
||||
store: Store,
|
||||
relays: List[String],
|
||||
dynamic: Boolean = true
|
||||
): Resource[IO, HtmlDivElement[IO]] =
|
||||
if !dynamic && relays.isEmpty then div("")
|
||||
else
|
||||
SignallingRef[IO].of(false).toResource.flatMap { active =>
|
||||
val value =
|
||||
if relays.size > 0 then relays.reduce((a, b) => s"$a, $b") else ""
|
||||
|
||||
div(
|
||||
cls := "flex items-center space-x-3",
|
||||
span(cls := "font-bold", "relay hints "),
|
||||
span(Styles.mono, cls := "max-w-xl", value),
|
||||
active.map {
|
||||
case true =>
|
||||
div(
|
||||
input.withSelf { self =>
|
||||
(
|
||||
onKeyPress --> (_.foreach(evt =>
|
||||
evt.key match {
|
||||
case "Enter" =>
|
||||
self.value.get.flatMap(url =>
|
||||
if url.startsWith("wss://") || url
|
||||
.startsWith("ws://")
|
||||
then {
|
||||
store.result.get.flatMap(result =>
|
||||
store.input.set(
|
||||
result
|
||||
.map {
|
||||
case a: AddressPointer =>
|
||||
NIP19
|
||||
.encode(
|
||||
a.copy(relays = url :: a.relays)
|
||||
)
|
||||
case p: ProfilePointer =>
|
||||
NIP19
|
||||
.encode(
|
||||
p.copy(relays = url :: p.relays)
|
||||
)
|
||||
case e: EventPointer =>
|
||||
NIP19
|
||||
.encode(
|
||||
e.copy(relays = url :: e.relays)
|
||||
)
|
||||
case r => ""
|
||||
}
|
||||
.getOrElse("")
|
||||
)
|
||||
)
|
||||
>> active.set(false)
|
||||
} else IO.unit
|
||||
)
|
||||
case _ => IO.unit
|
||||
}
|
||||
))
|
||||
)
|
||||
}
|
||||
)
|
||||
case false if dynamic =>
|
||||
button(
|
||||
Styles.buttonSmall,
|
||||
"add relay hint",
|
||||
onClick --> (_.foreach(_ => active.set(true)))
|
||||
)
|
||||
case false => div("")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private val external = img(cls := "inline w-4 ml-2", src := "ext.svg")
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
import cats.effect.*
|
||||
import cats.effect.syntax.all.*
|
||||
import cats.syntax.all.*
|
||||
import fs2.concurrent.*
|
||||
import fs2.dom.{Event => _, *}
|
||||
import io.circe.parser.*
|
||||
import io.circe.syntax.*
|
||||
import calico.*
|
||||
import calico.html.io.{*, given}
|
||||
import calico.syntax.*
|
||||
import scoin.*
|
||||
import snow.*
|
||||
|
||||
import Utils.*
|
||||
import Components.*
|
||||
|
||||
object Main extends IOWebApp {
|
||||
def render: Resource[IO, HtmlDivElement[IO]] = Store(window).flatMap {
|
||||
store =>
|
||||
div(
|
||||
cls := "flex w-full h-full flex-col items-center justify-center",
|
||||
div(
|
||||
cls := "w-4/5",
|
||||
h1(
|
||||
cls := "px-1 py-2 text-center text-xl",
|
||||
img(
|
||||
cls := "inline-block w-8 mr-2",
|
||||
src := "/favicon.ico"
|
||||
),
|
||||
a(
|
||||
href := "/",
|
||||
"nostr army knife"
|
||||
)
|
||||
),
|
||||
div(
|
||||
cls := "flex my-3",
|
||||
input(store),
|
||||
actions(store)
|
||||
),
|
||||
result(store)
|
||||
),
|
||||
div(
|
||||
cls := "flex justify-end mr-5 mt-10 text-xs w-4/5",
|
||||
a(
|
||||
href := "https://github.com/fiatjaf/nak",
|
||||
"source code"
|
||||
),
|
||||
a(
|
||||
cls := "ml-4",
|
||||
href := "https://github.com/fiatjaf/nak",
|
||||
"get the command-line tool"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def actions(store: Store): Resource[IO, HtmlDivElement[IO]] =
|
||||
div(
|
||||
cls := "flex flex-col space-y-1 my-3",
|
||||
store.input.map {
|
||||
case "" => div("")
|
||||
case _ =>
|
||||
button(
|
||||
Styles.button,
|
||||
"clear",
|
||||
onClick --> (_.foreach(_ => store.input.set("")))
|
||||
)
|
||||
},
|
||||
store.result.map {
|
||||
case Right(_: Event) =>
|
||||
button(
|
||||
Styles.button,
|
||||
"format",
|
||||
onClick --> (_.foreach(_ =>
|
||||
store.input.update(original =>
|
||||
parse(original).toOption
|
||||
.map(_.printWith(jsonPrinter))
|
||||
.getOrElse(original)
|
||||
)
|
||||
))
|
||||
)
|
||||
case _ => div("")
|
||||
},
|
||||
button(
|
||||
Styles.button,
|
||||
"generate event",
|
||||
onClick --> (_.foreach(_ =>
|
||||
store.input.set(
|
||||
Event(
|
||||
kind = 1,
|
||||
content = "hello world"
|
||||
).sign(keyOne)
|
||||
.asJson
|
||||
.printWith(jsonPrinter)
|
||||
)
|
||||
))
|
||||
),
|
||||
button(
|
||||
Styles.button,
|
||||
"generate keypair",
|
||||
onClick --> (_.foreach(_ =>
|
||||
store.input.set(
|
||||
NIP19.encode(PrivateKey(randomBytes32()))
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
def input(store: Store): Resource[IO, HtmlDivElement[IO]] =
|
||||
div(
|
||||
cls := "w-full grow",
|
||||
div(
|
||||
cls := "w-full flex justify-center",
|
||||
textArea.withSelf { self =>
|
||||
(
|
||||
cls := "w-full max-h-96 p-3 rounded",
|
||||
styleAttr := "min-height: 280px; font-family: monospace",
|
||||
spellCheck := false,
|
||||
placeholder := "paste something nostric (event JSON, nprofile, npub, nevent etc or hex key or id)",
|
||||
onInput --> (_.foreach(_ =>
|
||||
self.value.get.flatMap(store.input.set)
|
||||
)),
|
||||
value <-- store.input
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def result(store: Store): Resource[IO, HtmlDivElement[IO]] =
|
||||
div(
|
||||
cls := "w-full flex my-5",
|
||||
store.result.map {
|
||||
case Left(msg) => div(msg)
|
||||
case Right(bytes: ByteVector32) => render32Bytes(bytes)
|
||||
case Right(event: Event) => renderEvent(store, event)
|
||||
case Right(pp: ProfilePointer) => renderProfilePointer(store, pp)
|
||||
case Right(evp: EventPointer) => renderEventPointer(store, evp)
|
||||
case Right(sk: PrivateKey) =>
|
||||
renderProfilePointer(
|
||||
store,
|
||||
ProfilePointer(pubkey = sk.publicKey.xonly),
|
||||
Some(sk)
|
||||
)
|
||||
case Right(addr: AddressPointer) => renderAddressPointer(store, addr)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import scala.util.Try
|
||||
import io.circe.parser.*
|
||||
import cats.syntax.all.*
|
||||
import scodec.bits.ByteVector
|
||||
import scoin.*
|
||||
import snow.*
|
||||
|
||||
type Result = Either[
|
||||
String,
|
||||
Event | PrivateKey | AddressPointer | EventPointer | ProfilePointer |
|
||||
ByteVector32
|
||||
]
|
||||
|
||||
object Parser {
|
||||
val additions = raw" *\+ *".r
|
||||
|
||||
def parseInput(input: String): Result =
|
||||
if input == "" then Left("")
|
||||
else
|
||||
ByteVector
|
||||
.fromHex(input)
|
||||
.flatMap(b => Try(Right(ByteVector32(b))).toOption)
|
||||
.getOrElse(
|
||||
NIP19.decode(input) match {
|
||||
case Right(pp: ProfilePointer) => Right(pp)
|
||||
case Right(evp: EventPointer) => Right(evp)
|
||||
case Right(sk: PrivateKey) => Right(sk)
|
||||
case Right(addr: AddressPointer) => Right(addr)
|
||||
case Left(_) =>
|
||||
parse(input) match {
|
||||
case Left(err: io.circe.ParsingFailure) =>
|
||||
Left("not valid JSON or NIP-19 code")
|
||||
case Right(json) =>
|
||||
json
|
||||
.as[Event]
|
||||
.leftMap { err =>
|
||||
err.pathToRootString match {
|
||||
case None => s"decoding ${err.pathToRootString}"
|
||||
case Some(path) => s"field $path is missing or wrong"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import cats.data.*
|
||||
import cats.effect.*
|
||||
import cats.effect.syntax.all.*
|
||||
import cats.syntax.all.*
|
||||
import fs2.concurrent.*
|
||||
import fs2.dom.{Event => _, *}
|
||||
import scoin.PrivateKey
|
||||
|
||||
case class Store(
|
||||
input: SignallingRef[IO, String],
|
||||
result: SignallingRef[IO, Result]
|
||||
)
|
||||
|
||||
object Store {
|
||||
def apply(window: Window[IO]): Resource[IO, Store] = {
|
||||
val key = "nak-input"
|
||||
|
||||
for {
|
||||
input <- SignallingRef[IO].of("").toResource
|
||||
result <- SignallingRef[IO, Result](Left("")).toResource
|
||||
|
||||
_ <- Resource.eval {
|
||||
OptionT(window.localStorage.getItem(key))
|
||||
.foreachF(input.set(_))
|
||||
}
|
||||
|
||||
_ <- window.localStorage
|
||||
.events(window)
|
||||
.foreach {
|
||||
case Storage.Event.Updated(`key`, _, value, _) =>
|
||||
input.set(value)
|
||||
case _ => IO.unit
|
||||
}
|
||||
.compile
|
||||
.drain
|
||||
.background
|
||||
|
||||
_ <- input.discrete
|
||||
.evalTap(input => IO.cede *> window.localStorage.setItem(key, input))
|
||||
.evalTap(input => result.set(Parser.parseInput(input.trim())))
|
||||
.compile
|
||||
.drain
|
||||
.background
|
||||
} yield Store(input, result)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import calico.html.io.*
|
||||
|
||||
object Styles {
|
||||
val button = cls :=
|
||||
"shrink bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 mx-2 px-4 rounded "
|
||||
val buttonSmall = cls :=
|
||||
"shrink text-sm bg-blue-500 hover:bg-blue-700 text-white font-bold mx-2 px-2 rounded "
|
||||
val mono = styleAttr := "font-family: monospace"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import io.circe.Printer
|
||||
import scodec.bits.ByteVector
|
||||
import scoin.*
|
||||
|
||||
object Utils {
|
||||
val keyOne = PrivateKey(ByteVector32(ByteVector(0x01).padLeft(32)))
|
||||
|
||||
val jsonPrinter = Printer(
|
||||
dropNullValues = false,
|
||||
indent = " ",
|
||||
lbraceRight = "\n",
|
||||
rbraceLeft = "\n",
|
||||
lbracketRight = "\n",
|
||||
rbracketLeft = "\n",
|
||||
lrbracketsEmpty = "",
|
||||
arrayCommaRight = "\n",
|
||||
objectCommaRight = "\n",
|
||||
colonLeft = "",
|
||||
colonRight = " ",
|
||||
sortKeys = true
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user