applesauce-extra 0.0.0-next-20251009095925

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 hzrd149
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Applesauce Extra
2
+
3
+ A collection of stuff that doesn't fit into any applesauce package.
4
+
5
+ ## PrimalCache
6
+
7
+ Extended relay interface for Primal's caching server. Provides access to Primal's enhanced API endpoints for content discovery, user recommendations, trending hashtags, notifications, and more. Includes methods for:
8
+
9
+ - Content exploration and scoring
10
+ - User search and recommendations
11
+ - Trending hashtags and images
12
+ - Notification management
13
+ - Advanced search and feeds
14
+ - Media metadata retrieval
15
+
16
+ **Code:** [`src/primal.ts`](src/primal.ts)
17
+ **Upstream:** [PrimalHQ/primal-server](https://github.com/PrimalHQ/primal-server)
18
+
19
+ ## Vertex
20
+
21
+ Extended relay interface for the Vertex relay (relay.vertexlab.io). Provides reputation-based user discovery and verification services. Includes methods for:
22
+
23
+ - Profile lookup with reputation scoring
24
+ - Reputation verification
25
+ - Credit balance checking
26
+ - Automatic authentication handling
27
+
28
+ **Code:** [`src/vertex.ts`](src/vertex.ts)
29
+ **Upstream:** [vertexlab.io](https://vertexlab.io/docs/services/)
@@ -0,0 +1,2 @@
1
+ export * from "./vertex.js";
2
+ export * from "./primal.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./vertex.js";
2
+ export * from "./primal.js";
@@ -0,0 +1,413 @@
1
+ import type { NostrEvent } from "applesauce-core/helpers";
2
+ import { Relay, type RelayOptions } from "applesauce-relay";
3
+ import { Observable } from "rxjs";
4
+ export declare const DEFAULT_PRIMAL_RELAY = "wss://cache2.primal.net/v1";
5
+ export type ExploreLegendCounts = {
6
+ req: ["explore_legend_counts", {
7
+ pubkey: string;
8
+ }];
9
+ event: NostrEvent;
10
+ };
11
+ export type Explore = {
12
+ req: [
13
+ "explore",
14
+ {
15
+ timeframe: "popular" | "trending" | "recent";
16
+ pubkeys?: string[];
17
+ limit?: number;
18
+ created_after?: number;
19
+ since?: number;
20
+ until?: number;
21
+ offset?: number;
22
+ group_by_pubkey?: boolean;
23
+ user_pubkey?: string;
24
+ include_top_zaps?: boolean;
25
+ apply_humaness_check?: boolean;
26
+ gm_mode?: boolean;
27
+ }
28
+ ];
29
+ event: NostrEvent;
30
+ };
31
+ export type ExploreGlobalTrending24h = {
32
+ req: ["explore_global_trending_24h", {
33
+ limit?: number;
34
+ }];
35
+ event: NostrEvent;
36
+ };
37
+ export type ExploreGlobalMostZapped4h = {
38
+ req: ["explore_global_mostzapped_4h", {
39
+ limit?: number;
40
+ }];
41
+ event: NostrEvent;
42
+ };
43
+ export type Scored = {
44
+ req: [
45
+ "scored",
46
+ {
47
+ timeframe: "popular" | "trending" | "recent";
48
+ pubkeys?: string[];
49
+ limit?: number;
50
+ created_after?: number;
51
+ since?: number;
52
+ until?: number;
53
+ offset?: number;
54
+ group_by_pubkey?: boolean;
55
+ user_pubkey?: string;
56
+ include_top_zaps?: boolean;
57
+ apply_humaness_check?: boolean;
58
+ gm_mode?: boolean;
59
+ }
60
+ ];
61
+ event: NostrEvent;
62
+ };
63
+ export type ScoredUsers = {
64
+ req: ["scored_users", {
65
+ limit?: number;
66
+ }];
67
+ event: NostrEvent;
68
+ };
69
+ export type ScoredUsers24h = {
70
+ req: ["scored_users_24h", {
71
+ limit?: number;
72
+ }];
73
+ event: NostrEvent;
74
+ };
75
+ export type GetDefaultRelays = {
76
+ req: ["get_default_relays", {}];
77
+ event: NostrEvent;
78
+ };
79
+ export type GetRecommendedUsers = {
80
+ req: ["get_recommended_users", {
81
+ limit?: number;
82
+ }];
83
+ event: NostrEvent;
84
+ };
85
+ export type GetSuggestedUsers = {
86
+ req: ["get_suggested_users", {
87
+ limit?: number;
88
+ }];
89
+ event: NostrEvent;
90
+ };
91
+ export type UserProfileScoredContent = {
92
+ req: [
93
+ "user_profile_scored_content",
94
+ {
95
+ pubkey: string;
96
+ limit?: number;
97
+ created_after?: number;
98
+ since?: number;
99
+ until?: number;
100
+ offset?: number;
101
+ }
102
+ ];
103
+ event: NostrEvent;
104
+ };
105
+ export type UserProfileScoredMediaThumbnails = {
106
+ req: [
107
+ "user_profile_scored_media_thumbnails",
108
+ {
109
+ pubkey: string;
110
+ limit?: number;
111
+ created_after?: number;
112
+ since?: number;
113
+ until?: number;
114
+ offset?: number;
115
+ }
116
+ ];
117
+ event: NostrEvent;
118
+ };
119
+ export type Search = {
120
+ req: [
121
+ "search",
122
+ {
123
+ query: string;
124
+ limit?: number;
125
+ since?: number;
126
+ until?: number;
127
+ offset?: number;
128
+ }
129
+ ];
130
+ event: NostrEvent;
131
+ };
132
+ export type AdvancedSearch = {
133
+ req: [
134
+ "advanced_search",
135
+ {
136
+ query: string;
137
+ limit?: number;
138
+ since?: number;
139
+ until?: number;
140
+ offset?: number;
141
+ sort?: "latest" | "popular" | "trending";
142
+ }
143
+ ];
144
+ event: NostrEvent;
145
+ };
146
+ export type AdvancedFeed = {
147
+ req: [
148
+ "advanced_feed",
149
+ {
150
+ feed_type: string;
151
+ limit?: number;
152
+ since?: number;
153
+ until?: number;
154
+ offset?: number;
155
+ }
156
+ ];
157
+ event: NostrEvent;
158
+ };
159
+ export type Relays = {
160
+ req: ["relays", {}];
161
+ event: NostrEvent;
162
+ };
163
+ export type GetNotifications = {
164
+ req: [
165
+ "get_notifications",
166
+ {
167
+ pubkey: string;
168
+ limit?: number;
169
+ since?: number;
170
+ until?: number;
171
+ offset?: number;
172
+ }
173
+ ];
174
+ event: NostrEvent;
175
+ };
176
+ export type SetNotificationsSeen = {
177
+ req: ["set_notifications_seen", {
178
+ pubkey: string;
179
+ until: number;
180
+ }];
181
+ event: NostrEvent;
182
+ };
183
+ export type GetNotificationsSeen = {
184
+ req: ["get_notifications_seen", {
185
+ pubkey: string;
186
+ }];
187
+ event: NostrEvent;
188
+ };
189
+ export type UserSearch = {
190
+ req: ["user_search", {
191
+ query: string;
192
+ limit?: number;
193
+ }];
194
+ event: NostrEvent;
195
+ };
196
+ export type FeedDirective = {
197
+ req: [
198
+ "feed_directive",
199
+ {
200
+ pubkey: string;
201
+ feed_type: string;
202
+ limit?: number;
203
+ since?: number;
204
+ until?: number;
205
+ offset?: number;
206
+ }
207
+ ];
208
+ event: NostrEvent;
209
+ };
210
+ export type FeedDirective2 = {
211
+ req: [
212
+ "feed_directive_2",
213
+ {
214
+ pubkey: string;
215
+ feed_type: string;
216
+ limit?: number;
217
+ since?: number;
218
+ until?: number;
219
+ offset?: number;
220
+ }
221
+ ];
222
+ event: NostrEvent;
223
+ };
224
+ export type GetAdvancedFeeds = {
225
+ req: ["get_advanced_feeds", {}];
226
+ event: NostrEvent;
227
+ };
228
+ export type TrendingHashtags = {
229
+ req: ["trending_hashtags", {
230
+ limit?: number;
231
+ }];
232
+ event: NostrEvent;
233
+ };
234
+ export type TrendingHashtags4h = {
235
+ req: ["trending_hashtags_4h", {
236
+ limit?: number;
237
+ }];
238
+ event: NostrEvent;
239
+ };
240
+ export type TrendingHashtags7d = {
241
+ req: ["trending_hashtags_7d", {
242
+ limit?: number;
243
+ }];
244
+ event: NostrEvent;
245
+ };
246
+ export type TrendingImages = {
247
+ req: ["trending_images", {
248
+ limit?: number;
249
+ }];
250
+ event: NostrEvent;
251
+ };
252
+ export type TrendingImages4h = {
253
+ req: ["trending_images_4h", {
254
+ limit?: number;
255
+ }];
256
+ event: NostrEvent;
257
+ };
258
+ export type ReportUser = {
259
+ req: ["report_user", {
260
+ pubkey: string;
261
+ reason: string;
262
+ }];
263
+ event: NostrEvent;
264
+ };
265
+ export type ReportNote = {
266
+ req: ["report_note", {
267
+ event_id: string;
268
+ reason: string;
269
+ }];
270
+ event: NostrEvent;
271
+ };
272
+ export type GetFilterlist = {
273
+ req: ["get_filterlist", {}];
274
+ event: NostrEvent;
275
+ };
276
+ export type CheckFilterlist = {
277
+ req: ["check_filterlist", {
278
+ pubkeys: string[];
279
+ }];
280
+ event: NostrEvent;
281
+ };
282
+ export type BroadcastReply = {
283
+ req: ["broadcast_reply", {
284
+ event: NostrEvent;
285
+ }];
286
+ event: NostrEvent;
287
+ };
288
+ export type BroadcastEvents = {
289
+ req: ["broadcast_events", {
290
+ events: NostrEvent[];
291
+ relays: string[];
292
+ }];
293
+ event: NostrEvent;
294
+ };
295
+ export type TrustedUsers = {
296
+ req: ["trusted_users", {
297
+ limit?: number;
298
+ extended_response?: boolean;
299
+ }];
300
+ event: NostrEvent;
301
+ };
302
+ export type NoteMentions = {
303
+ req: [
304
+ "note_mentions",
305
+ {
306
+ event_id?: string;
307
+ pubkey?: string;
308
+ identifier?: string;
309
+ limit?: number;
310
+ offset?: number;
311
+ user_pubkey?: string;
312
+ }
313
+ ];
314
+ event: NostrEvent;
315
+ };
316
+ export type NoteMentionsCount = {
317
+ req: ["note_mentions_count", {
318
+ event_id: string;
319
+ }];
320
+ event: NostrEvent;
321
+ };
322
+ export type GetMediaMetadata = {
323
+ req: ["get_media_metadata", {
324
+ urls: string[];
325
+ }];
326
+ event: NostrEvent;
327
+ };
328
+ export type CacheRequest = ExploreLegendCounts | Explore | ExploreGlobalTrending24h | ExploreGlobalMostZapped4h | Scored | ScoredUsers | ScoredUsers24h | GetDefaultRelays | GetRecommendedUsers | GetSuggestedUsers | UserProfileScoredContent | UserProfileScoredMediaThumbnails | Search | AdvancedSearch | AdvancedFeed | Relays | GetNotifications | SetNotificationsSeen | GetNotificationsSeen | UserSearch | FeedDirective | FeedDirective2 | GetAdvancedFeeds | TrendingHashtags | TrendingHashtags4h | TrendingHashtags7d | TrendingImages | TrendingImages4h | ReportUser | ReportNote | GetFilterlist | CheckFilterlist | BroadcastReply | BroadcastEvents | TrustedUsers | NoteMentions | NoteMentionsCount | GetMediaMetadata;
329
+ /**
330
+ * Extended relay interface for primal caching server
331
+ * @see https://github.com/PrimalHQ/primal-server/blob/main/src/app_ext.jl
332
+ */
333
+ export declare class PrimalCache extends Relay {
334
+ constructor(url?: string, opts?: RelayOptions);
335
+ /** Make a "cache" request to the caching server */
336
+ cacheRequest<R extends CacheRequest>(request: CacheRequest["req"]): Observable<R["event"]>;
337
+ /** Get legend counts for explore page */
338
+ exploreLegendCounts(pubkey: string): Promise<ExploreLegendCounts["event"][]>;
339
+ /** Explore content with various filters */
340
+ explore(params: Explore["req"][1]): Promise<Explore["event"][]>;
341
+ /** Get global trending content from last 24 hours */
342
+ exploreGlobalTrending24h(limit?: number): Promise<ExploreGlobalTrending24h["event"][]>;
343
+ /** Get global most zapped content from last 4 hours */
344
+ exploreGlobalMostZapped4h(limit?: number): Promise<ExploreGlobalMostZapped4h["event"][]>;
345
+ /** Get scored content */
346
+ scored(params: Scored["req"][1]): Promise<Scored["event"][]>;
347
+ /** Get scored users */
348
+ scoredUsers(limit?: number): Promise<ScoredUsers["event"][]>;
349
+ /** Get scored users from last 24 hours */
350
+ scoredUsers24h(limit?: number): Promise<ScoredUsers24h["event"][]>;
351
+ /** Get default relays */
352
+ getDefaultRelays(): Promise<GetDefaultRelays["event"][]>;
353
+ /** Get recommended users */
354
+ getRecommendedUsers(limit?: number): Promise<GetRecommendedUsers["event"][]>;
355
+ /** Get suggested users */
356
+ getSuggestedUsers(limit?: number): Promise<GetSuggestedUsers["event"][]>;
357
+ /** Search for users by query */
358
+ userSearch(query: string, limit?: number): Promise<UserSearch["event"][]>;
359
+ /** Get trusted users */
360
+ trustedUsers(limit?: number, extendedResponse?: boolean): Promise<TrustedUsers["event"][]>;
361
+ /** Get user profile scored content */
362
+ userProfileScoredContent(params: UserProfileScoredContent["req"][1]): Promise<UserProfileScoredContent["event"][]>;
363
+ /** Get user profile scored media thumbnails */
364
+ userProfileScoredMediaThumbnails(params: UserProfileScoredMediaThumbnails["req"][1]): Promise<UserProfileScoredMediaThumbnails["event"][]>;
365
+ /** Search content */
366
+ search(params: Search["req"][1]): Promise<Search["event"][]>;
367
+ /** Advanced search */
368
+ advancedSearch(params: AdvancedSearch["req"][1]): Promise<AdvancedSearch["event"][]>;
369
+ /** Advanced feed */
370
+ advancedFeed(params: AdvancedFeed["req"][1]): Promise<AdvancedFeed["event"][]>;
371
+ /** Feed directive */
372
+ feedDirective(params: FeedDirective["req"][1]): Promise<FeedDirective["event"][]>;
373
+ /** Feed directive v2 */
374
+ feedDirective2(params: FeedDirective2["req"][1]): Promise<FeedDirective2["event"][]>;
375
+ /** Get advanced feeds */
376
+ getAdvancedFeeds(): Promise<GetAdvancedFeeds["event"][]>;
377
+ /** Get trending hashtags */
378
+ trendingHashtags(limit?: number): Promise<TrendingHashtags["event"][]>;
379
+ /** Get trending hashtags from last 4 hours */
380
+ trendingHashtags4h(limit?: number): Promise<TrendingHashtags4h["event"][]>;
381
+ /** Get trending hashtags from last 7 days */
382
+ trendingHashtags7d(limit?: number): Promise<TrendingHashtags7d["event"][]>;
383
+ /** Get trending images */
384
+ trendingImages(limit?: number): Promise<TrendingImages["event"][]>;
385
+ /** Get trending images from last 4 hours */
386
+ trendingImages4h(limit?: number): Promise<TrendingImages4h["event"][]>;
387
+ /** Get notifications */
388
+ getNotifications(params: GetNotifications["req"][1]): Promise<GetNotifications["event"][]>;
389
+ /** Set notifications as seen */
390
+ setNotificationsSeen(pubkey: string, until: number): Promise<SetNotificationsSeen["event"][]>;
391
+ /** Get notifications seen status */
392
+ getNotificationsSeen(pubkey: string): Promise<GetNotificationsSeen["event"][]>;
393
+ /** Get relays */
394
+ relays(): Promise<Relays["event"][]>;
395
+ /** Report user */
396
+ reportUser(pubkey: string, reason: string): Promise<ReportUser["event"][]>;
397
+ /** Report note */
398
+ reportNote(eventId: string, reason: string): Promise<ReportNote["event"][]>;
399
+ /** Get filterlist */
400
+ getFilterlist(): Promise<GetFilterlist["event"][]>;
401
+ /** Check filterlist */
402
+ checkFilterlist(pubkeys: string[]): Promise<CheckFilterlist["event"][]>;
403
+ /** Broadcast reply */
404
+ broadcastReply(event: NostrEvent): Promise<BroadcastReply["event"][]>;
405
+ /** Broadcast events */
406
+ broadcastEvents(events: NostrEvent[], relays: string[]): Promise<BroadcastEvents["event"][]>;
407
+ /** Get note mentions */
408
+ noteMentions(params: NoteMentions["req"][1]): Promise<NoteMentions["event"][]>;
409
+ /** Get note mentions count */
410
+ noteMentionsCount(eventId: string): Promise<NoteMentionsCount["event"][]>;
411
+ /** Get media metadata */
412
+ getMediaMetadata(urls: string[]): Promise<GetMediaMetadata["event"][]>;
413
+ }
package/dist/primal.js ADDED
@@ -0,0 +1,190 @@
1
+ import { mapEventsToTimeline } from "applesauce-core";
2
+ import { completeOnEose, Relay } from "applesauce-relay";
3
+ import { lastValueFrom } from "rxjs";
4
+ export const DEFAULT_PRIMAL_RELAY = "wss://cache2.primal.net/v1";
5
+ /**
6
+ * Extended relay interface for primal caching server
7
+ * @see https://github.com/PrimalHQ/primal-server/blob/main/src/app_ext.jl
8
+ */
9
+ export class PrimalCache extends Relay {
10
+ constructor(url = DEFAULT_PRIMAL_RELAY, opts) {
11
+ super(url, opts);
12
+ }
13
+ /** Make a "cache" request to the caching server */
14
+ cacheRequest(request) {
15
+ return this.req({
16
+ // @ts-expect-error
17
+ cache: request,
18
+ }).pipe(completeOnEose());
19
+ }
20
+ // ===== EXPLORE METHODS =====
21
+ /** Get legend counts for explore page */
22
+ exploreLegendCounts(pubkey) {
23
+ return lastValueFrom(this.cacheRequest(["explore_legend_counts", { pubkey }]).pipe(mapEventsToTimeline()));
24
+ }
25
+ /** Explore content with various filters */
26
+ explore(params) {
27
+ return lastValueFrom(this.cacheRequest(["explore", params]).pipe(mapEventsToTimeline()));
28
+ }
29
+ /** Get global trending content from last 24 hours */
30
+ exploreGlobalTrending24h(limit = 20) {
31
+ return lastValueFrom(this.cacheRequest(["explore_global_trending_24h", { limit }]).pipe(mapEventsToTimeline()));
32
+ }
33
+ /** Get global most zapped content from last 4 hours */
34
+ exploreGlobalMostZapped4h(limit = 20) {
35
+ return lastValueFrom(this.cacheRequest(["explore_global_mostzapped_4h", { limit }]).pipe(mapEventsToTimeline()));
36
+ }
37
+ // ===== SCORED CONTENT METHODS =====
38
+ /** Get scored content */
39
+ scored(params) {
40
+ return lastValueFrom(this.cacheRequest(["scored", params]).pipe(mapEventsToTimeline()));
41
+ }
42
+ /** Get scored users */
43
+ scoredUsers(limit = 20) {
44
+ return lastValueFrom(this.cacheRequest(["scored_users", { limit }]).pipe(mapEventsToTimeline()));
45
+ }
46
+ /** Get scored users from last 24 hours */
47
+ scoredUsers24h(limit = 20) {
48
+ return lastValueFrom(this.cacheRequest(["scored_users_24h", { limit }]).pipe(mapEventsToTimeline()));
49
+ }
50
+ // ===== RELAY METHODS =====
51
+ /** Get default relays */
52
+ getDefaultRelays() {
53
+ return lastValueFrom(this.cacheRequest(["get_default_relays", {}]).pipe(mapEventsToTimeline()));
54
+ }
55
+ // ===== USER METHODS =====
56
+ /** Get recommended users */
57
+ getRecommendedUsers(limit = 20) {
58
+ return lastValueFrom(this.cacheRequest(["get_recommended_users", { limit }]).pipe(mapEventsToTimeline()));
59
+ }
60
+ /** Get suggested users */
61
+ getSuggestedUsers(limit = 20) {
62
+ return lastValueFrom(this.cacheRequest(["get_suggested_users", { limit }]).pipe(mapEventsToTimeline()));
63
+ }
64
+ /** Search for users by query */
65
+ userSearch(query, limit = 10) {
66
+ return lastValueFrom(this.cacheRequest(["user_search", { query, limit }]).pipe(mapEventsToTimeline()));
67
+ }
68
+ /** Get trusted users */
69
+ trustedUsers(limit = 500, extendedResponse = true) {
70
+ return lastValueFrom(this.cacheRequest([
71
+ "trusted_users",
72
+ { limit, extended_response: extendedResponse },
73
+ ]).pipe(mapEventsToTimeline()));
74
+ }
75
+ // ===== PROFILE METHODS =====
76
+ /** Get user profile scored content */
77
+ userProfileScoredContent(params) {
78
+ return lastValueFrom(this.cacheRequest(["user_profile_scored_content", params]).pipe(mapEventsToTimeline()));
79
+ }
80
+ /** Get user profile scored media thumbnails */
81
+ userProfileScoredMediaThumbnails(params) {
82
+ return lastValueFrom(this.cacheRequest(["user_profile_scored_media_thumbnails", params]).pipe(mapEventsToTimeline()));
83
+ }
84
+ // ===== SEARCH METHODS =====
85
+ /** Search content */
86
+ search(params) {
87
+ return lastValueFrom(this.cacheRequest(["search", params]).pipe(mapEventsToTimeline()));
88
+ }
89
+ /** Advanced search */
90
+ advancedSearch(params) {
91
+ return lastValueFrom(this.cacheRequest(["advanced_search", params]).pipe(mapEventsToTimeline()));
92
+ }
93
+ // ===== FEED METHODS =====
94
+ /** Advanced feed */
95
+ advancedFeed(params) {
96
+ return lastValueFrom(this.cacheRequest(["advanced_feed", params]).pipe(mapEventsToTimeline()));
97
+ }
98
+ /** Feed directive */
99
+ feedDirective(params) {
100
+ return lastValueFrom(this.cacheRequest(["feed_directive", params]).pipe(mapEventsToTimeline()));
101
+ }
102
+ /** Feed directive v2 */
103
+ feedDirective2(params) {
104
+ return lastValueFrom(this.cacheRequest(["feed_directive_2", params]).pipe(mapEventsToTimeline()));
105
+ }
106
+ /** Get advanced feeds */
107
+ getAdvancedFeeds() {
108
+ return lastValueFrom(this.cacheRequest(["get_advanced_feeds", {}]).pipe(mapEventsToTimeline()));
109
+ }
110
+ // ===== TRENDING METHODS =====
111
+ /** Get trending hashtags */
112
+ trendingHashtags(limit = 20) {
113
+ return lastValueFrom(this.cacheRequest(["trending_hashtags", { limit }]).pipe(mapEventsToTimeline()));
114
+ }
115
+ /** Get trending hashtags from last 4 hours */
116
+ trendingHashtags4h(limit = 20) {
117
+ return lastValueFrom(this.cacheRequest(["trending_hashtags_4h", { limit }]).pipe(mapEventsToTimeline()));
118
+ }
119
+ /** Get trending hashtags from last 7 days */
120
+ trendingHashtags7d(limit = 20) {
121
+ return lastValueFrom(this.cacheRequest(["trending_hashtags_7d", { limit }]).pipe(mapEventsToTimeline()));
122
+ }
123
+ /** Get trending images */
124
+ trendingImages(limit = 20) {
125
+ return lastValueFrom(this.cacheRequest(["trending_images", { limit }]).pipe(mapEventsToTimeline()));
126
+ }
127
+ /** Get trending images from last 4 hours */
128
+ trendingImages4h(limit = 20) {
129
+ return lastValueFrom(this.cacheRequest(["trending_images_4h", { limit }]).pipe(mapEventsToTimeline()));
130
+ }
131
+ // ===== NOTIFICATION METHODS =====
132
+ /** Get notifications */
133
+ getNotifications(params) {
134
+ return lastValueFrom(this.cacheRequest(["get_notifications", params]).pipe(mapEventsToTimeline()));
135
+ }
136
+ /** Set notifications as seen */
137
+ setNotificationsSeen(pubkey, until) {
138
+ return lastValueFrom(this.cacheRequest(["set_notifications_seen", { pubkey, until }]).pipe(mapEventsToTimeline()));
139
+ }
140
+ /** Get notifications seen status */
141
+ getNotificationsSeen(pubkey) {
142
+ return lastValueFrom(this.cacheRequest(["get_notifications_seen", { pubkey }]).pipe(mapEventsToTimeline()));
143
+ }
144
+ // ===== RELAY METHODS =====
145
+ /** Get relays */
146
+ relays() {
147
+ return lastValueFrom(this.cacheRequest(["relays", {}]).pipe(mapEventsToTimeline()));
148
+ }
149
+ // ===== REPORT METHODS =====
150
+ /** Report user */
151
+ reportUser(pubkey, reason) {
152
+ return lastValueFrom(this.cacheRequest(["report_user", { pubkey, reason }]).pipe(mapEventsToTimeline()));
153
+ }
154
+ /** Report note */
155
+ reportNote(eventId, reason) {
156
+ return lastValueFrom(this.cacheRequest(["report_note", { event_id: eventId, reason }]).pipe(mapEventsToTimeline()));
157
+ }
158
+ // ===== FILTERLIST METHODS =====
159
+ /** Get filterlist */
160
+ getFilterlist() {
161
+ return lastValueFrom(this.cacheRequest(["get_filterlist", {}]).pipe(mapEventsToTimeline()));
162
+ }
163
+ /** Check filterlist */
164
+ checkFilterlist(pubkeys) {
165
+ return lastValueFrom(this.cacheRequest(["check_filterlist", { pubkeys }]).pipe(mapEventsToTimeline()));
166
+ }
167
+ // ===== BROADCAST METHODS =====
168
+ /** Broadcast reply */
169
+ broadcastReply(event) {
170
+ return lastValueFrom(this.cacheRequest(["broadcast_reply", { event }]).pipe(mapEventsToTimeline()));
171
+ }
172
+ /** Broadcast events */
173
+ broadcastEvents(events, relays) {
174
+ return lastValueFrom(this.cacheRequest(["broadcast_events", { events, relays }]).pipe(mapEventsToTimeline()));
175
+ }
176
+ // ===== MENTION METHODS =====
177
+ /** Get note mentions */
178
+ noteMentions(params) {
179
+ return lastValueFrom(this.cacheRequest(["note_mentions", params]).pipe(mapEventsToTimeline()));
180
+ }
181
+ /** Get note mentions count */
182
+ noteMentionsCount(eventId) {
183
+ return lastValueFrom(this.cacheRequest(["note_mentions_count", { event_id: eventId }]).pipe(mapEventsToTimeline()));
184
+ }
185
+ // ===== MEDIA METHODS =====
186
+ /** Get media metadata */
187
+ getMediaMetadata(urls) {
188
+ return lastValueFrom(this.cacheRequest(["get_media_metadata", { urls }]).pipe(mapEventsToTimeline()));
189
+ }
190
+ }
@@ -0,0 +1,36 @@
1
+ import { ProfilePointer } from "applesauce-core/helpers/pointers";
2
+ import { Relay, type RelayOptions } from "applesauce-relay";
3
+ import { type ISigner } from "applesauce-signers";
4
+ export declare const VERTEX_RELAY = "wss://relay.vertexlab.io";
5
+ export declare const VERTEX_SEARCH_KIND = 5315;
6
+ export declare const VERTEX_VERIFY_REPUTATION_KIND = 5312;
7
+ export type SortMethod = "globalPagerank" | "personalizedPagerank" | "followerCount";
8
+ export type SearchProfileResult = ProfilePointer & {
9
+ rank: number;
10
+ };
11
+ export type VerifyReputationResult = {
12
+ pubkey: string;
13
+ rank: number;
14
+ follows?: number;
15
+ followers?: number;
16
+ };
17
+ export type VerifyReputationOptions = {
18
+ target: string;
19
+ sort?: SortMethod;
20
+ source?: string;
21
+ limit?: number;
22
+ };
23
+ /** Extended relay class for interacting with the vertex relay */
24
+ export declare class Vertex extends Relay {
25
+ private signer;
26
+ private autoAuth;
27
+ constructor(signer: ISigner, relay?: string, opts?: RelayOptions);
28
+ /** Lookup user profiles by search query */
29
+ lookupProfiles(query: string, sortMethod?: SortMethod, limit?: number): Promise<ProfilePointer[]>;
30
+ /** Verify reputation of a pubkey */
31
+ verifyReputation(options: VerifyReputationOptions): Promise<VerifyReputationResult[]>;
32
+ /** Method to get credit balance on vertex */
33
+ getCreditBalance(): Promise<number>;
34
+ /** Close the connection to the relay */
35
+ close(): void;
36
+ }
package/dist/vertex.js ADDED
@@ -0,0 +1,111 @@
1
+ import { getTagValue, hasNameValueTag, unixNow } from "applesauce-core/helpers";
2
+ import { onlyEvents, Relay } from "applesauce-relay";
3
+ import { combineLatest, filter, firstValueFrom, map, take } from "rxjs";
4
+ export const VERTEX_RELAY = "wss://relay.vertexlab.io";
5
+ export const VERTEX_SEARCH_KIND = 5315;
6
+ export const VERTEX_VERIFY_REPUTATION_KIND = 5312;
7
+ /** Extended relay class for interacting with the vertex relay */
8
+ export class Vertex extends Relay {
9
+ signer;
10
+ autoAuth;
11
+ constructor(signer, relay = VERTEX_RELAY, opts) {
12
+ super(relay, opts);
13
+ this.signer = signer;
14
+ // Automatically authenticate to the relay when a challenge is received
15
+ this.autoAuth = combineLatest([this.challenge$, this.authenticated$]).subscribe(([challenge, authenticated]) => {
16
+ if (challenge && !authenticated) {
17
+ console.info("[VERTEX] Authenticating to relay");
18
+ this.authenticate(this.signer);
19
+ }
20
+ });
21
+ }
22
+ /** Lookup user profiles by search query */
23
+ async lookupProfiles(query, sortMethod = "globalPagerank", limit = 10) {
24
+ // Create request
25
+ const request = await this.signer.signEvent({
26
+ kind: VERTEX_SEARCH_KIND,
27
+ tags: [
28
+ ["param", "search", query],
29
+ ["param", "limit", limit.toString()],
30
+ ["param", "source", await this.signer.getPublicKey()],
31
+ ["param", "sort", sortMethod],
32
+ ],
33
+ created_at: unixNow(),
34
+ content: "",
35
+ });
36
+ // send request
37
+ await this.publish(request);
38
+ // Wait for response
39
+ const response = await firstValueFrom(this.subscription({
40
+ kinds: [VERTEX_SEARCH_KIND + 1000, 7000],
41
+ "#e": [request.id],
42
+ "#p": [request.pubkey],
43
+ }).pipe(onlyEvents(),
44
+ // Only select response events
45
+ filter((event) => hasNameValueTag(event, "e", request.id)),
46
+ // Only accept error events for response
47
+ filter((event) => (event.kind === 7000 ? getTagValue(event, "status") === "error" : true))));
48
+ const error = response.kind === 7000 ? response.tags.find((t) => t[0] === "status" && t[1] === "error")?.[2] : undefined;
49
+ // Throw vertex error
50
+ if (error)
51
+ throw new Error(error);
52
+ const pointers = JSON.parse(response.content)
53
+ // Sort by rank descending
54
+ .sort((a, b) => b.rank - a.rank)
55
+ // Convert to profile pointers
56
+ .map(({ pubkey }) => ({
57
+ pubkey,
58
+ relays: [VERTEX_RELAY],
59
+ }));
60
+ return pointers;
61
+ }
62
+ /** Verify reputation of a pubkey */
63
+ async verifyReputation(options) {
64
+ const { target, sort = "globalPagerank", source, limit = 5 } = options;
65
+ // Create request
66
+ const request = await this.signer.signEvent({
67
+ kind: VERTEX_VERIFY_REPUTATION_KIND,
68
+ tags: [
69
+ ["param", "target", target],
70
+ ["param", "sort", sort],
71
+ ["param", "limit", limit.toString()],
72
+ ...(source ? [["param", "source", source]] : []),
73
+ ],
74
+ created_at: unixNow(),
75
+ content: "",
76
+ });
77
+ // Send request
78
+ await this.publish(request);
79
+ // Wait for response
80
+ const response = await firstValueFrom(this.subscription({
81
+ kinds: [VERTEX_VERIFY_REPUTATION_KIND + 1000, 7000],
82
+ "#e": [request.id],
83
+ "#p": [request.pubkey],
84
+ }).pipe(onlyEvents(),
85
+ // Only select response events
86
+ filter((event) => hasNameValueTag(event, "e", request.id)),
87
+ // Only accept error events for response
88
+ filter((event) => (event.kind === 7000 ? getTagValue(event, "status") === "error" : true))));
89
+ const error = response.kind === 7000 ? response.tags.find((t) => t[0] === "status" && t[1] === "error")?.[2] : undefined;
90
+ // Throw vertex error
91
+ if (error)
92
+ throw new Error(error);
93
+ // Parse and return the results
94
+ const results = JSON.parse(response.content);
95
+ return results;
96
+ }
97
+ /** Method to get credit balance on vertex */
98
+ async getCreditBalance() {
99
+ return firstValueFrom(this.request({ kinds: [22243] }).pipe(take(1), map((e) => {
100
+ const tag = e.tags.find((t) => t[0] === "credits");
101
+ if (!tag)
102
+ throw new Error("No credits tag found");
103
+ return parseInt(tag?.[1] || "0");
104
+ })));
105
+ }
106
+ /** Close the connection to the relay */
107
+ close() {
108
+ this.autoAuth.unsubscribe();
109
+ super.close();
110
+ }
111
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "applesauce-extra",
3
+ "version": "0.0.0-next-20251009095925",
4
+ "description": "",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "keywords": [
9
+ "nostr",
10
+ "applesauce",
11
+ "vertex",
12
+ "primal"
13
+ ],
14
+ "author": "hzrd149",
15
+ "license": "MIT",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "dependencies": {
20
+ "rxjs": "^7.8.2",
21
+ "applesauce-core": "^4.0.0",
22
+ "applesauce-relay": "^4.0.0",
23
+ "applesauce-signers": "^0.0.0-next-20251009095925"
24
+ },
25
+ "devDependencies": {
26
+ "@hirez_io/observer-spy": "^2.2.0",
27
+ "rimraf": "^6.0.1",
28
+ "typescript": "^5.8.3",
29
+ "vitest": "^3.2.4"
30
+ },
31
+ "funding": {
32
+ "type": "lightning",
33
+ "url": "lightning:nostrudel@geyser.fund"
34
+ },
35
+ "scripts": {
36
+ "prebuild": "rimraf dist",
37
+ "build": "tsc",
38
+ "watch:build": "tsc --watch > /dev/null",
39
+ "test": "vitest run --passWithNoTests",
40
+ "watch:test": "vitest"
41
+ }
42
+ }