feat: @snort/bot
This commit is contained in:
parent
07a0d3ce57
commit
ed8aec1008
23
packages/bot/package.json
Normal file
23
packages/bot/package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@snort/bot",
|
||||
"version": "1.1.1",
|
||||
"description": "Simple bot framework",
|
||||
"type": "module",
|
||||
"module": "src/index.ts",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"repository": "https://git.v0l.io/Kieran/snort",
|
||||
"author": "Kieran",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@snort/system": "^1.5.1",
|
||||
"eventemitter3": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/debug": "^4.1.8",
|
||||
"typescript": "^5.2.2"
|
||||
}
|
||||
}
|
137
packages/bot/src/index.ts
Normal file
137
packages/bot/src/index.ts
Normal file
@ -0,0 +1,137 @@
|
||||
import {
|
||||
EventPublisher,
|
||||
NostrLink,
|
||||
RequestBuilder,
|
||||
type NostrEvent,
|
||||
type SystemInterface,
|
||||
NostrPrefix,
|
||||
EventKind
|
||||
} from "@snort/system";
|
||||
import EventEmitter from "eventemitter3";
|
||||
|
||||
export interface BotEvents {
|
||||
message: (msg: BotMessage) => void,
|
||||
event: (ev: NostrEvent) => void,
|
||||
}
|
||||
|
||||
export interface BotMessage {
|
||||
link: NostrLink,
|
||||
from: string,
|
||||
message: string,
|
||||
event: NostrEvent,
|
||||
reply: (msg: string) => void,
|
||||
}
|
||||
|
||||
export type CommandHandler = (msg: BotMessage) => void;
|
||||
|
||||
export class SnortBot extends EventEmitter<BotEvents> {
|
||||
#streams: Array<NostrLink> = [];
|
||||
#seen: Set<string> = new Set();
|
||||
#activeStreamSub: Set<string> = new Set();
|
||||
|
||||
constructor(
|
||||
readonly name: string,
|
||||
readonly system: SystemInterface,
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get activeStreams() {
|
||||
return this.system.GetQuery("streams")?.snapshot?.filter(a => a.tags.find(b => b[0] === "status")?.at(1) === "live") ?? []
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a stream to listen on
|
||||
*/
|
||||
link(a: NostrLink) {
|
||||
this.#streams.push(a);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple command handler
|
||||
*/
|
||||
command(cmd: string, h: CommandHandler) {
|
||||
this.on("message", m => {
|
||||
if (m.message.startsWith(cmd)) {
|
||||
h(m);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
run() {
|
||||
const req = new RequestBuilder("streams");
|
||||
req.withOptions({ leaveOpen: true });
|
||||
for (const link of this.#streams) {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.system.Query(req);
|
||||
return this;
|
||||
}
|
||||
|
||||
async #sendReplyTo(link: NostrLink, msg: string) {
|
||||
const ev = await this.publisher.generic(eb => {
|
||||
eb.kind(1311 as EventKind)
|
||||
.tag(link.toEventTag("root")!)
|
||||
.content(msg);
|
||||
return eb;
|
||||
});
|
||||
await this.system.BroadcastEvent(ev);
|
||||
}
|
||||
}
|
18
packages/bot/tsconfig.json
Normal file
18
packages/bot/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"target": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"esModuleInterop": true,
|
||||
"noImplicitOverride": true,
|
||||
"module": "ESNext",
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSourceMap": true,
|
||||
"outDir": "dist",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["./src/**/*.ts"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
3
packages/bot/typedoc.json
Normal file
3
packages/bot/typedoc.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"entryPoints": ["src/index.ts"]
|
||||
}
|
11
yarn.lock
11
yarn.lock
@ -4792,6 +4792,17 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@snort/bot@workspace:packages/bot":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@snort/bot@workspace:packages/bot"
|
||||
dependencies:
|
||||
"@snort/system": "npm:^1.5.1"
|
||||
"@types/debug": "npm:^4.1.8"
|
||||
eventemitter3: "npm:^5.0.1"
|
||||
typescript: "npm:^5.2.2"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@snort/shared@npm:^1.0.14, @snort/shared@npm:^1.0.17, @snort/shared@npm:^1.0.6, @snort/shared@workspace:*, @snort/shared@workspace:packages/shared":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@snort/shared@workspace:packages/shared"
|
||||
|
Loading…
x
Reference in New Issue
Block a user