applesauce-core 0.0.0-next-20250703183032 → 0.0.0-next-20250723224513

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.
@@ -5,6 +5,7 @@ import { LRU } from "../helpers/lru.js";
5
5
  import { Mutes } from "../helpers/mutes.js";
6
6
  import { ProfileContent } from "../helpers/profile.js";
7
7
  import { Thread } from "../models/thread.js";
8
+ import { AddressPointerWithoutD } from "../helpers/pointers.js";
8
9
  /** The read interface for an event store */
9
10
  export interface IEventStoreRead {
10
11
  /** Check if the event store has an event with id */
@@ -51,6 +52,17 @@ export interface IEventClaims {
51
52
  /** Removes all claims on an event */
52
53
  clearClaim(event: NostrEvent): void;
53
54
  }
55
+ /** An event store that can be subscribed to */
56
+ export interface IEventStoreSubscriptions {
57
+ /** Susbscribe to an event by id */
58
+ event(id: string | EventPointer): Observable<NostrEvent | undefined>;
59
+ /** Subscribe to a replaceable event by pointer */
60
+ replaceable(pointer: AddressPointerWithoutD): Observable<NostrEvent | undefined>;
61
+ /** Subscribe to an addressable event by pointer */
62
+ addressable(pointer: AddressPointer): Observable<NostrEvent | undefined>;
63
+ /** Subscribe to a batch of events that match the filters */
64
+ filter(filters: Filter | Filter[]): Observable<NostrEvent[]>;
65
+ }
54
66
  /** Methods for creating common models */
55
67
  export interface IEventStoreModels {
56
68
  event(id: string): Observable<NostrEvent | undefined>;
@@ -0,0 +1,36 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { ProfilePointer } from "nostr-tools/nip19";
3
+ export declare const DATE_BASED_CALENDAR_EVENT_KIND = 31922;
4
+ export declare const TIME_BASED_CALENDAR_EVENT_KIND = 31923;
5
+ export type CalendarEventParticipant = ProfilePointer & {
6
+ role?: string;
7
+ };
8
+ export declare const CalendarEventLocationsSymbol: unique symbol;
9
+ export declare const CalendarEventParticipantsSymbol: unique symbol;
10
+ export declare const CalendarEventHashtagsSymbol: unique symbol;
11
+ export declare const CalendarEventReferencesSymbol: unique symbol;
12
+ export declare const CalendarEventGeohashSymbol: unique symbol;
13
+ /** Gets the title of a calendar event or calendar */
14
+ export declare function getCalendarEventTitle(event: NostrEvent): string | undefined;
15
+ /** Gets the summary of a calendar event */
16
+ export declare function getCalendarEventSummary(event: NostrEvent): string | undefined;
17
+ /** Gets the image URL of a calendar event */
18
+ export declare function getCalendarEventImage(event: NostrEvent): string | undefined;
19
+ /** Gets the start Unix timestamp of a calendar event */
20
+ export declare function getCalendarEventStart(event: NostrEvent): number | undefined;
21
+ /** Gets the timezone of the start timestamp of a calendar event */
22
+ export declare function getCalendarEventStartTimezone(event: NostrEvent): string | undefined;
23
+ /** Gets the timezone of the end timestamp of a calendar event */
24
+ export declare function getCalendarEventEndTimezone(event: NostrEvent): string | undefined;
25
+ /** Gets the end Unix timestamp of a calendar event */
26
+ export declare function getCalendarEventEnd(event: NostrEvent): number | undefined;
27
+ /** Gets all locations from a calendar event */
28
+ export declare function getCalendarEventLocations(event: NostrEvent): string[];
29
+ /** Gets the geohash of a calendar event */
30
+ export declare function getCalendarEventGeohash(event: NostrEvent): string | undefined;
31
+ /** Gets all participants from a calendar event */
32
+ export declare function getCalendarEventParticipants(event: NostrEvent): CalendarEventParticipant[];
33
+ /** Gets all hashtags from a calendar event */
34
+ export declare function getCalendarEventHashtags(event: NostrEvent): string[];
35
+ /** Gets all references from a calendar event */
36
+ export declare function getCalendarEventReferences(event: NostrEvent): string[];
@@ -0,0 +1,110 @@
1
+ import { getOrComputeCachedValue } from "./cache.js";
2
+ import { getTagValue } from "./event-tags.js";
3
+ import { getProfilePointerFromPTag } from "./pointers.js";
4
+ import { isPTag, isRTag, isTTag } from "./tags.js";
5
+ // NIP-52 Calendar Event Kinds
6
+ export const DATE_BASED_CALENDAR_EVENT_KIND = 31922;
7
+ export const TIME_BASED_CALENDAR_EVENT_KIND = 31923;
8
+ // Cache symbols for complex operations only
9
+ export const CalendarEventLocationsSymbol = Symbol.for("calendar-event-locations");
10
+ export const CalendarEventParticipantsSymbol = Symbol.for("calendar-event-participants");
11
+ export const CalendarEventHashtagsSymbol = Symbol.for("calendar-event-hashtags");
12
+ export const CalendarEventReferencesSymbol = Symbol.for("calendar-event-references");
13
+ export const CalendarEventGeohashSymbol = Symbol.for("calendar-event-geohash");
14
+ /** Gets the title of a calendar event or calendar */
15
+ export function getCalendarEventTitle(event) {
16
+ return getTagValue(event, "title") || getTagValue(event, "name"); // fallback to deprecated "name" tag
17
+ }
18
+ /** Gets the summary of a calendar event */
19
+ export function getCalendarEventSummary(event) {
20
+ return getTagValue(event, "summary");
21
+ }
22
+ /** Gets the image URL of a calendar event */
23
+ export function getCalendarEventImage(event) {
24
+ return getTagValue(event, "image");
25
+ }
26
+ /** Gets the start Unix timestamp of a calendar event */
27
+ export function getCalendarEventStart(event) {
28
+ const value = getTagValue(event, "start");
29
+ if (!value)
30
+ return undefined;
31
+ if (event.kind === DATE_BASED_CALENDAR_EVENT_KIND)
32
+ return new Date(value).valueOf() / 1000;
33
+ else if (event.kind === TIME_BASED_CALENDAR_EVENT_KIND)
34
+ return parseInt(value);
35
+ else
36
+ return undefined;
37
+ }
38
+ /** Gets the timezone of the start timestamp of a calendar event */
39
+ export function getCalendarEventStartTimezone(event) {
40
+ if (event.kind === DATE_BASED_CALENDAR_EVENT_KIND)
41
+ return undefined;
42
+ return getTagValue(event, "start_tzid");
43
+ }
44
+ /** Gets the timezone of the end timestamp of a calendar event */
45
+ export function getCalendarEventEndTimezone(event) {
46
+ if (event.kind === DATE_BASED_CALENDAR_EVENT_KIND)
47
+ return undefined;
48
+ return getTagValue(event, "end_tzid");
49
+ }
50
+ /** Gets the end Unix timestamp of a calendar event */
51
+ export function getCalendarEventEnd(event) {
52
+ const value = getTagValue(event, "end");
53
+ if (!value)
54
+ return undefined;
55
+ if (event.kind === DATE_BASED_CALENDAR_EVENT_KIND)
56
+ return new Date(value).valueOf() / 1000;
57
+ else if (event.kind === TIME_BASED_CALENDAR_EVENT_KIND)
58
+ return parseInt(value);
59
+ else
60
+ return undefined;
61
+ }
62
+ /** Gets all locations from a calendar event */
63
+ export function getCalendarEventLocations(event) {
64
+ if (event.kind !== DATE_BASED_CALENDAR_EVENT_KIND && event.kind !== TIME_BASED_CALENDAR_EVENT_KIND)
65
+ throw new Error("Event is not a date-based or time-based calendar event");
66
+ return getOrComputeCachedValue(event, CalendarEventLocationsSymbol, () => {
67
+ return event.tags.filter((t) => t[0] === "location" && t[1]).map((t) => t[1]);
68
+ });
69
+ }
70
+ /** Gets the geohash of a calendar event */
71
+ export function getCalendarEventGeohash(event) {
72
+ if (event.kind !== DATE_BASED_CALENDAR_EVENT_KIND && event.kind !== TIME_BASED_CALENDAR_EVENT_KIND)
73
+ throw new Error("Event is not a date-based or time-based calendar event");
74
+ return getOrComputeCachedValue(event, CalendarEventGeohashSymbol, () => {
75
+ let hash = undefined;
76
+ for (const tag of event.tags) {
77
+ if (tag[0] === "g" && tag[1] && (!hash || tag[1].length > hash.length))
78
+ hash = tag[1];
79
+ }
80
+ return hash;
81
+ });
82
+ }
83
+ /** Gets all participants from a calendar event */
84
+ export function getCalendarEventParticipants(event) {
85
+ if (event.kind !== DATE_BASED_CALENDAR_EVENT_KIND && event.kind !== TIME_BASED_CALENDAR_EVENT_KIND)
86
+ throw new Error("Event is not a date-based or time-based calendar event");
87
+ return getOrComputeCachedValue(event, CalendarEventParticipantsSymbol, () => {
88
+ return event.tags.filter(isPTag).map((tag) => ({
89
+ ...getProfilePointerFromPTag(tag),
90
+ // Third index of tag is optional "role"
91
+ role: tag[3] || undefined,
92
+ }));
93
+ });
94
+ }
95
+ /** Gets all hashtags from a calendar event */
96
+ export function getCalendarEventHashtags(event) {
97
+ if (event.kind !== DATE_BASED_CALENDAR_EVENT_KIND && event.kind !== TIME_BASED_CALENDAR_EVENT_KIND)
98
+ throw new Error("Event is not a date-based or time-based calendar event");
99
+ return getOrComputeCachedValue(event, CalendarEventHashtagsSymbol, () => {
100
+ return event.tags.filter(isTTag).map((t) => t[1]);
101
+ });
102
+ }
103
+ /** Gets all references from a calendar event */
104
+ export function getCalendarEventReferences(event) {
105
+ if (event.kind !== DATE_BASED_CALENDAR_EVENT_KIND && event.kind !== TIME_BASED_CALENDAR_EVENT_KIND)
106
+ throw new Error("Event is not a date-based or time-based calendar event");
107
+ return getOrComputeCachedValue(event, CalendarEventReferencesSymbol, () => {
108
+ return event.tags.filter(isRTag).map((t) => t[1]);
109
+ });
110
+ }
@@ -0,0 +1,15 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
3
+ export declare const CALENDAR_EVENT_RSVP_KIND = 31925;
4
+ export type RSVPStatus = "accepted" | "declined" | "tentative";
5
+ export type RSVPFreeBusy = "free" | "busy";
6
+ /** Gets the RSVP status from a calendar event RSVP */
7
+ export declare function getRSVPStatus(event: NostrEvent): RSVPStatus | undefined;
8
+ /** Gets the free/busy status from a calendar event RSVP (will be undefined if the RSVP is declined) */
9
+ export declare function getRSVPFreeBusy(event: NostrEvent): RSVPFreeBusy | undefined;
10
+ /** Gets the referenced calendar event coordinate that the RSVP is responding to */
11
+ export declare function getRSVPAddressPointer(event: NostrEvent): AddressPointer | undefined;
12
+ /** Gets the referenced calendar event pointer that the RSVP is responding to */
13
+ export declare function getRSVPEventPointer(event: NostrEvent): EventPointer | undefined;
14
+ /** Gets the profile pointer that the RSVP is responding to */
15
+ export declare function getRSVPProfilePointer(event: NostrEvent): ProfilePointer | undefined;
@@ -0,0 +1,38 @@
1
+ import { getTagValue } from "./event-tags.js";
2
+ import { getAddressPointerFromATag, getEventPointerFromETag, getProfilePointerFromPTag } from "./pointers.js";
3
+ import { isATag, isETag, isPTag } from "./tags.js";
4
+ export const CALENDAR_EVENT_RSVP_KIND = 31925;
5
+ /** Gets the RSVP status from a calendar event RSVP */
6
+ export function getRSVPStatus(event) {
7
+ const status = getTagValue(event, "status");
8
+ return status && ["accepted", "declined", "tentative"].includes(status) ? status : undefined;
9
+ }
10
+ /** Gets the free/busy status from a calendar event RSVP (will be undefined if the RSVP is declined) */
11
+ export function getRSVPFreeBusy(event) {
12
+ const status = getRSVPStatus(event);
13
+ if (status === "declined")
14
+ return undefined;
15
+ const fb = getTagValue(event, "fb");
16
+ return fb && ["free", "busy"].includes(fb) ? fb : undefined;
17
+ }
18
+ /** Gets the referenced calendar event coordinate that the RSVP is responding to */
19
+ export function getRSVPAddressPointer(event) {
20
+ const tag = event.tags.find(isATag);
21
+ if (!tag)
22
+ return undefined;
23
+ return getAddressPointerFromATag(tag);
24
+ }
25
+ /** Gets the referenced calendar event pointer that the RSVP is responding to */
26
+ export function getRSVPEventPointer(event) {
27
+ const tag = event.tags.find(isETag);
28
+ if (!tag)
29
+ return undefined;
30
+ return getEventPointerFromETag(tag);
31
+ }
32
+ /** Gets the profile pointer that the RSVP is responding to */
33
+ export function getRSVPProfilePointer(event) {
34
+ const tag = event.tags.find(isPTag);
35
+ if (!tag)
36
+ return undefined;
37
+ return getProfilePointerFromPTag(tag);
38
+ }
@@ -0,0 +1,6 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { AddressPointer } from "nostr-tools/nip19";
3
+ /** Gets the title of a calendar */
4
+ export declare function getCalendarTitle(event: NostrEvent): string | undefined;
5
+ /** Gets the address pointers to all the events on the calendar */
6
+ export declare function getCalendarAddressPointers(event: NostrEvent): AddressPointer[];
@@ -0,0 +1,11 @@
1
+ import { getTagValue } from "./event-tags.js";
2
+ import { getAddressPointerFromATag } from "./pointers.js";
3
+ import { isATag } from "./tags.js";
4
+ /** Gets the title of a calendar */
5
+ export function getCalendarTitle(event) {
6
+ return getTagValue(event, "title");
7
+ }
8
+ /** Gets the address pointers to all the events on the calendar */
9
+ export function getCalendarAddressPointers(event) {
10
+ return event.tags.filter(isATag).map(getAddressPointerFromATag);
11
+ }
@@ -4,6 +4,9 @@ export * from "./blossom.js";
4
4
  export * from "./bolt11.js";
5
5
  export * from "./bookmarks.js";
6
6
  export * from "./cache.js";
7
+ export * from "./calendar-event.js";
8
+ export * from "./calendar-rsvp.js";
9
+ export * from "./calendar.js";
7
10
  export * from "./channels.js";
8
11
  export * from "./comment.js";
9
12
  export * from "./contacts.js";
@@ -4,6 +4,9 @@ export * from "./blossom.js";
4
4
  export * from "./bolt11.js";
5
5
  export * from "./bookmarks.js";
6
6
  export * from "./cache.js";
7
+ export * from "./calendar-event.js";
8
+ export * from "./calendar-rsvp.js";
9
+ export * from "./calendar.js";
7
10
  export * from "./channels.js";
8
11
  export * from "./comment.js";
9
12
  export * from "./contacts.js";
@@ -187,7 +187,7 @@ export function addRelayHintsToPointer(pointer, relays) {
187
187
  /** Gets the hex pubkey from any nip-19 encoded string */
188
188
  export function normalizeToPubkey(str) {
189
189
  if (isHexKey(str))
190
- return str;
190
+ return str.toLowerCase();
191
191
  else {
192
192
  const decode = nip19.decode(str);
193
193
  const pubkey = getPubkeyFromDecodeResult(decode);
@@ -0,0 +1,6 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { Model } from "../event-store/interface.js";
3
+ /** A model that gets all the events for a calendar */
4
+ export declare function CalendarEventsModel(calendar: NostrEvent): Model<NostrEvent[]>;
5
+ /** A model that gets all the RSVPs for a calendar event */
6
+ export declare function CalendarEventRSVPsModel(event: NostrEvent): Model<NostrEvent[]>;
@@ -0,0 +1,15 @@
1
+ import { combineLatest } from "rxjs";
2
+ import { CALENDAR_EVENT_RSVP_KIND } from "../helpers/calendar-rsvp.js";
3
+ import { getCalendarAddressPointers } from "../helpers/calendar.js";
4
+ import { getReplaceableAddress } from "../helpers/index.js";
5
+ import { defined } from "../observable/defined.js";
6
+ /** A model that gets all the events for a calendar */
7
+ export function CalendarEventsModel(calendar) {
8
+ return (events) => combineLatest(getCalendarAddressPointers(calendar).map((p) => events.replaceable(p.kind, p.pubkey, p.identifier).pipe(defined())));
9
+ }
10
+ /** A model that gets all the RSVPs for a calendar event */
11
+ export function CalendarEventRSVPsModel(event) {
12
+ return (events) => {
13
+ return events.timeline({ kinds: [CALENDAR_EVENT_RSVP_KIND], "#a": [getReplaceableAddress(event)] });
14
+ };
15
+ }
@@ -1,5 +1,6 @@
1
1
  export * from "./blossom.js";
2
2
  export * from "./bookmarks.js";
3
+ export * from "./calendar.js";
3
4
  export * from "./channels.js";
4
5
  export * from "./comments.js";
5
6
  export * from "./common.js";
@@ -1,5 +1,6 @@
1
1
  export * from "./blossom.js";
2
2
  export * from "./bookmarks.js";
3
+ export * from "./calendar.js";
3
4
  export * from "./channels.js";
4
5
  export * from "./comments.js";
5
6
  export * from "./common.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-core",
3
- "version": "0.0.0-next-20250703183032",
3
+ "version": "0.0.0-next-20250723224513",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,17 +0,0 @@
1
- import type { NostrEvent } from "nostr-tools";
2
- import { EncryptedContentSigner } from "../helpers/encrypted-content.js";
3
- export declare class FakeUser implements EncryptedContentSigner {
4
- key: Uint8Array<ArrayBufferLike>;
5
- pubkey: string;
6
- nip04: {
7
- encrypt: (pubkey: string, plaintext: string) => string;
8
- decrypt: (pubkey: string, ciphertext: string) => string;
9
- };
10
- nip44: {
11
- encrypt: (pubkey: string, plaintext: string) => string;
12
- decrypt: (pubkey: string, ciphertext: string) => string;
13
- };
14
- event(data?: Partial<NostrEvent>): NostrEvent;
15
- note(content?: string, extra?: Partial<NostrEvent>): import("nostr-tools").Event;
16
- profile(profile: any, extra?: Partial<NostrEvent>): import("nostr-tools").Event;
17
- }
@@ -1,28 +0,0 @@
1
- import { finalizeEvent, generateSecretKey, getPublicKey, kinds, nip04, nip44 } from "nostr-tools";
2
- import { unixNow } from "../helpers/time.js";
3
- export class FakeUser {
4
- key = generateSecretKey();
5
- pubkey = getPublicKey(this.key);
6
- nip04 = {
7
- encrypt: (pubkey, plaintext) => nip04.encrypt(this.key, pubkey, plaintext),
8
- decrypt: (pubkey, ciphertext) => nip04.decrypt(this.key, pubkey, ciphertext),
9
- };
10
- nip44 = {
11
- encrypt: (pubkey, plaintext) => nip44.encrypt(plaintext, nip44.getConversationKey(this.key, pubkey)),
12
- decrypt: (pubkey, ciphertext) => nip44.decrypt(ciphertext, nip44.getConversationKey(this.key, pubkey)),
13
- };
14
- event(data) {
15
- return finalizeEvent({
16
- kind: data?.kind ?? kinds.ShortTextNote,
17
- content: data?.content || "",
18
- created_at: data?.created_at ?? unixNow(),
19
- tags: data?.tags || [],
20
- }, this.key);
21
- }
22
- note(content = "Hello World", extra) {
23
- return this.event({ kind: kinds.ShortTextNote, content, ...extra });
24
- }
25
- profile(profile, extra) {
26
- return this.event({ kind: kinds.Metadata, content: JSON.stringify({ ...profile }), ...extra });
27
- }
28
- }