feat: upgrade bot
This commit is contained in:
parent
b417ff27d7
commit
eaaa7edc78
30
packages/bot/README.md
Normal file
30
packages/bot/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# @snort/bot
|
||||
|
||||
Simple live stream event chat bot (NIP-53)
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
import { parseNostrLink } from "@snort/system";
|
||||
import { SnortBot } from "../src/index";
|
||||
|
||||
// listen to chat events on every NoGood live stream
|
||||
const noGoodLink = parseNostrLink("npub12hcytyr8fumy3axde8wgeced523gyp6v6zczqktwuqeaztfc2xzsz3rdp4");
|
||||
|
||||
// Run a simple bot
|
||||
SnortBot.simple("example")
|
||||
.link(noGoodLink)
|
||||
.relay("wss://relay.damus.io")
|
||||
.relay("wss://nos.lol")
|
||||
.relay("wss://relay.nostr.band")
|
||||
.profile({
|
||||
name: "PingBot",
|
||||
picture: "https://nostr.download/572f5ff8286e8c719196f904fed24aef14586ec8181c14b09efa726682ef48ef",
|
||||
lud16: "kieran@zap.stream",
|
||||
about: "An example bot",
|
||||
})
|
||||
.command("!ping", h => {
|
||||
h.reply("PONG!");
|
||||
})
|
||||
.run();
|
||||
```
|
20
packages/bot/example/simple.ts
Normal file
20
packages/bot/example/simple.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { parseNostrLink } from "@snort/system";
|
||||
import { SnortBot } from "../src/index";
|
||||
|
||||
const noGoodLink = parseNostrLink("npub12hcytyr8fumy3axde8wgeced523gyp6v6zczqktwuqeaztfc2xzsz3rdp4");
|
||||
|
||||
SnortBot.simple("example")
|
||||
.link(noGoodLink)
|
||||
.relay("wss://relay.damus.io")
|
||||
.relay("wss://nos.lol")
|
||||
.relay("wss://relay.nostr.band")
|
||||
.profile({
|
||||
name: "PingBot",
|
||||
picture: "https://nostr.download/572f5ff8286e8c719196f904fed24aef14586ec8181c14b09efa726682ef48ef",
|
||||
lud16: "kieran@zap.stream",
|
||||
about: "An example bot",
|
||||
})
|
||||
.command("!ping", h => {
|
||||
h.reply("PONG!");
|
||||
})
|
||||
.run();
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@snort/bot",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.0",
|
||||
"description": "Simple bot framework",
|
||||
"type": "module",
|
||||
"module": "src/index.ts",
|
||||
@ -13,7 +13,7 @@
|
||||
"build": "rm -rf dist && tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@snort/system": "^1.5.1",
|
||||
"@snort/system": "^1.5.2",
|
||||
"eventemitter3": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -6,6 +6,10 @@ import {
|
||||
type SystemInterface,
|
||||
NostrPrefix,
|
||||
EventKind,
|
||||
TaggedNostrEvent,
|
||||
NostrSystem,
|
||||
PrivateKeySigner,
|
||||
UserMetadata,
|
||||
} from "@snort/system";
|
||||
import EventEmitter from "eventemitter3";
|
||||
|
||||
@ -15,11 +19,26 @@ export interface BotEvents {
|
||||
}
|
||||
|
||||
export interface BotMessage {
|
||||
/**
|
||||
* Event which this message belongs to
|
||||
*/
|
||||
link: NostrLink;
|
||||
/**
|
||||
* Pubkey of the message author
|
||||
*/
|
||||
from: string;
|
||||
/**
|
||||
* Message content string
|
||||
*/
|
||||
message: string;
|
||||
/**
|
||||
* Original message event
|
||||
*/
|
||||
event: NostrEvent;
|
||||
reply: (msg: string) => void;
|
||||
/**
|
||||
* Reply handler for this message
|
||||
*/
|
||||
reply: (msg: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export type CommandHandler = (msg: BotMessage) => void;
|
||||
@ -35,48 +54,15 @@ export class SnortBot extends EventEmitter<BotEvents> {
|
||||
readonly publisher: EventPublisher,
|
||||
) {
|
||||
super();
|
||||
system.pool.on("event", (addr, sub, e) => {
|
||||
this.emit("event", e);
|
||||
if (e.kind === 30311) {
|
||||
const links = [e, ...this.activeStreams].map(v => NostrLink.fromEvent(v));
|
||||
const linkStr = links.map(e => e.encode());
|
||||
if (linkStr.every(a => this.#activeStreamSub.has(a))) {
|
||||
return;
|
||||
}
|
||||
const rb = new RequestBuilder("stream-chat");
|
||||
rb.withOptions({ replaceable: true, leaveOpen: true });
|
||||
rb.withFilter()
|
||||
.kinds([1311 as EventKind])
|
||||
.replyToLink(links)
|
||||
.since(Math.floor(new Date().getTime() / 1000));
|
||||
this.system.Query(rb);
|
||||
console.log("Looking for chat messages from: ", linkStr);
|
||||
this.#activeStreamSub = new Set(linkStr);
|
||||
} else if (e.kind === 1311) {
|
||||
// skip my own messages
|
||||
if (e.pubkey === this.publisher.pubKey) {
|
||||
return;
|
||||
}
|
||||
// skip already seen chat messages
|
||||
if (this.#seen.has(e.id)) {
|
||||
return;
|
||||
}
|
||||
this.#seen.add(e.id);
|
||||
const streamTag = e.tags.find(a => a[0] === "a" && a[1].startsWith("30311:"));
|
||||
if (streamTag) {
|
||||
const link = NostrLink.fromTag(streamTag);
|
||||
this.emit("message", {
|
||||
link,
|
||||
from: e.pubkey,
|
||||
message: e.content,
|
||||
event: e,
|
||||
reply: (msg: string) => {
|
||||
this.#sendReplyTo(link, msg);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new simple bot
|
||||
*/
|
||||
static simple(name: string) {
|
||||
const system = new NostrSystem({});
|
||||
const signer = PrivateKeySigner.random();
|
||||
return new SnortBot(name, system, new EventPublisher(signer, signer.getPubKey()));
|
||||
}
|
||||
|
||||
get activeStreams() {
|
||||
@ -94,6 +80,22 @@ export class SnortBot extends EventEmitter<BotEvents> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a relay for communication
|
||||
*/
|
||||
relay(r: string) {
|
||||
this.system.ConnectToRelay(r, { read: true, write: true });
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a profile
|
||||
*/
|
||||
profile(p: UserMetadata) {
|
||||
this.publisher.metadata(p).then(ev => this.system.BroadcastEvent(ev));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple command handler
|
||||
*/
|
||||
@ -106,6 +108,9 @@ export class SnortBot extends EventEmitter<BotEvents> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the bot
|
||||
*/
|
||||
run() {
|
||||
const req = new RequestBuilder("streams");
|
||||
req.withOptions({ leaveOpen: true });
|
||||
@ -113,21 +118,90 @@ export class SnortBot extends EventEmitter<BotEvents> {
|
||||
if (link.type === NostrPrefix.PublicKey || link.type === NostrPrefix.Profile) {
|
||||
req.withFilter().authors([link.id]).kinds([30311]);
|
||||
req.withFilter().tag("p", [link.id]).kinds([30311]);
|
||||
} else if (link.type === NostrPrefix.Address) {
|
||||
const f = req.withFilter().tag("d", [link.id]);
|
||||
if (link.author) {
|
||||
f.authors([link.author]);
|
||||
}
|
||||
if (link.kind) {
|
||||
f.kinds([link.kind]);
|
||||
}
|
||||
} else {
|
||||
req.withFilter().link(link);
|
||||
}
|
||||
}
|
||||
|
||||
this.system.Query(req);
|
||||
// requst streams by input links
|
||||
const q = this.system.Query(req);
|
||||
q.on("event", evs => {
|
||||
for (const e of evs) {
|
||||
this.#handleEvent(e);
|
||||
}
|
||||
});
|
||||
|
||||
// setup chat query, its empty for now
|
||||
const rbChat = new RequestBuilder("stream-chat");
|
||||
rbChat.withOptions({ replaceable: true, leaveOpen: true });
|
||||
const qChat = this.system.Query(rbChat);
|
||||
qChat.on("event", evs => {
|
||||
for (const e of evs) {
|
||||
this.#handleEvent(e);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to all active streams
|
||||
*/
|
||||
async notify(msg: string) {
|
||||
for (const stream of this.activeStreams) {
|
||||
const ev = await this.publisher.reply(stream, msg, eb => {
|
||||
return eb.kind(1311 as EventKind);
|
||||
});
|
||||
await this.system.BroadcastEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
#handleEvent(e: TaggedNostrEvent) {
|
||||
this.emit("event", e);
|
||||
if (e.kind === 30311) {
|
||||
this.#checkActiveStreams(e);
|
||||
} else if (e.kind === 1311) {
|
||||
// skip my own messages
|
||||
if (e.pubkey === this.publisher.pubKey) {
|
||||
return;
|
||||
}
|
||||
// skip already seen chat messages
|
||||
if (this.#seen.has(e.id)) {
|
||||
return;
|
||||
}
|
||||
this.#seen.add(e.id);
|
||||
const streamTag = e.tags.find(a => a[0] === "a" && a[1].startsWith("30311:"));
|
||||
if (streamTag) {
|
||||
const link = NostrLink.fromTag(streamTag);
|
||||
this.emit("message", {
|
||||
link,
|
||||
from: e.pubkey,
|
||||
message: e.content,
|
||||
event: e,
|
||||
reply: (msg: string) => this.#sendReplyTo(link, msg),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#checkActiveStreams(e: TaggedNostrEvent) {
|
||||
const links = [e, ...this.activeStreams].map(v => NostrLink.fromEvent(v));
|
||||
const linkStr = [...new Set(links.map(e => e.encode()))];
|
||||
if (linkStr.every(a => this.#activeStreamSub.has(a))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rb = new RequestBuilder("stream-chat");
|
||||
rb.withFilter()
|
||||
.kinds([1311 as EventKind])
|
||||
.replyToLink(links)
|
||||
.since(Math.floor(new Date().getTime() / 1000));
|
||||
this.system.Query(rb);
|
||||
|
||||
console.log("Looking for chat messages from: ", linkStr);
|
||||
this.#activeStreamSub = new Set(linkStr);
|
||||
}
|
||||
|
||||
async #sendReplyTo(link: NostrLink, msg: string) {
|
||||
const ev = await this.publisher.generic(eb => {
|
||||
eb.kind(1311 as EventKind)
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@snort/system",
|
||||
"version": "1.5.1",
|
||||
"version": "1.5.2",
|
||||
"description": "Snort nostr system package",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
@ -4,6 +4,7 @@ import { EventExt } from "./event-ext";
|
||||
import { Nip4WebCryptoEncryptor } from "./impl/nip4";
|
||||
import { Nip44Encryptor } from "./impl/nip44";
|
||||
import { NostrEvent, NotSignedNostrEvent } from "./nostr";
|
||||
import { randomBytes } from "@noble/hashes/utils";
|
||||
|
||||
export type SignerSupports = "nip04" | "nip44" | string;
|
||||
|
||||
@ -31,6 +32,14 @@ export class PrivateKeySigner implements EventSigner {
|
||||
this.#publicKey = getPublicKey(this.#privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new private key
|
||||
*/
|
||||
static random() {
|
||||
const k = randomBytes(32);
|
||||
return new PrivateKeySigner(k);
|
||||
}
|
||||
|
||||
get supports(): string[] {
|
||||
return ["nip04", "nip44"];
|
||||
}
|
||||
|
43
yarn.lock
43
yarn.lock
@ -4796,9 +4796,10 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@snort/bot@workspace:packages/bot"
|
||||
dependencies:
|
||||
"@snort/system": "npm:^1.5.1"
|
||||
"@snort/system": "npm:^1.5.2"
|
||||
"@types/debug": "npm:^4.1.8"
|
||||
eventemitter3: "npm:^5.0.1"
|
||||
ts-node: "npm:^10.9.2"
|
||||
typescript: "npm:^5.2.2"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@ -4858,7 +4859,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@snort/system@npm:^1.0.21, @snort/system@npm:^1.2.11, @snort/system@npm:^1.5.1, @snort/system@workspace:*, @snort/system@workspace:packages/system":
|
||||
"@snort/system@npm:^1.0.21, @snort/system@npm:^1.2.11, @snort/system@npm:^1.5.1, @snort/system@npm:^1.5.2, @snort/system@workspace:*, @snort/system@workspace:packages/system":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@snort/system@workspace:packages/system"
|
||||
dependencies:
|
||||
@ -14534,6 +14535,44 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-node@npm:^10.9.2":
|
||||
version: 10.9.2
|
||||
resolution: "ts-node@npm:10.9.2"
|
||||
dependencies:
|
||||
"@cspotcode/source-map-support": "npm:^0.8.0"
|
||||
"@tsconfig/node10": "npm:^1.0.7"
|
||||
"@tsconfig/node12": "npm:^1.0.7"
|
||||
"@tsconfig/node14": "npm:^1.0.0"
|
||||
"@tsconfig/node16": "npm:^1.0.2"
|
||||
acorn: "npm:^8.4.1"
|
||||
acorn-walk: "npm:^8.1.1"
|
||||
arg: "npm:^4.1.0"
|
||||
create-require: "npm:^1.1.0"
|
||||
diff: "npm:^4.0.1"
|
||||
make-error: "npm:^1.1.1"
|
||||
v8-compile-cache-lib: "npm:^3.0.1"
|
||||
yn: "npm:3.1.1"
|
||||
peerDependencies:
|
||||
"@swc/core": ">=1.2.50"
|
||||
"@swc/wasm": ">=1.2.50"
|
||||
"@types/node": "*"
|
||||
typescript: ">=2.7"
|
||||
peerDependenciesMeta:
|
||||
"@swc/core":
|
||||
optional: true
|
||||
"@swc/wasm":
|
||||
optional: true
|
||||
bin:
|
||||
ts-node: dist/bin.js
|
||||
ts-node-cwd: dist/bin-cwd.js
|
||||
ts-node-esm: dist/bin-esm.js
|
||||
ts-node-script: dist/bin-script.js
|
||||
ts-node-transpile-only: dist/bin-transpile.js
|
||||
ts-script: dist/bin-script-deprecated.js
|
||||
checksum: 10/a91a15b3c9f76ac462f006fa88b6bfa528130dcfb849dd7ef7f9d640832ab681e235b8a2bc58ecde42f72851cc1d5d4e22c901b0c11aa51001ea1d395074b794
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tsconfig-paths@npm:^3.15.0":
|
||||
version: 3.15.0
|
||||
resolution: "tsconfig-paths@npm:3.15.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user