Merge pull request 'sigit-issue-30' (#1) from sigit-issue-30 into master

Reviewed-on: https://git.sigit.io/sig/dvm/pulls/1
This commit is contained in:
Y 2024-05-24 13:15:15 +00:00
commit 772e73f48d
23 changed files with 704 additions and 797 deletions

View File

@ -1,13 +1,7 @@
{
"singleQuote": true,
"trailingComma": "all",
"tabWidth": 4,
"overrides": [
{
"files": ["*.ts", "*.mts"],
"options": {
"parser": "typescript"
}
}
]
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"endOfLine": "auto"
}

213
package-lock.json generated
View File

@ -9,7 +9,7 @@
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"@nostr-dev-kit/ndk": "^0.7.5",
"@nostr-dev-kit/ndk": "^0.8.1",
"axios": "^1.4.0",
"debug": "^4.3.4",
"file-type": "^18.5.0",
@ -29,7 +29,7 @@
"eslint-config-prettier": "~8.8",
"eslint-plugin-jest": "~27.2",
"jest": "~29.5",
"prettier": "~2.8",
"prettier": "3.2.5",
"rimraf": "~5.0",
"ts-api-utils": "~0.0.44",
"ts-jest": "~29.1",
@ -1644,9 +1644,9 @@
}
},
"node_modules/@nostr-dev-kit/ndk": {
"version": "0.7.7",
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.7.7.tgz",
"integrity": "sha512-IRTW16q40zzuSBkpYpDUcZJRrbw26JTeicfZN6O/2Gw7D2w6Pe42VqFwpbcP9xOnFPEGP2eNV6SwXQ3y0tjBtw==",
"version": "0.8.23",
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.8.23.tgz",
"integrity": "sha512-wX/9Cl02gCR0Kz25C/1xxGO47K13Ve1x8IISbkF/M3RTTXftYBvzB7bL8qwLaFaeqb02cMU0YVL+oKDrYzH/Ng==",
"dependencies": {
"@noble/hashes": "^1.3.1",
"@noble/secp256k1": "^2.0.0",
@ -1663,10 +1663,9 @@
"eventemitter3": "^5.0.1",
"light-bolt11-decoder": "^3.0.0",
"node-fetch": "^3.3.1",
"nostr-tools": "^1.11.2",
"nostr-tools": "^1.14.0",
"tsd": "^0.28.1",
"utf8-buffer": "^1.0.0",
"websocket-polyfill": "^0.0.3"
"utf8-buffer": "^1.0.0"
}
},
"node_modules/@pkgjs/parseargs": {
@ -2552,18 +2551,6 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"node_modules/bufferutil": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz",
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
"hasInstallScript": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/call-bind": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
@ -2792,18 +2779,6 @@
"node": ">= 8"
}
},
"node_modules/d": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
"integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==",
"dependencies": {
"es5-ext": "^0.10.64",
"type": "^2.7.2"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
@ -3177,43 +3152,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/es5-ext": {
"version": "0.10.64",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
"integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
"hasInstallScript": true,
"dependencies": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
"esniff": "^2.0.1",
"next-tick": "^1.1.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
"dependencies": {
"d": "1",
"es5-ext": "^0.10.35",
"es6-symbol": "^3.1.1"
}
},
"node_modules/es6-symbol": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz",
"integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==",
"dependencies": {
"d": "^1.0.2",
"ext": "^1.7.0"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/esbuild": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
@ -3561,20 +3499,6 @@
"node": ">=14.17"
}
},
"node_modules/esniff": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
"integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
"dependencies": {
"d": "^1.0.1",
"es5-ext": "^0.10.62",
"event-emitter": "^0.3.5",
"type": "^2.7.2"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/espree": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
@ -3658,15 +3582,6 @@
"node": ">=0.10.0"
}
},
"node_modules/event-emitter": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
"integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
"dependencies": {
"d": "1",
"es5-ext": "~0.10.14"
}
},
"node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
@ -3720,14 +3635,6 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/ext": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
"dependencies": {
"type": "^2.7.2"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -4702,11 +4609,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"node_modules/is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
@ -5884,11 +5786,6 @@
"resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g=="
},
"node_modules/next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
},
"node_modules/nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@ -5929,16 +5826,6 @@
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/node-gyp-build": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz",
"integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@ -6578,15 +6465,15 @@
}
},
"node_modules/prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=10.13.0"
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
@ -7659,11 +7546,6 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
"integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
},
"node_modules/tstl": {
"version": "2.5.16",
"resolved": "https://registry.npmjs.org/tstl/-/tstl-2.5.16.tgz",
"integrity": "sha512-+O2ybLVLKcBwKm4HymCEwZIT0PpwS3gCYnxfSDEjJEKADvIFruaQjd3m7CAKNU1c7N3X3WjVz87re7TA2A5FUw=="
},
"node_modules/tsutils": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
@ -7683,11 +7565,6 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/type": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
"integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@ -7788,14 +7665,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/typedarray-to-buffer": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
"dependencies": {
"is-typedarray": "^1.0.0"
}
},
"node_modules/typescript": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
@ -7866,18 +7735,6 @@
"punycode": "^2.1.0"
}
},
"node_modules/utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"hasInstallScript": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/utf8-buffer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/utf8-buffer/-/utf8-buffer-1.0.0.tgz",
@ -7931,44 +7788,6 @@
"node": ">= 14"
}
},
"node_modules/websocket": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz",
"integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==",
"dependencies": {
"bufferutil": "^4.0.1",
"debug": "^2.2.0",
"es5-ext": "^0.10.50",
"typedarray-to-buffer": "^3.1.5",
"utf-8-validate": "^5.0.2",
"yaeti": "^0.0.6"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/websocket-polyfill": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/websocket-polyfill/-/websocket-polyfill-0.0.3.tgz",
"integrity": "sha512-pF3kR8Uaoau78MpUmFfzbIRxXj9PeQrCuPepGE6JIsfsJ/o/iXr07Q2iQNzKSSblQJ0FiGWlS64N4pVSm+O3Dg==",
"dependencies": {
"tstl": "^2.0.7",
"websocket": "^1.0.28"
}
},
"node_modules/websocket/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/websocket/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -8086,14 +7905,6 @@
"node": ">=10"
}
},
"node_modules/yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
"integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==",
"engines": {
"node": ">=0.10.32"
}
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",

View File

@ -1,58 +1,60 @@
{
"name": "data-vending-machine-skeleton",
"version": "0.1.0",
"description": "nostr data vending machine skeleton",
"type": "module",
"engines": {
"node": "~19"
},
"keywords": [
"nostr",
"ai",
"bitcoin"
],
"devDependencies": {
"@types/jest": "~29.5",
"@types/node": "~18",
"@typescript-eslint/eslint-plugin": "~5.59",
"@typescript-eslint/parser": "~5.59",
"eslint": "~8.38",
"eslint-config-prettier": "~8.8",
"eslint-plugin-jest": "~27.2",
"jest": "~29.5",
"prettier": "~2.8",
"rimraf": "~5.0",
"ts-api-utils": "~0.0.44",
"ts-jest": "~29.1",
"typescript": "~5.0"
},
"scripts": {
"start": "node build/src/main.js",
"clean": "rimraf coverage build tmp",
"_prebuild": "npm run lint",
"build": "tsc -p tsconfig.json",
"build:watch": "tsc -w -p tsconfig.json",
"build:release": "npm run clean && tsc -p tsconfig.release.json",
"lint": "eslint . --ext .ts --ext .mts",
"test": "jest --coverage",
"prettier": "prettier --config .prettierrc --write .",
"test:watch": "jest --watch"
},
"author": "pablof7z",
"license": "MIT",
"dependencies": {
"@nostr-dev-kit/ndk": "^0.8.1",
"axios": "^1.4.0",
"debug": "^4.3.4",
"file-type": "^18.5.0",
"form-data": "^4.0.0",
"form-data-encoder": "^3.0.0",
"formdata-node": "^5.0.1",
"light-bolt11-decoder": "^3.0.0",
"lnbits": "^1.1.5",
"tslib": "~2.5"
},
"volta": {
"node": "18.12.1"
}
"name": "data-vending-machine-skeleton",
"version": "0.1.0",
"description": "nostr data vending machine skeleton",
"type": "module",
"engines": {
"node": "~19"
},
"keywords": [
"nostr",
"ai",
"bitcoin"
],
"devDependencies": {
"@types/jest": "~29.5",
"@types/node": "~18",
"@typescript-eslint/eslint-plugin": "~5.59",
"@typescript-eslint/parser": "~5.59",
"eslint": "~8.38",
"eslint-config-prettier": "~8.8",
"eslint-plugin-jest": "~27.2",
"jest": "~29.5",
"prettier": "3.2.5",
"rimraf": "~5.0",
"ts-api-utils": "~0.0.44",
"ts-jest": "~29.1",
"typescript": "~5.0"
},
"scripts": {
"start": "node build/src/main.js",
"clean": "rimraf coverage build tmp",
"_prebuild": "npm run lint",
"build": "tsc -p tsconfig.json",
"build:watch": "tsc -w -p tsconfig.json",
"build:release": "npm run clean && tsc -p tsconfig.release.json",
"lint": "eslint . --ext .ts --ext .mts",
"test": "jest --coverage",
"prettier": "prettier --config .prettierrc --write .",
"test:watch": "jest --watch",
"formatter:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
"formatter:fix": "prettier --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\""
},
"author": "pablof7z",
"license": "MIT",
"dependencies": {
"@nostr-dev-kit/ndk": "^0.8.1",
"axios": "^1.4.0",
"debug": "^4.3.4",
"file-type": "^18.5.0",
"form-data": "^4.0.0",
"form-data-encoder": "^3.0.0",
"formdata-node": "^5.0.1",
"light-bolt11-decoder": "^3.0.0",
"lnbits": "^1.1.5",
"tslib": "~2.5"
},
"volta": {
"node": "18.12.1"
}
}

View File

@ -64,8 +64,8 @@ importers:
specifier: ~29.5
version: 29.5.0(@types/node@18.16.19)
prettier:
specifier: ~2.8
version: 2.8.8
specifier: 3.2.5
version: 3.2.5
rimraf:
specifier: ~5.0
version: 5.0.1
@ -2171,9 +2171,9 @@ packages:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
prettier@2.8.8:
resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==}
engines: {node: '>=10.13.0'}
prettier@3.2.5:
resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
engines: {node: '>=14'}
hasBin: true
pretty-format@29.6.1:
@ -5246,7 +5246,7 @@ snapshots:
prelude-ls@1.2.1: {}
prettier@2.8.8: {}
prettier@3.2.5: {}
pretty-format@29.6.1:
dependencies:

View File

@ -1,37 +1,37 @@
import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import fs from 'fs';
import { log, configFile } from '../main.js';
import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk'
import fs from 'fs'
import { log, configFile } from '../main.js'
type IConfig = {
key: string;
discount?: number;
undercut?: number;
processWithoutPaymentLimit?: number;
serveResultsWithoutPaymentLimit?: number;
key: string
discount?: number
undercut?: number
processWithoutPaymentLimit?: number
serveResultsWithoutPaymentLimit?: number
}
export function getConfig(): IConfig {
let config: IConfig;
let config: IConfig
if (fs.existsSync(configFile)) {
config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
if (fs.existsSync(configFile)) {
config = JSON.parse(fs.readFileSync(configFile, 'utf8'))
}
if (!config) {
log('Generating new config')
config = {
key: NDKPrivateKeySigner.generate().privateKey
}
if (!config) {
log('Generating new config')
config = {
key: NDKPrivateKeySigner.generate().privateKey,
};
const signer = NDKPrivateKeySigner.generate()
config.key = signer.privateKey
const signer = NDKPrivateKeySigner.generate();
config.key = signer.privateKey;
saveConfig(config)
}
saveConfig(config);
}
return config;
return config
}
export function saveConfig(config: IConfig) {
fs.writeFileSync(configFile, JSON.stringify(config));
fs.writeFileSync(configFile, JSON.stringify(config))
}

View File

@ -1,43 +1,43 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { log } from '../main.js';
import axios from 'axios';
import { NDKEvent } from '@nostr-dev-kit/ndk'
import { log } from '../main.js'
import axios from 'axios'
export async function blockChainBlockNumberJob(
event: NDKEvent,
event: NDKEvent
): Promise<string> {
log('New blockChain-block-number job', event.rawEvent());
log('New blockChain-block-number job', event.rawEvent())
const input = event.tagValue('i');
const input = event.tagValue('i')
const blockChainUrl = `https://blockchain.info/blocks/${input}?format=json`;
const blockChainUrl = `https://blockchain.info/blocks/${input}?format=json`
const output = await axios
.get(blockChainUrl)
.then((res) => {
const closestObject = findClosestObject(res.data, input);
return closestObject.block_index.toString();
})
.catch((err) => {
console.log('err in blockChain request :>> ', err);
return Promise.reject(err);
});
const output = await axios
.get(blockChainUrl)
.then((res) => {
const closestObject = findClosestObject(res.data, input)
return closestObject.block_index.toString()
})
.catch((err) => {
console.log('err in blockChain request :>> ', err)
return Promise.reject(err)
})
return output;
return output
}
// todo: define type for array
// Function to find the object with the closest timestamp
function findClosestObject(array, timestamp) {
let closestObject = null;
let minDifference = Infinity;
let closestObject = null
let minDifference = Infinity
array.forEach((obj) => {
const difference = Math.abs(obj.time - timestamp);
if (difference < minDifference) {
minDifference = difference;
closestObject = obj;
}
});
array.forEach((obj) => {
const difference = Math.abs(obj.time - timestamp)
if (difference < minDifference) {
minDifference = difference
closestObject = obj
}
})
return closestObject;
return closestObject
}

View File

@ -0,0 +1,57 @@
import { NDKEvent } from '@nostr-dev-kit/ndk'
import { log } from '../main.js'
import axios from 'axios'
import { RelayInfo } from '../types/index.js'
export const relayInfoJob = async (event: NDKEvent): Promise<string> => {
log('New relay-info job', event.rawEvent())
const input: string = event.tagValue('i')
let relays: string[] = []
try {
const relaysArr = JSON.parse(input)
// input is a string containing an array of strings representing multiple relay URIs
relays = relaysArr
} catch (err) {
// input is a string containing a string representing single relay URI
relays.push(input)
}
const prefixes = { wss: 'wss://', https: 'https://', http: 'http://' }
const headers = {
headers: { Accept: 'application/nostr+json' }
}
const requests = relays.map((relay) => {
if (relay.startsWith(prefixes.wss)) {
relay = relay.replace(prefixes.wss, '')
}
return axios
.get<RelayInfo>((relay = `${prefixes.https}${relay}`), headers)
.catch(() => {
return axios
.get<RelayInfo>(`${prefixes.http}${relay}`, headers)
.catch(() => undefined)
})
})
let responses = await Promise.all(requests)
responses = responses.filter((response) => response !== undefined)
const relaysInfo: { [key: string]: RelayInfo } = {}
responses.forEach((response) => {
const relayURI = response.config.url
.replace(prefixes.https, prefixes.wss)
.replace(prefixes.http, prefixes.wss)
relaysInfo[relayURI] = response.data
})
return Promise.resolve(JSON.stringify(relaysInfo))
}

View File

@ -1,142 +1,144 @@
import { NDKEvent, type NostrEvent } from '@nostr-dev-kit/ndk';
import axios from 'axios';
import FormData from 'form-data';
import { log } from '../main.js';
import { fetchFileFromInput } from '../utils/fetch-file-from-input.js';
import { ndk } from '../main.js';
import { NDKEvent, type NostrEvent } from '@nostr-dev-kit/ndk'
import axios from 'axios'
import FormData from 'form-data'
import { log } from '../main.js'
import { fetchFileFromInput } from '../utils/fetch-file-from-input.js'
import { ndk } from '../main.js'
// import { fileTypeFromFile, type FileTypeResult } from 'file-type';
import { exec } from 'child_process';
import fs from 'fs';
import { createInvoice } from '../utils/lnbits.js';
import { exec } from 'child_process'
import fs from 'fs'
import { createInvoice } from '../utils/lnbits.js'
export async function speechToTextJob(event: NDKEvent): Promise<string> {
log("New speech-to-text job", event.rawEvent());
log('New speech-to-text job', event.rawEvent())
// fetch the input file
const file = await fetchFile(event);
log(`file: ${file}`);
// fetch the input file
const file = await fetchFile(event)
log(`file: ${file}`)
if (!file) {
return undefined;
}
if (!file) {
return undefined
}
// check if file exists
// if (!fs.existsSync(file)) {
// log(`file does not exist: ${file}`);
// return undefined;
// }
// check if file exists
// if (!fs.existsSync(file)) {
// log(`file does not exist: ${file}`);
// return undefined;
// }
// let fileType: FileTypeResult;
// let fileType: FileTypeResult;
// try {
// fileType = await fileTypeFromFile(file);
// log(`fileType: ${fileType}`);
// } catch (error) {
// log(error);
// }
// try {
// fileType = await fileTypeFromFile(file);
// log(`fileType: ${fileType}`);
// } catch (error) {
// log(error);
// }
const whisperCommand = (resolve: any, file: string) => {
setTimeout(() => {
const whisperCommand = (resolve: any, file: string) => {
setTimeout(() => {
if (!fs.existsSync(file)) {
log(`file does not exist: ${file}`)
return undefined
}
const formData = new FormData()
const a = fs.createReadStream(file)
console.log({ a })
if (!fs.existsSync(file)) {
log(`file does not exist: ${file}`);
return undefined;
}
const formData = new FormData();
const a = fs.createReadStream(file);
console.log({a});
formData.append('file', a)
formData.append('model', 'whisper-1')
formData.append('file', a);
formData.append('model', 'whisper-1');
axios
.post('http://127.0.0.1:10111/v1/audio/transcriptions', formData, {
headers: {
Authorization: 'Bearer fsdfdsf',
...formData.getHeaders()
}
})
.then((response) => {
const { text } = response.data
console.log({ text })
resolve(text)
// Handle response
})
.catch((error) => {
console.log({ error })
// Handle error
})
}, 1000)
}
axios.post('http://127.0.0.1:10111/v1/audio/transcriptions', formData, {
headers: {
'Authorization': 'Bearer fsdfdsf',
...formData.getHeaders(),
},
}).then(response => {
const { text } = response.data;
console.log({text});
resolve(text);
// Handle response
})
.catch(error => {
console.log({error});
// Handle error
});
}, 1000);
}
// const input = event.tagValue('i');
const range = event.getMatchingTags('param').find((tag) => tag[1] === 'range')
return new Promise((resolve) => whisperCommand(resolve, file))
// const input = event.tagValue('i');
const range = event.getMatchingTags('param').find((tag) => tag[1] === 'range');
return new Promise((resolve) => whisperCommand(resolve, file));
if (range) {
return new Promise((resolve) => {
const startTime = range[2];
const endTime = range[3];
const randomName = Math.random().toString(36).substring(7)+'.mp3';
const command = `ffmpeg -ss ${startTime} -to ${endTime} -i ${file} -vn -acodec copy ${randomName}`;
console.log({startTime, endTime, randomName, command});
exec(command, (error, stderr, stdout) => {
console.log('ffmpeg', {error, stderr, stdout});
if (range) {
return new Promise((resolve) => {
const startTime = range[2]
const endTime = range[3]
const randomName = Math.random().toString(36).substring(7) + '.mp3'
const command = `ffmpeg -ss ${startTime} -to ${endTime} -i ${file} -vn -acodec copy ${randomName}`
console.log({ startTime, endTime, randomName, command })
exec(command, (error, stderr, stdout) => {
console.log('ffmpeg', { error, stderr, stdout })
whisperCommand(resolve, randomName);
});
});
} else {
return new Promise((resolve) => whisperCommand(resolve, file));
}
whisperCommand(resolve, randomName)
})
})
} else {
return new Promise((resolve) => whisperCommand(resolve, file))
}
}
interface ICompleteParams {
output: string;
output: string
}
export async function addAmount(event: NDKEvent, amount: number, includeInvoice = true): Promise<void> {
const tag = ['amount', amount.toString()];
export async function addAmount(
event: NDKEvent,
amount: number,
includeInvoice = true
): Promise<void> {
const tag = ['amount', amount.toString()]
if (includeInvoice) {
const invoice = await createInvoice(amount / 1000);
tag.push(invoice.payment_request);
}
if (includeInvoice) {
const invoice = await createInvoice(amount / 1000)
tag.push(invoice.payment_request)
}
event.tags.push(tag);
event.tags.push(tag)
}
export async function complete(
jobRequest: NDKEvent,
amount: number,
completeParams: ICompleteParams,
includeInvoice?: boolean
jobRequest: NDKEvent,
amount: number,
completeParams: ICompleteParams,
includeInvoice?: boolean
): Promise<void> {
const jobResult = new NDKEvent(ndk, {
kind: 68002,
content: completeParams.output,
tags: [
[ 'status', 'success' ]
]
} as NostrEvent);
const jobResult = new NDKEvent(ndk, {
kind: 68002,
content: completeParams.output,
tags: [['status', 'success']]
} as NostrEvent)
if (amount > 0) {
await addAmount(jobResult, amount, includeInvoice);
}
jobResult.tag(jobRequest);
if (amount > 0) {
await addAmount(jobResult, amount, includeInvoice)
}
jobResult.tag(jobRequest)
await jobResult.sign()
log(jobResult.rawEvent())
await jobResult.sign();
log(jobResult.rawEvent());
await jobResult.publish();
return jobResult;
await jobResult.publish()
return jobResult
}
export async function fetchFile(event: NDKEvent): Promise<string> {
const inputTags = event.getMatchingTags("i");
const inputTags = event.getMatchingTags('i')
if (inputTags.length !== 1) {
throw new Error(`Incorrect number of inputs: ${inputTags.length}`);
}
if (inputTags.length !== 1) {
throw new Error(`Incorrect number of inputs: ${inputTags.length}`)
}
return fetchFileFromInput(inputTags[0]);
}
return fetchFileFromInput(inputTags[0])
}

View File

@ -1,11 +1,11 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { validateJobRequest } from '../validations/index.js';
import { inProgress } from '../jobs/reactions/in-progress.js';
import { NDKEvent } from '@nostr-dev-kit/ndk'
import { validateJobRequest } from '../validations/index.js'
import { inProgress } from '../jobs/reactions/in-progress.js'
export async function onNewSummarizationJob(event: NDKEvent): Promise<void> {
console.log("New summarization job");
console.log('New summarization job')
await validateJobRequest(event);
await validateJobRequest(event)
inProgress(event);
inProgress(event)
}

View File

@ -1,17 +1,17 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { log } from '../main.js';
import { getConfig } from '../config/index.js';
import { NDKEvent } from '@nostr-dev-kit/ndk'
import { log } from '../main.js'
import { getConfig } from '../config/index.js'
export async function priceJob(event: NDKEvent): Promise<number> {
const config = getConfig();
const bidTag = event.tagValue('bid');
let bidAmount = bidTag ? parseInt(bidTag) : 1001 * 1000;
const config = getConfig()
const bidTag = event.tagValue('bid')
let bidAmount = bidTag ? parseInt(bidTag) : 1001 * 1000
if (config.discount) {
bidAmount = bidAmount * config.discount;
}
if (config.discount) {
bidAmount = bidAmount * config.discount
}
log(`bid amount: ${bidAmount} (${config.discount??'no'} discount)`);
log(`bid amount: ${bidAmount} (${config.discount ?? 'no'} discount)`)
return bidAmount;
return bidAmount
}

View File

@ -1,19 +1,17 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { log, ndk } from '../../main.js';
import { NDKEvent } from '@nostr-dev-kit/ndk'
import { log, ndk } from '../../main.js'
export async function inProgress(event: NDKEvent): Promise<NDKEvent> {
log("marking job as in progress");
log('marking job as in progress')
const reactEvent = new NDKEvent(ndk, {
kind: 68003,
content: "👍",
tags: [
[ "status", "started" ],
]
})
const reactEvent = new NDKEvent(ndk, {
kind: 68003,
content: '👍',
tags: [['status', 'started']]
})
reactEvent.tag(event, "job");
await reactEvent.sign();
await reactEvent.publish();
return reactEvent;
reactEvent.tag(event, 'job')
await reactEvent.sign()
await reactEvent.publish()
return reactEvent
}

View File

@ -1,25 +1,23 @@
import { NDKEvent, NDKTag } from '@nostr-dev-kit/ndk';
import { log, ndk } from '../../main.js';
import { NDKEvent, NDKTag } from '@nostr-dev-kit/ndk'
import { log, ndk } from '../../main.js'
export async function publishStatus(
event: NDKEvent,
status: string,
extraTags: NDKTag[] = []): Promise<NDKEvent> {
log("marking job as finished");
event: NDKEvent,
status: string,
extraTags: NDKTag[] = []
): Promise<NDKEvent> {
log('marking job as finished')
const reactEvent = new NDKEvent(ndk, {
kind: 68003,
content: "👍",
tags: [
[ "status", status ],
...extraTags
]
})
const reactEvent = new NDKEvent(ndk, {
kind: 68003,
content: '👍',
tags: [['status', status], ...extraTags]
})
console.log({extraTags})
console.log({ extraTags })
reactEvent.tag(event, "job");
await reactEvent.sign();
await reactEvent.publish();
return reactEvent;
reactEvent.tag(event, 'job')
await reactEvent.sign()
await reactEvent.publish()
return reactEvent
}

View File

@ -1,8 +1,8 @@
import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import { getConfig } from './config/index.js';
import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk'
import { getConfig } from './config/index.js'
export default function getSigner(): NDKPrivateKeySigner {
const config = getConfig();
const config = getConfig()
return new NDKPrivateKeySigner(config.key!);
}
return new NDKPrivateKeySigner(config.key!)
}

View File

@ -1,240 +1,249 @@
import NDK, { NDKEvent } from '@nostr-dev-kit/ndk';
import debug from 'debug';
import { onNewSummarizationJob } from './job-types/summarization.js';
import { complete, speechToTextJob } from './job-types/speech-to-text.js';
import getSigner from './local-signer.js';
import { requirePayment, validateJobRequest } from './validations/index.js';
import { inProgress } from './jobs/reactions/in-progress.js';
import { publishStatus } from './jobs/reactions/status.js';
import { priceJob } from './jobs/price.js';
import { getConfig } from './config/index.js';
import { decode } from 'light-bolt11-decoder';
import { checkInvoiceStatus } from './utils/lnbits.js';
import { blockChainBlockNumberJob } from './job-types/blockChain-block-number.js';
import NDK, { NDKEvent } from '@nostr-dev-kit/ndk'
import debug from 'debug'
import { onNewSummarizationJob } from './job-types/summarization.js'
import { complete, speechToTextJob } from './job-types/speech-to-text.js'
import getSigner from './local-signer.js'
import { requirePayment, validateJobRequest } from './validations/index.js'
import { inProgress } from './jobs/reactions/in-progress.js'
import { publishStatus } from './jobs/reactions/status.js'
import { priceJob } from './jobs/price.js'
import { getConfig } from './config/index.js'
import { decode } from 'light-bolt11-decoder'
import { checkInvoiceStatus } from './utils/lnbits.js'
import { blockChainBlockNumberJob } from './job-types/blockChain-block-number.js'
import { relayInfoJob } from './job-types/relay-info.js'
import { JobType, JobTypes } from './types/index.js'
export const log = debug('fool-me-once-dvm');
export const log = debug('fool-me-once-dvm')
export const configFile =
process.argv[2] || `${process.env.HOME}/.fool-me-once.json`;
process.argv[2] || `${process.env.HOME}/.fool-me-once.json`
log('configFile', { configFile });
log('configFile', { configFile })
export const ndk = new NDK({
explicitRelayUrls: [
'wss://relay.damus.io',
'wss://relay.primal.net',
'wss://relayable.org',
],
signer: getSigner(),
});
await ndk.connect(2000);
log('connected');
explicitRelayUrls: [
'wss://relay.damus.io',
'wss://relay.primal.net',
'wss://relayable.org'
],
signer: getSigner()
})
await ndk.connect(2000)
log('connected')
const subs = ndk.subscribe(
{
kinds: [68001 as number],
since: Math.floor(Date.now() / 1000),
'#j': ['summarize', 'explain'],
},
{ closeOnEose: false },
);
{
kinds: [68001 as number],
since: Math.floor(Date.now() / 1000),
'#j': [JobTypes.Summarize, JobTypes.Explain]
},
{ closeOnEose: false }
)
const speechToTextSub = ndk.subscribe(
{
kinds: [68001 as number],
since: Math.floor(Date.now() / 1000),
'#j': ['speech-to-text'],
},
{ closeOnEose: false },
);
{
kinds: [68001 as number],
since: Math.floor(Date.now() / 1000),
'#j': [JobTypes.SpeechToText]
},
{ closeOnEose: false }
)
const blockChainBlockNumberSub = ndk.subscribe(
{
kinds: [68001 as number],
since: Math.floor(Date.now() / 1000),
'#j': ['blockChain-block-number'],
},
{ closeOnEose: false },
);
{
kinds: [68001 as number],
since: Math.floor(Date.now() / 1000),
'#j': [JobTypes.BlockChainBlockNumber]
},
{ closeOnEose: false }
)
const relayInfoSub = ndk.subscribe(
{
kinds: [68001 as number],
since: Math.floor(Date.now() / 1000),
'#j': [JobTypes.RelayInfo]
},
{ closeOnEose: false }
)
subs.on('event', (e) => processJobEvent(e, 'summarize'));
speechToTextSub.on('event', (e) => processJobEvent(e, 'speech-to-text'));
subs.on('event', (e) => processJobEvent(e, JobTypes.Summarize))
speechToTextSub.on('event', (e) => processJobEvent(e, JobTypes.SpeechToText))
blockChainBlockNumberSub.on('event', (e) =>
processJobEvent(e, 'blockChain-block-number'),
);
processJobEvent(e, JobTypes.BlockChainBlockNumber)
)
relayInfoSub.on('event', (e) => processJobEvent(e, JobTypes.RelayInfo))
type JobType =
| 'summarize'
| 'explain'
| 'speech-to-text'
| 'blockChain-block-number';
const freeJobs: string[] = [JobTypes.BlockChainBlockNumber, JobTypes.RelayInfo]
async function processJobEvent(event: NDKEvent, type: JobType): Promise<void> {
const config = getConfig();
let jobAmount =
type === 'blockChain-block-number' ? 0 : await priceJob(event);
let output: any;
let payReqEvent: NDKEvent;
let paidAmount = 0;
const config = getConfig()
let jobAmount = freeJobs.includes(type) ? 0 : await priceJob(event)
let output: any
let payReqEvent: NDKEvent
let paidAmount = 0
const waitForPaymentBeforeProcessing = (): boolean => {
const processWithoutPaymentLimit =
config.processWithoutPaymentLimit ?? 500;
log('waitForPaymentBeforeProcessing', {
jobAmount,
processWithoutPaymentLimit,
});
return jobAmount && jobAmount > processWithoutPaymentLimit * 1000;
};
const waitForPaymentBeforeProcessing = (): boolean => {
const processWithoutPaymentLimit = config.processWithoutPaymentLimit ?? 500
log('waitForPaymentBeforeProcessing', {
jobAmount,
processWithoutPaymentLimit
})
return jobAmount && jobAmount > processWithoutPaymentLimit * 1000
}
const waitForPaymentBeforePublishingResult = (): boolean => {
const serveResultsWithoutPaymentLimit =
config.serveResultsWithoutPaymentLimit ?? 1000;
log('waitForPaymentBeforeProcessing', { jobAmount });
return jobAmount && jobAmount > serveResultsWithoutPaymentLimit * 1000;
};
const waitForPaymentBeforePublishingResult = (): boolean => {
const serveResultsWithoutPaymentLimit =
config.serveResultsWithoutPaymentLimit ?? 1000
log('waitForPaymentBeforeProcessing', { jobAmount })
return jobAmount && jobAmount > serveResultsWithoutPaymentLimit * 1000
}
const missingAmount = (): number => jobAmount - paidAmount;
const missingAmount = (): number => jobAmount - paidAmount
const reqPayment = async (): Promise<void> => {
payReqEvent = await requirePayment(event, missingAmount(), true);
const reqPayment = async (): Promise<void> => {
payReqEvent = await requirePayment(event, missingAmount(), true)
if (config.undercut) {
startUndercutting();
}
};
const startUndercutting = async (): Promise<void> => {
const undercutSub = ndk.subscribe(
{
kinds: [68002 as number, 68003 as number],
...event.filter(),
},
{ closeOnEose: false, groupable: false },
);
undercutSub.on('event', async (e) => {
if (e.pubkey === payReqEvent.pubkey) return;
// check if this is a payment request
const amountValue = e.tagValue('amount');
if (!amountValue) return;
log(`found someone else's bid`, amountValue);
// check if it's more-or-less than the current bid
const amount = parseInt(amountValue);
if (amount > jobAmount) return;
// if so, undercut
jobAmount = Math.round(amount * config.undercut);
log(`undercutting to ${jobAmount}`);
setTimeout(async () => {
payReqEvent = await requirePayment(event, jobAmount, true);
}, 5000);
});
};
const waitForPaymentViaZap = (payReqEvent, resolve, reject) => {
const zapmon = ndk.subscribe(
{
kinds: [68002, 9735],
...payReqEvent.filter(),
},
{ closeOnEose: false },
);
zapmon.on('event', (e) => {
log(`received a ${e.kind} for the payment request`, e.rawEvent());
// TODO: validate amount, zapper, etc
if (e.kind === 9735) {
// TODO: This needs to check the actual zap
paidAmount = jobAmount;
// zapmon.close();
resolve();
} else if (e.kind === 68003) {
zapmon.stop();
reject();
}
});
};
const waitForPaymentViaLNInvoice = (payReqEvent, resolve) => {
const amountTag = payReqEvent.getMatchingTags('amount')[0];
const bolt11 = amountTag[2];
if (!bolt11) return;
const invoice = decode(bolt11);
const pr = invoice.payment_hash;
log({ invoice });
log({ pr });
const checkInterval = setInterval(() => {
checkInvoiceStatus(invoice).then((status) => {
if (status.paid) {
log('invoice paid');
paidAmount = jobAmount;
clearInterval(checkInterval);
resolve();
}
});
}, 2000);
};
const waitForPayment = async (payReqEvent: NDKEvent): Promise<void> => {
log('waitForPayment');
const promise = new Promise<void>((resolve, reject) => {
waitForPaymentViaZap(payReqEvent, resolve, reject);
waitForPaymentViaLNInvoice(payReqEvent, resolve);
});
return promise;
};
const startProcessing = async () => {
log('startProcessing');
await inProgress(event);
switch (type) {
case 'summarize': {
output = await onNewSummarizationJob(event);
break;
}
case 'speech-to-text': {
output = await speechToTextJob(event);
break;
}
case 'blockChain-block-number': {
output = await blockChainBlockNumberJob(event);
break;
}
}
};
const publishResult = (): void => {
log('publishResult');
complete(event, missingAmount(), { output });
};
await validateJobRequest(event);
if (jobAmount > paidAmount && waitForPaymentBeforeProcessing()) {
await reqPayment();
await waitForPayment(payReqEvent);
if (config.undercut) {
startUndercutting()
}
}
await startProcessing();
const startUndercutting = async (): Promise<void> => {
const undercutSub = ndk.subscribe(
{
kinds: [68002 as number, 68003 as number],
...event.filter()
},
{ closeOnEose: false, groupable: false }
)
undercutSub.on('event', async (e) => {
if (e.pubkey === payReqEvent.pubkey) return
await publishStatus(event, 'finished').catch((err) =>
console.log('err :>> ', err),
);
// check if this is a payment request
const amountValue = e.tagValue('amount')
if (!amountValue) return
if (jobAmount > paidAmount && waitForPaymentBeforePublishingResult()) {
await reqPayment();
await waitForPayment(payReqEvent);
log(`done with wait for publish`);
log(`found someone else's bid`, amountValue)
// check if it's more-or-less than the current bid
const amount = parseInt(amountValue)
if (amount > jobAmount) return
// if so, undercut
jobAmount = Math.round(amount * config.undercut)
log(`undercutting to ${jobAmount}`)
setTimeout(async () => {
payReqEvent = await requirePayment(event, jobAmount, true)
}, 5000)
})
}
const waitForPaymentViaZap = (payReqEvent, resolve, reject) => {
const zapmon = ndk.subscribe(
{
kinds: [68002, 9735],
...payReqEvent.filter()
},
{ closeOnEose: false }
)
zapmon.on('event', (e) => {
log(`received a ${e.kind} for the payment request`, e.rawEvent())
// TODO: validate amount, zapper, etc
if (e.kind === 9735) {
// TODO: This needs to check the actual zap
paidAmount = jobAmount
// zapmon.close();
resolve()
} else if (e.kind === 68003) {
zapmon.stop()
reject()
}
})
}
const waitForPaymentViaLNInvoice = (payReqEvent, resolve) => {
const amountTag = payReqEvent.getMatchingTags('amount')[0]
const bolt11 = amountTag[2]
if (!bolt11) return
const invoice = decode(bolt11)
const pr = invoice.payment_hash
log({ invoice })
log({ pr })
const checkInterval = setInterval(() => {
checkInvoiceStatus(invoice).then((status) => {
if (status.paid) {
log('invoice paid')
paidAmount = jobAmount
clearInterval(checkInterval)
resolve()
}
})
}, 2000)
}
const waitForPayment = async (payReqEvent: NDKEvent): Promise<void> => {
log('waitForPayment')
const promise = new Promise<void>((resolve, reject) => {
waitForPaymentViaZap(payReqEvent, resolve, reject)
waitForPaymentViaLNInvoice(payReqEvent, resolve)
})
return promise
}
const startProcessing = async () => {
log('startProcessing')
await inProgress(event)
switch (type) {
case JobTypes.Summarize: {
output = await onNewSummarizationJob(event)
break
}
case JobTypes.SpeechToText: {
output = await speechToTextJob(event)
break
}
case JobTypes.BlockChainBlockNumber: {
output = await blockChainBlockNumberJob(event)
break
}
case JobTypes.RelayInfo: {
output = await relayInfoJob(event)
break
}
}
}
await publishResult();
const publishResult = (): void => {
log('publishResult')
complete(event, missingAmount(), { output })
}
await validateJobRequest(event)
if (jobAmount > paidAmount && waitForPaymentBeforeProcessing()) {
await reqPayment()
await waitForPayment(payReqEvent)
}
await startProcessing()
await publishStatus(event, 'finished').catch((err) =>
console.log('err :>> ', err)
)
if (jobAmount > paidAmount && waitForPaymentBeforePublishingResult()) {
await reqPayment()
await waitForPayment(payReqEvent)
log(`done with wait for publish`)
}
await publishResult()
}

2
src/types/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './relay.js'
export * from './job.js'

14
src/types/job.ts Normal file
View File

@ -0,0 +1,14 @@
export type JobType =
| 'summarize'
| 'explain'
| 'speech-to-text'
| 'blockChain-block-number'
| 'relay-info'
export enum JobTypes {
Summarize = 'summarize',
Explain = 'explain',
SpeechToText = 'speech-to-text',
BlockChainBlockNumber = 'blockChain-block-number',
RelayInfo = 'relay-info'
}

9
src/types/relay.ts Normal file
View File

@ -0,0 +1,9 @@
export interface RelayInfo {
name: string
description: string
pubkey: string
contact: string
supported_nips: number[]
software: string
version: string
}

View File

@ -1,28 +1,28 @@
import { NDKTag } from '@nostr-dev-kit/ndk';
import axios from 'axios';
import fs from 'fs';
import { NDKTag } from '@nostr-dev-kit/ndk'
import axios from 'axios'
import fs from 'fs'
export async function fetchFileFromInput(input: NDKTag): Promise<string> {
switch (input[2]) {
case 'url': {
const url = input[1];
const fileExtension = url.split('.').pop();
switch (input[2]) {
case 'url': {
const url = input[1]
const fileExtension = url.split('.').pop()
// download the file
// save it to the local filesystem
// return the path to the file
const response = await axios.get(url, {
responseType: 'stream'
});
// download the file
// save it to the local filesystem
// return the path to the file
const response = await axios.get(url, {
responseType: 'stream'
})
const randomName = Math.random().toString(36).substring(7);
const path = `./${randomName}.${fileExtension}`;
const randomName = Math.random().toString(36).substring(7)
const path = `./${randomName}.${fileExtension}`
await response.data.pipe(fs.createWriteStream(path));
await response.data.pipe(fs.createWriteStream(path))
return path;
}
return path
}
}
return undefined;
}
return undefined
}

View File

@ -1,38 +1,38 @@
import _LNBits from "lnbits";
import _LNBits from 'lnbits'
let LNBits: any;
let LNBits: any
if (_LNBits.default) {
LNBits = _LNBits.default;
LNBits = _LNBits.default
} else {
LNBits = _LNBits;
LNBits = _LNBits
}
function getWallet (): any {
return LNBits({
adminKey: "",
invoiceReadKey: 'bfbcb5b116c04179bc618ab78265a939',
endpoint: 'https://legend.lnbits.com'
});
function getWallet(): any {
return LNBits({
adminKey: '',
invoiceReadKey: 'bfbcb5b116c04179bc618ab78265a939',
endpoint: 'https://legend.lnbits.com'
})
}
export async function createInvoice(amount: number): Promise<any> {
const { wallet } = getWallet();
const { wallet } = getWallet()
const newInvoice = await wallet.createInvoice({
amount: amount,
memo: 'data vending machine',
out: false,
});
const newInvoice = await wallet.createInvoice({
amount: amount,
memo: 'data vending machine',
out: false
})
return newInvoice;
return newInvoice
}
export async function checkInvoiceStatus(invoice): Promise<any> {
const { wallet } = getWallet();
const invoiceStatus = await wallet.checkInvoice({
payment_hash: invoice.payment_hash,
});
const { wallet } = getWallet()
const invoiceStatus = await wallet.checkInvoice({
payment_hash: invoice.payment_hash
})
return invoiceStatus;
}
return invoiceStatus
}

View File

@ -1,12 +1,12 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { NDKEvent } from '@nostr-dev-kit/ndk'
export default async function validateExpiration(event: NDKEvent) {
const expTag = event.tagValue('exp');
const timeNow = Math.floor(Date.now() / 1000);
const expTag = event.tagValue('exp')
const timeNow = Math.floor(Date.now() / 1000)
if (expTag && parseInt(expTag) < timeNow - 10) {
throw new Error('Job expired');
}
if (expTag && parseInt(expTag) < timeNow - 10) {
throw new Error('Job expired')
}
return
}
return
}

View File

@ -1,40 +1,42 @@
import { NDKEvent, type NostrEvent } from '@nostr-dev-kit/ndk';
import validateExpiration from './expiration.js';
import validateRequester from './requester.js';
import validateNoRecentResults from './no-recent-results.js';
import { ndk } from '../main.js';
import { addAmount } from '../job-types/speech-to-text.js';
import { NDKEvent, type NostrEvent } from '@nostr-dev-kit/ndk'
import validateExpiration from './expiration.js'
import validateRequester from './requester.js'
import validateNoRecentResults from './no-recent-results.js'
import { ndk } from '../main.js'
import { addAmount } from '../job-types/speech-to-text.js'
export async function validateJobRequest(event: NDKEvent): Promise<void> {
await validateExpiration(event);
await validateRequester(event);
await validateNoRecentResults(event);
await validateExpiration(event)
await validateRequester(event)
await validateNoRecentResults(event)
}
export async function requirePayment(event: NDKEvent, amount?: number, publish?: boolean): Promise<NDKEvent> {
if (!amount) {
const bidTag = event.tagValue('bid');
amount = bidTag ? parseInt(bidTag) : undefined;
}
export async function requirePayment(
event: NDKEvent,
amount?: number,
publish?: boolean
): Promise<NDKEvent> {
if (!amount) {
const bidTag = event.tagValue('bid')
amount = bidTag ? parseInt(bidTag) : undefined
}
if (!amount) {
throw new Error("No amount specified");
}
if (!amount) {
throw new Error('No amount specified')
}
const payReq = new NDKEvent(ndk, {
kind: 68003,
content: "`Please pay for this job`",
tags: [
["status", "payment-required"],
]
} as NostrEvent);
await addAmount(payReq, amount);
payReq.tag(event, "job");
const payReq = new NDKEvent(ndk, {
kind: 68003,
content: '`Please pay for this job`',
tags: [['status', 'payment-required']]
} as NostrEvent)
await addAmount(payReq, amount)
payReq.tag(event, 'job')
await payReq.sign();
console.log(payReq.rawEvent());
await payReq.sign()
console.log(payReq.rawEvent())
if (publish !== false) await payReq.publish();
if (publish !== false) await payReq.publish()
return payReq;
}
return payReq
}

View File

@ -1,13 +1,18 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { ndk } from '../main.js';
import { NDKEvent } from '@nostr-dev-kit/ndk'
import { ndk } from '../main.js'
export default async function validateNoRecentResults(event: NDKEvent): Promise<void> {
const results = ndk.fetchEvents({
kinds: [68002 as number],
"#e": [event.id],
}, { groupable: false })
export default async function validateNoRecentResults(
event: NDKEvent
): Promise<void> {
const results = ndk.fetchEvents(
{
kinds: [68002 as number],
'#e': [event.id]
},
{ groupable: false }
)
if (results.length > 0) {
throw new Error(`This job already has ${results.length} results`);
}
}
if (results.length > 0) {
throw new Error(`This job already has ${results.length} results`)
}
}

View File

@ -1,5 +1,9 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { NDKEvent } from '@nostr-dev-kit/ndk'
export default async function validateRequester(event: NDKEvent): Promise<void> {
if (event) { /* empty */ }
}
export default async function validateRequester(
event: NDKEvent
): Promise<void> {
if (event) {
/* empty */
}
}