@nostrify/policies 0.36.7 → 0.36.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +9 -17
- package/CHANGELOG.md +9 -0
- package/dist/AntiDuplicationPolicy.d.ts +43 -0
- package/dist/AntiDuplicationPolicy.d.ts.map +1 -0
- package/dist/AntiDuplicationPolicy.js +44 -0
- package/dist/AntiDuplicationPolicy.ts +82 -0
- package/dist/AnyPolicy.d.ts +8 -0
- package/dist/AnyPolicy.d.ts.map +1 -0
- package/dist/AnyPolicy.js +20 -0
- package/dist/AnyPolicy.ts +24 -0
- package/dist/AuthorPolicy.d.ts +9 -0
- package/dist/AuthorPolicy.d.ts.map +1 -0
- package/dist/AuthorPolicy.js +22 -0
- package/dist/AuthorPolicy.ts +29 -0
- package/dist/DomainPolicy.d.ts +21 -0
- package/dist/DomainPolicy.d.ts.map +1 -0
- package/dist/DomainPolicy.js +62 -0
- package/dist/DomainPolicy.ts +96 -0
- package/dist/FiltersPolicy.d.ts +18 -0
- package/dist/FiltersPolicy.d.ts.map +1 -0
- package/dist/FiltersPolicy.js +17 -0
- package/dist/FiltersPolicy.ts +30 -0
- package/dist/HashtagPolicy.d.ts +16 -0
- package/dist/HashtagPolicy.d.ts.map +1 -0
- package/dist/HashtagPolicy.js +18 -0
- package/dist/HashtagPolicy.ts +28 -0
- package/dist/HellthreadPolicy.d.ts +14 -0
- package/dist/HellthreadPolicy.d.ts.map +1 -0
- package/dist/HellthreadPolicy.js +20 -0
- package/dist/HellthreadPolicy.ts +30 -0
- package/dist/InvertPolicy.d.ts +9 -0
- package/dist/InvertPolicy.d.ts.map +1 -0
- package/dist/InvertPolicy.js +20 -0
- package/dist/InvertPolicy.ts +23 -0
- package/dist/KeywordPolicy.d.ts +16 -0
- package/dist/KeywordPolicy.d.ts.map +1 -0
- package/dist/KeywordPolicy.js +18 -0
- package/dist/KeywordPolicy.ts +28 -0
- package/dist/NoOpPolicy.d.ts +6 -0
- package/dist/NoOpPolicy.d.ts.map +1 -0
- package/dist/NoOpPolicy.js +9 -0
- package/dist/NoOpPolicy.ts +9 -0
- package/dist/OpenAIPolicy.d.ts +80 -0
- package/dist/OpenAIPolicy.d.ts.map +1 -0
- package/dist/OpenAIPolicy.js +38 -0
- package/dist/OpenAIPolicy.ts +116 -0
- package/dist/PipePolicy.d.ts +26 -0
- package/dist/PipePolicy.d.ts.map +1 -0
- package/dist/PipePolicy.js +18 -0
- package/dist/PipePolicy.ts +39 -0
- package/dist/PowPolicy.d.ts +21 -0
- package/dist/PowPolicy.d.ts.map +1 -0
- package/dist/PowPolicy.js +27 -0
- package/dist/PowPolicy.ts +44 -0
- package/dist/PubkeyBanPolicy.d.ts +15 -0
- package/dist/PubkeyBanPolicy.d.ts.map +1 -0
- package/dist/PubkeyBanPolicy.js +18 -0
- package/dist/PubkeyBanPolicy.ts +27 -0
- package/dist/ReadOnlyPolicy.d.ts +6 -0
- package/dist/ReadOnlyPolicy.d.ts.map +1 -0
- package/dist/ReadOnlyPolicy.js +9 -0
- package/dist/ReadOnlyPolicy.ts +9 -0
- package/dist/RegexPolicy.d.ts +15 -0
- package/dist/RegexPolicy.d.ts.map +1 -0
- package/dist/RegexPolicy.js +16 -0
- package/dist/RegexPolicy.ts +25 -0
- package/dist/ReplyBotPolicy.d.ts +25 -0
- package/dist/ReplyBotPolicy.d.ts.map +1 -0
- package/dist/ReplyBotPolicy.js +42 -0
- package/dist/ReplyBotPolicy.ts +62 -0
- package/dist/SizePolicy.d.ts +23 -0
- package/dist/SizePolicy.d.ts.map +1 -0
- package/dist/SizePolicy.js +19 -0
- package/dist/SizePolicy.ts +39 -0
- package/dist/WhitelistPolicy.d.ts +16 -0
- package/dist/WhitelistPolicy.d.ts.map +1 -0
- package/dist/WhitelistPolicy.js +25 -0
- package/dist/WhitelistPolicy.ts +36 -0
- package/dist/WoTPolicy.d.ts +26 -0
- package/dist/WoTPolicy.d.ts.map +1 -0
- package/dist/WoTPolicy.js +39 -0
- package/dist/WoTPolicy.ts +64 -0
- package/dist/mod.d.ts +21 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +42 -0
- package/dist/mod.ts +20 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reject events containing any of the banned hashtags.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* // Reject events with banned hashtags.
|
|
9
|
+
* HashtagPolicy(['nsfw']);
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export class HashtagPolicy implements NPolicy {
|
|
13
|
+
private hashtags: string[];
|
|
14
|
+
constructor(hashtags: string[]) {
|
|
15
|
+
this.hashtags = hashtags;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// deno-lint-ignore require-await
|
|
19
|
+
async call({ id, tags }: NostrEvent): Promise<NostrRelayOK> {
|
|
20
|
+
for (const [name, value] of tags) {
|
|
21
|
+
if (name === 't' && this.hashtags.includes(value.toLowerCase())) {
|
|
22
|
+
return ['OK', id, false, 'blocked: contains a banned hashtag'];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return ['OK', id, true, ''];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
|
|
2
|
+
/** Policy options for `HellthreadPolicy`. */
|
|
3
|
+
interface HellthreadPolicyOpts {
|
|
4
|
+
/** Total number of "p" tags a kind 1 note may have before it's rejected. Default: `100` */
|
|
5
|
+
limit?: number;
|
|
6
|
+
}
|
|
7
|
+
/** Basic policy to demonstrate how policies work. Accepts all events. */
|
|
8
|
+
export declare class HellthreadPolicy implements NPolicy {
|
|
9
|
+
private opts;
|
|
10
|
+
constructor(opts?: HellthreadPolicyOpts);
|
|
11
|
+
call({ id, kind, tags }: NostrEvent): Promise<NostrRelayOK>;
|
|
12
|
+
}
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=HellthreadPolicy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HellthreadPolicy.d.ts","sourceRoot":"","sources":["../HellthreadPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEzE,6CAA6C;AAC7C,UAAU,oBAAoB;IAC5B,2FAA2F;IAC3F,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,yEAAyE;AACzE,qBAAa,gBAAiB,YAAW,OAAO;IAC9C,OAAO,CAAC,IAAI,CAAuB;gBACvB,IAAI,GAAE,oBAAyB;IAKrC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;CAalE"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class HellthreadPolicy {
|
|
2
|
+
opts;
|
|
3
|
+
constructor(opts = {}) {
|
|
4
|
+
this.opts = opts;
|
|
5
|
+
}
|
|
6
|
+
// deno-lint-ignore require-await
|
|
7
|
+
async call({ id, kind, tags }) {
|
|
8
|
+
const { limit = 100 } = this.opts;
|
|
9
|
+
if (kind === 1) {
|
|
10
|
+
const p = tags.filter((tag) => tag[0] === "p");
|
|
11
|
+
if (p.length > limit) {
|
|
12
|
+
return ["OK", id, false, `blocked: rejected due to ${p.length} "p" tags (${limit} is the limit).`];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return ["OK", id, true, ""];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
HellthreadPolicy
|
|
20
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
|
|
2
|
+
|
|
3
|
+
/** Policy options for `HellthreadPolicy`. */
|
|
4
|
+
interface HellthreadPolicyOpts {
|
|
5
|
+
/** Total number of "p" tags a kind 1 note may have before it's rejected. Default: `100` */
|
|
6
|
+
limit?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Basic policy to demonstrate how policies work. Accepts all events. */
|
|
10
|
+
export class HellthreadPolicy implements NPolicy {
|
|
11
|
+
private opts: HellthreadPolicyOpts;
|
|
12
|
+
constructor(opts: HellthreadPolicyOpts = {}) {
|
|
13
|
+
this.opts = opts;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// deno-lint-ignore require-await
|
|
17
|
+
async call({ id, kind, tags }: NostrEvent): Promise<NostrRelayOK> {
|
|
18
|
+
const { limit = 100 } = this.opts;
|
|
19
|
+
|
|
20
|
+
if (kind === 1) {
|
|
21
|
+
const p = tags.filter((tag: string[]) => tag[0] === 'p');
|
|
22
|
+
|
|
23
|
+
if (p.length > limit) {
|
|
24
|
+
return ['OK', id, false, `blocked: rejected due to ${p.length} "p" tags (${limit} is the limit).`];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return ['OK', id, true, ''];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
|
|
2
|
+
/** Rejects if the policy passes, passes if the policy rejects. */
|
|
3
|
+
export declare class InvertPolicy implements NPolicy {
|
|
4
|
+
private policy;
|
|
5
|
+
private reason;
|
|
6
|
+
constructor(policy: NPolicy, reason: string);
|
|
7
|
+
call(event: NostrEvent): Promise<NostrRelayOK>;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=InvertPolicy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InvertPolicy.d.ts","sourceRoot":"","sources":["../InvertPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEzE,kEAAkE;AAClE,qBAAa,YAAa,YAAW,OAAO;IAC1C,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM;IAKrC,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;CAUrD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class InvertPolicy {
|
|
2
|
+
policy;
|
|
3
|
+
reason;
|
|
4
|
+
constructor(policy, reason) {
|
|
5
|
+
this.policy = policy;
|
|
6
|
+
this.reason = reason;
|
|
7
|
+
}
|
|
8
|
+
async call(event) {
|
|
9
|
+
const result = await this.policy.call(event);
|
|
10
|
+
const ok = result[2];
|
|
11
|
+
if (ok) {
|
|
12
|
+
return ["OK", event.id, false, this.reason];
|
|
13
|
+
} else {
|
|
14
|
+
return ["OK", event.id, true, ""];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
InvertPolicy
|
|
20
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
|
|
2
|
+
|
|
3
|
+
/** Rejects if the policy passes, passes if the policy rejects. */
|
|
4
|
+
export class InvertPolicy implements NPolicy {
|
|
5
|
+
private policy: NPolicy;
|
|
6
|
+
private reason: string;
|
|
7
|
+
|
|
8
|
+
constructor(policy: NPolicy, reason: string) {
|
|
9
|
+
this.policy = policy;
|
|
10
|
+
this.reason = reason;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async call(event: NostrEvent): Promise<NostrRelayOK> {
|
|
14
|
+
const result = await this.policy.call(event);
|
|
15
|
+
const ok = result[2];
|
|
16
|
+
|
|
17
|
+
if (ok) {
|
|
18
|
+
return ['OK', event.id, false, this.reason];
|
|
19
|
+
} else {
|
|
20
|
+
return ['OK', event.id, true, ''];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
|
|
2
|
+
/**
|
|
3
|
+
* Reject events containing any of the strings in its content.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* // Reject events with bad words.
|
|
8
|
+
* KeywordPolicy(['moo', 'oink', 'honk']);
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export declare class KeywordPolicy implements NPolicy {
|
|
12
|
+
private words;
|
|
13
|
+
constructor(words: Iterable<string>);
|
|
14
|
+
call({ id, content }: NostrEvent): Promise<NostrRelayOK>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=KeywordPolicy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KeywordPolicy.d.ts","sourceRoot":"","sources":["../KeywordPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEzE;;;;;;;;GAQG;AACH,qBAAa,aAAc,YAAW,OAAO;IAC3C,OAAO,CAAC,KAAK,CAAmB;gBACpB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC;IAK7B,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;CAS/D"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class KeywordPolicy {
|
|
2
|
+
words;
|
|
3
|
+
constructor(words) {
|
|
4
|
+
this.words = words;
|
|
5
|
+
}
|
|
6
|
+
// deno-lint-ignore require-await
|
|
7
|
+
async call({ id, content }) {
|
|
8
|
+
for (const word of this.words) {
|
|
9
|
+
if (content.toLowerCase().includes(word.toLowerCase())) {
|
|
10
|
+
return ["OK", id, false, "blocked: contains a banned word or phrase"];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return ["OK", id, true, ""];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
KeywordPolicy
|
|
18
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reject events containing any of the strings in its content.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* // Reject events with bad words.
|
|
9
|
+
* KeywordPolicy(['moo', 'oink', 'honk']);
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export class KeywordPolicy implements NPolicy {
|
|
13
|
+
private words: Iterable<string>;
|
|
14
|
+
constructor(words: Iterable<string>) {
|
|
15
|
+
this.words = words;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// deno-lint-ignore require-await
|
|
19
|
+
async call({ id, content }: NostrEvent): Promise<NostrRelayOK> {
|
|
20
|
+
for (const word of this.words) {
|
|
21
|
+
if (content.toLowerCase().includes(word.toLowerCase())) {
|
|
22
|
+
return ['OK', id, false, 'blocked: contains a banned word or phrase'];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return ['OK', id, true, ''];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
|
|
2
|
+
/** Basic policy to demonstrate how policies work. Accepts all events. */
|
|
3
|
+
export declare class NoOpPolicy implements NPolicy {
|
|
4
|
+
call(event: NostrEvent): Promise<NostrRelayOK>;
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=NoOpPolicy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NoOpPolicy.d.ts","sourceRoot":"","sources":["../NoOpPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEzE,yEAAyE;AACzE,qBAAa,UAAW,YAAW,OAAO;IAElC,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;CAGrD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
|
|
2
|
+
|
|
3
|
+
/** Basic policy to demonstrate how policies work. Accepts all events. */
|
|
4
|
+
export class NoOpPolicy implements NPolicy {
|
|
5
|
+
// deno-lint-ignore require-await
|
|
6
|
+
async call(event: NostrEvent): Promise<NostrRelayOK> {
|
|
7
|
+
return ['OK', event.id, true, ''];
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
|
|
2
|
+
/**
|
|
3
|
+
* OpenAI moderation result.
|
|
4
|
+
*
|
|
5
|
+
* https://platform.openai.com/docs/api-reference/moderations/create
|
|
6
|
+
*/
|
|
7
|
+
interface OpenAIModerationResult {
|
|
8
|
+
id: string;
|
|
9
|
+
model: string;
|
|
10
|
+
results: {
|
|
11
|
+
categories: {
|
|
12
|
+
'hate': boolean;
|
|
13
|
+
'hate/threatening': boolean;
|
|
14
|
+
'self-harm': boolean;
|
|
15
|
+
'sexual': boolean;
|
|
16
|
+
'sexual/minors': boolean;
|
|
17
|
+
'violence': boolean;
|
|
18
|
+
'violence/graphic': boolean;
|
|
19
|
+
};
|
|
20
|
+
category_scores: {
|
|
21
|
+
'hate': number;
|
|
22
|
+
'hate/threatening': number;
|
|
23
|
+
'self-harm': number;
|
|
24
|
+
'sexual': number;
|
|
25
|
+
'sexual/minors': number;
|
|
26
|
+
'violence': number;
|
|
27
|
+
'violence/graphic': number;
|
|
28
|
+
};
|
|
29
|
+
flagged: boolean;
|
|
30
|
+
}[];
|
|
31
|
+
}
|
|
32
|
+
/** Policy options for `OpenAIPolicy`. */
|
|
33
|
+
interface OpenAIPolicyOpts {
|
|
34
|
+
/**
|
|
35
|
+
* Callback for fine control over the policy. It contains the event and the OpenAI moderation data.
|
|
36
|
+
* Implementations should return `true` to **reject** the content, and `false` to accept.
|
|
37
|
+
*/
|
|
38
|
+
handler?(event: NostrEvent, result: OpenAIModerationResult): boolean;
|
|
39
|
+
/** Custom endpoint to use instead of `https://api.openai.com/v1/moderations`. */
|
|
40
|
+
endpoint?: string;
|
|
41
|
+
/** Custom fetch implementation. */
|
|
42
|
+
fetch?: typeof fetch;
|
|
43
|
+
/** Which event kinds to apply the policy to. */
|
|
44
|
+
kinds?: number[];
|
|
45
|
+
/** OpenAI API key for making the requests. */
|
|
46
|
+
apiKey: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Passes event content to OpenAI and then rejects flagged events.
|
|
50
|
+
*
|
|
51
|
+
* By default, this policy will reject kind 1 events that OpenAI flags.
|
|
52
|
+
* It's possible to pass a custom handler for more control. An OpenAI API key is required.
|
|
53
|
+
*
|
|
54
|
+
* ```ts
|
|
55
|
+
* // Default handler. It's so strict it's suitable for children.
|
|
56
|
+
* new OpenAIPolicy({ apiKey: Deno.env.get('OPENAI_API_KEY') });
|
|
57
|
+
*
|
|
58
|
+
* // With a custom handler.
|
|
59
|
+
* new OpenAIPolicy({
|
|
60
|
+
* apiKey: Deno.env.get('OPENAI_API_KEY'),
|
|
61
|
+
* handler(event, data) {
|
|
62
|
+
* // Loop each result.
|
|
63
|
+
* return data.results.some((result) => {
|
|
64
|
+
* if (result.flagged) {
|
|
65
|
+
* const { sexual, violence } = result.categories;
|
|
66
|
+
* // Reject only events flagged as sexual and violent.
|
|
67
|
+
* return sexual && violence;
|
|
68
|
+
* }
|
|
69
|
+
* });
|
|
70
|
+
* },
|
|
71
|
+
* });
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare class OpenAIPolicy implements NPolicy {
|
|
75
|
+
private opts;
|
|
76
|
+
constructor(opts: OpenAIPolicyOpts);
|
|
77
|
+
call(event: NostrEvent, signal?: AbortSignal): Promise<NostrRelayOK>;
|
|
78
|
+
}
|
|
79
|
+
export {};
|
|
80
|
+
//# sourceMappingURL=OpenAIPolicy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OpenAIPolicy.d.ts","sourceRoot":"","sources":["../OpenAIPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEzE;;;;GAIG;AACH,UAAU,sBAAsB;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE;QACP,UAAU,EAAE;YACV,MAAM,EAAE,OAAO,CAAC;YAChB,kBAAkB,EAAE,OAAO,CAAC;YAC5B,WAAW,EAAE,OAAO,CAAC;YACrB,QAAQ,EAAE,OAAO,CAAC;YAClB,eAAe,EAAE,OAAO,CAAC;YACzB,UAAU,EAAE,OAAO,CAAC;YACpB,kBAAkB,EAAE,OAAO,CAAC;SAC7B,CAAC;QACF,eAAe,EAAE;YACf,MAAM,EAAE,MAAM,CAAC;YACf,kBAAkB,EAAE,MAAM,CAAC;YAC3B,WAAW,EAAE,MAAM,CAAC;YACpB,QAAQ,EAAE,MAAM,CAAC;YACjB,eAAe,EAAE,MAAM,CAAC;YACxB,UAAU,EAAE,MAAM,CAAC;YACnB,kBAAkB,EAAE,MAAM,CAAC;SAC5B,CAAC;QACF,OAAO,EAAE,OAAO,CAAC;KAClB,EAAE,CAAC;CACL;AAED,yCAAyC;AACzC,UAAU,gBAAgB;IACxB;;;OAGG;IACH,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC;IACrE,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,YAAa,YAAW,OAAO;IAC1C,OAAO,CAAC,IAAI,CAAmB;gBACnB,IAAI,EAAE,gBAAgB;IAI5B,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;CAiC3E"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class OpenAIPolicy {
|
|
2
|
+
opts;
|
|
3
|
+
constructor(opts) {
|
|
4
|
+
this.opts = opts;
|
|
5
|
+
}
|
|
6
|
+
async call(event, signal) {
|
|
7
|
+
const {
|
|
8
|
+
handler = (_, { results }) => results.some((r) => r.flagged),
|
|
9
|
+
endpoint = "https://api.openai.com/v1/moderations",
|
|
10
|
+
kinds = [1, 30023],
|
|
11
|
+
apiKey
|
|
12
|
+
} = this.opts;
|
|
13
|
+
if (kinds.includes(event.kind)) {
|
|
14
|
+
try {
|
|
15
|
+
const resp = await fetch(endpoint, {
|
|
16
|
+
headers: {
|
|
17
|
+
"Content-Type": "application/json",
|
|
18
|
+
"Authorization": `Bearer ${apiKey}`
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
input: event.content
|
|
22
|
+
}),
|
|
23
|
+
signal
|
|
24
|
+
});
|
|
25
|
+
const result = await resp.json();
|
|
26
|
+
if (handler(event, result)) {
|
|
27
|
+
return ["OK", event.id, false, "blocked: content flagged by AI"];
|
|
28
|
+
}
|
|
29
|
+
} catch (_) {
|
|
30
|
+
return ["OK", event.id, false, "blocked: error analyzing content"];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return ["OK", event.id, true, ""];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
OpenAIPolicy
|
|
38
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OpenAI moderation result.
|
|
5
|
+
*
|
|
6
|
+
* https://platform.openai.com/docs/api-reference/moderations/create
|
|
7
|
+
*/
|
|
8
|
+
interface OpenAIModerationResult {
|
|
9
|
+
id: string;
|
|
10
|
+
model: string;
|
|
11
|
+
results: {
|
|
12
|
+
categories: {
|
|
13
|
+
'hate': boolean;
|
|
14
|
+
'hate/threatening': boolean;
|
|
15
|
+
'self-harm': boolean;
|
|
16
|
+
'sexual': boolean;
|
|
17
|
+
'sexual/minors': boolean;
|
|
18
|
+
'violence': boolean;
|
|
19
|
+
'violence/graphic': boolean;
|
|
20
|
+
};
|
|
21
|
+
category_scores: {
|
|
22
|
+
'hate': number;
|
|
23
|
+
'hate/threatening': number;
|
|
24
|
+
'self-harm': number;
|
|
25
|
+
'sexual': number;
|
|
26
|
+
'sexual/minors': number;
|
|
27
|
+
'violence': number;
|
|
28
|
+
'violence/graphic': number;
|
|
29
|
+
};
|
|
30
|
+
flagged: boolean;
|
|
31
|
+
}[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Policy options for `OpenAIPolicy`. */
|
|
35
|
+
interface OpenAIPolicyOpts {
|
|
36
|
+
/**
|
|
37
|
+
* Callback for fine control over the policy. It contains the event and the OpenAI moderation data.
|
|
38
|
+
* Implementations should return `true` to **reject** the content, and `false` to accept.
|
|
39
|
+
*/
|
|
40
|
+
handler?(event: NostrEvent, result: OpenAIModerationResult): boolean;
|
|
41
|
+
/** Custom endpoint to use instead of `https://api.openai.com/v1/moderations`. */
|
|
42
|
+
endpoint?: string;
|
|
43
|
+
/** Custom fetch implementation. */
|
|
44
|
+
fetch?: typeof fetch;
|
|
45
|
+
/** Which event kinds to apply the policy to. */
|
|
46
|
+
kinds?: number[];
|
|
47
|
+
/** OpenAI API key for making the requests. */
|
|
48
|
+
apiKey: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Passes event content to OpenAI and then rejects flagged events.
|
|
53
|
+
*
|
|
54
|
+
* By default, this policy will reject kind 1 events that OpenAI flags.
|
|
55
|
+
* It's possible to pass a custom handler for more control. An OpenAI API key is required.
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* // Default handler. It's so strict it's suitable for children.
|
|
59
|
+
* new OpenAIPolicy({ apiKey: Deno.env.get('OPENAI_API_KEY') });
|
|
60
|
+
*
|
|
61
|
+
* // With a custom handler.
|
|
62
|
+
* new OpenAIPolicy({
|
|
63
|
+
* apiKey: Deno.env.get('OPENAI_API_KEY'),
|
|
64
|
+
* handler(event, data) {
|
|
65
|
+
* // Loop each result.
|
|
66
|
+
* return data.results.some((result) => {
|
|
67
|
+
* if (result.flagged) {
|
|
68
|
+
* const { sexual, violence } = result.categories;
|
|
69
|
+
* // Reject only events flagged as sexual and violent.
|
|
70
|
+
* return sexual && violence;
|
|
71
|
+
* }
|
|
72
|
+
* });
|
|
73
|
+
* },
|
|
74
|
+
* });
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export class OpenAIPolicy implements NPolicy {
|
|
78
|
+
private opts: OpenAIPolicyOpts;
|
|
79
|
+
constructor(opts: OpenAIPolicyOpts) {
|
|
80
|
+
this.opts = opts;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async call(event: NostrEvent, signal?: AbortSignal): Promise<NostrRelayOK> {
|
|
84
|
+
const {
|
|
85
|
+
handler = (_, { results }) => results.some((r) => r.flagged),
|
|
86
|
+
endpoint = 'https://api.openai.com/v1/moderations',
|
|
87
|
+
kinds = [1, 30023],
|
|
88
|
+
apiKey,
|
|
89
|
+
} = this.opts;
|
|
90
|
+
|
|
91
|
+
if (kinds.includes(event.kind)) {
|
|
92
|
+
try {
|
|
93
|
+
const resp = await fetch(endpoint, {
|
|
94
|
+
headers: {
|
|
95
|
+
'Content-Type': 'application/json',
|
|
96
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify({
|
|
99
|
+
input: event.content,
|
|
100
|
+
}),
|
|
101
|
+
signal,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const result = await resp.json();
|
|
105
|
+
|
|
106
|
+
if (handler(event, result)) {
|
|
107
|
+
return ['OK', event.id, false, 'blocked: content flagged by AI'];
|
|
108
|
+
}
|
|
109
|
+
} catch (_) {
|
|
110
|
+
return ['OK', event.id, false, 'blocked: error analyzing content'];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return ['OK', event.id, true, ''];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
|
|
2
|
+
/**
|
|
3
|
+
* Processes events through multiple policies.
|
|
4
|
+
*
|
|
5
|
+
* If any policy rejects, the pipeline will stop and return the rejected message.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const policy = new PipePolicy([
|
|
9
|
+
* new NoOpPolicy(),
|
|
10
|
+
* new FiltersPolicy([{ kinds: [0, 1, 3, 5, 7, 1984, 9734, 9735, 10002] }]),
|
|
11
|
+
* new KeywordPolicy(['https://t.me/']),
|
|
12
|
+
* new RegexPolicy(/(🟠|🔥|😳)ChtaGPT/i),
|
|
13
|
+
* new PubkeyBanPolicy(['e810fafa1e89cdf80cced8e013938e87e21b699b24c8570537be92aec4b12c18']),
|
|
14
|
+
* new HellthreadPolicy({ limit: 100 }),
|
|
15
|
+
* new AntiDuplicationPolicy({ kv: await Deno.openKv(), expireIn: 60000, minLength: 50 }),
|
|
16
|
+
* ]);
|
|
17
|
+
*
|
|
18
|
+
* const [_, eventId, ok, reason] = await policy.call(event);
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare class PipePolicy implements NPolicy {
|
|
22
|
+
private policies;
|
|
23
|
+
constructor(policies: NPolicy[]);
|
|
24
|
+
call(event: NostrEvent, signal?: AbortSignal): Promise<NostrRelayOK>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=PipePolicy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PipePolicy.d.ts","sourceRoot":"","sources":["../PipePolicy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEzE;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,UAAW,YAAW,OAAO;IACxC,OAAO,CAAC,QAAQ,CAAiB;gBACrB,QAAQ,EAAE,OAAO,EAAE;IAIzB,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;CAW3E"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class PipePolicy {
|
|
2
|
+
policies = [];
|
|
3
|
+
constructor(policies) {
|
|
4
|
+
this.policies = policies;
|
|
5
|
+
}
|
|
6
|
+
async call(event, signal) {
|
|
7
|
+
for (const policy of this.policies) {
|
|
8
|
+
const [_, eventId, ok, reason] = await policy.call(event, signal);
|
|
9
|
+
if (!ok) {
|
|
10
|
+
return [_, eventId, ok, reason];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return ["OK", event.id, true, ""];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
PipePolicy
|
|
18
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { NostrEvent, NostrRelayOK, NPolicy } from '@nostrify/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Processes events through multiple policies.
|
|
5
|
+
*
|
|
6
|
+
* If any policy rejects, the pipeline will stop and return the rejected message.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const policy = new PipePolicy([
|
|
10
|
+
* new NoOpPolicy(),
|
|
11
|
+
* new FiltersPolicy([{ kinds: [0, 1, 3, 5, 7, 1984, 9734, 9735, 10002] }]),
|
|
12
|
+
* new KeywordPolicy(['https://t.me/']),
|
|
13
|
+
* new RegexPolicy(/(🟠|🔥|😳)ChtaGPT/i),
|
|
14
|
+
* new PubkeyBanPolicy(['e810fafa1e89cdf80cced8e013938e87e21b699b24c8570537be92aec4b12c18']),
|
|
15
|
+
* new HellthreadPolicy({ limit: 100 }),
|
|
16
|
+
* new AntiDuplicationPolicy({ kv: await Deno.openKv(), expireIn: 60000, minLength: 50 }),
|
|
17
|
+
* ]);
|
|
18
|
+
*
|
|
19
|
+
* const [_, eventId, ok, reason] = await policy.call(event);
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class PipePolicy implements NPolicy {
|
|
23
|
+
private policies: NPolicy[] = [];
|
|
24
|
+
constructor(policies: NPolicy[]) {
|
|
25
|
+
this.policies = policies;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async call(event: NostrEvent, signal?: AbortSignal): Promise<NostrRelayOK> {
|
|
29
|
+
for (const policy of this.policies) {
|
|
30
|
+
const [_, eventId, ok, reason] = await policy.call(event, signal);
|
|
31
|
+
|
|
32
|
+
if (!ok) {
|
|
33
|
+
return [_, eventId, ok, reason];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return ['OK', event.id, true, ''];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { NostrEvent, NostrRelayInfo, NostrRelayOK, NPolicy } from '@nostrify/types';
|
|
2
|
+
/** Policy options for `PowPolicy`. */
|
|
3
|
+
interface PowPolicyOpts {
|
|
4
|
+
/** Events will be rejected if their `id` does not contain at least this many leading 0 bits. Default: `1` */
|
|
5
|
+
difficulty?: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Reject events which don't meet Proof-of-Work ([NIP-13](https://github.com/nostr-protocol/nips/blob/master/13.md)) criteria.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* new PowPolicy({ difficulty: 20 });
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare class PowPolicy implements NPolicy {
|
|
15
|
+
private opts;
|
|
16
|
+
constructor(opts?: PowPolicyOpts);
|
|
17
|
+
call({ id, tags }: NostrEvent): Promise<NostrRelayOK>;
|
|
18
|
+
get info(): NostrRelayInfo;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=PowPolicy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PowPolicy.d.ts","sourceRoot":"","sources":["../PowPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAGzF,sCAAsC;AACtC,UAAU,aAAa;IACrB,6GAA6G;IAC7G,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,qBAAa,SAAU,YAAW,OAAO;IACvC,OAAO,CAAC,IAAI,CAAgB;gBAChB,IAAI,GAAE,aAAkB;IAK9B,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;IAa3D,IAAI,IAAI,IAAI,cAAc,CAMzB;CACF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { nip13 } from "nostr-tools";
|
|
2
|
+
class PowPolicy {
|
|
3
|
+
opts;
|
|
4
|
+
constructor(opts = {}) {
|
|
5
|
+
this.opts = opts;
|
|
6
|
+
}
|
|
7
|
+
// deno-lint-ignore require-await
|
|
8
|
+
async call({ id, tags }) {
|
|
9
|
+
const { difficulty = 1 } = this.opts;
|
|
10
|
+
const pow = nip13.getPow(id);
|
|
11
|
+
const nonce = tags.find(([name]) => name === "nonce");
|
|
12
|
+
if (pow >= difficulty && nonce && Number(nonce[2]) >= difficulty) {
|
|
13
|
+
return ["OK", id, true, ""];
|
|
14
|
+
}
|
|
15
|
+
return ["OK", id, false, `pow: insufficient proof-of-work (difficulty ${difficulty})`];
|
|
16
|
+
}
|
|
17
|
+
get info() {
|
|
18
|
+
return {
|
|
19
|
+
limitation: {
|
|
20
|
+
min_pow_difficulty: this.opts.difficulty
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
PowPolicy
|
|
27
|
+
};
|