@destink/substack-sdk 0.1.0
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 +21 -0
- package/README.md +84 -0
- package/dist/index.d.ts +420 -0
- package/dist/index.js +929 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jakub Slys
|
|
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,84 @@
|
|
|
1
|
+
# Substack SDK
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://www.typescriptlang.org/)
|
|
5
|
+
|
|
6
|
+
A modern, type-safe TypeScript client for the Substack API. Connects directly to Substack's endpoints using CycleTLS to bypass Cloudflare bot detection. No third-party proxy or gateway required.
|
|
7
|
+
|
|
8
|
+
## Why This Fork?
|
|
9
|
+
|
|
10
|
+
This project is based on [substack-api](https://github.com/jakub-k-slys/substack-api) by [Jakub Slys](https://github.com/jakub-k-slys). The original library routes all API requests through a third-party gateway proxy (`substack-gateway.vercel.app`), which means your Substack session cookies pass through a server you don't control. That's a dealbreaker for anyone handling user data or building a production application.
|
|
11
|
+
|
|
12
|
+
This fork takes a different architectural approach:
|
|
13
|
+
|
|
14
|
+
| | substack-api | substack-sdk |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| **HTTP layer** | Plain axios through a gateway proxy | CycleTLS directly to Substack (bypasses Cloudflare) |
|
|
17
|
+
| **Authentication** | Cookies sent to a third-party proxy | Cookies sent directly to Substack |
|
|
18
|
+
| **Credentials** | Single base64-encoded token | Two explicit cookies (`substack.sid` + `substack.lli`) |
|
|
19
|
+
| **TLS handling** | Relies on gateway to handle Cloudflare | Spoofs browser TLS fingerprints via CycleTLS |
|
|
20
|
+
| **Infrastructure** | Requires the gateway to be running | No external dependencies |
|
|
21
|
+
|
|
22
|
+
The entity-based API, async iterators, and io-ts runtime validation from the original are preserved.
|
|
23
|
+
|
|
24
|
+
## QuickStart
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm add @destink/substack-sdk
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { SubstackClient } from '@destink/substack-sdk';
|
|
32
|
+
|
|
33
|
+
const client = new SubstackClient({
|
|
34
|
+
substackSid: process.env.SUBSTACK_SID!,
|
|
35
|
+
substackLli: process.env.SUBSTACK_LLI!,
|
|
36
|
+
publicationUrl: 'https://yoursite.substack.com',
|
|
37
|
+
handle: 'yourhandle'
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Get your profile and iterate through posts
|
|
41
|
+
const profile = await client.ownProfile();
|
|
42
|
+
for await (const post of profile.posts({ limit: 5 })) {
|
|
43
|
+
console.log(`"${post.title}" - ${post.publishedAt?.toLocaleDateString()}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Test connectivity
|
|
47
|
+
const isConnected = await client.testConnectivity();
|
|
48
|
+
|
|
49
|
+
// Always close when done (shuts down the CycleTLS subprocess)
|
|
50
|
+
await client.close();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Authentication
|
|
54
|
+
|
|
55
|
+
All requests go directly to Substack's API. Authentication requires two session cookies from your browser.
|
|
56
|
+
|
|
57
|
+
### Step 1: Obtain your session cookies
|
|
58
|
+
|
|
59
|
+
1. Log in to [substack.com](https://substack.com) in your browser.
|
|
60
|
+
2. Open DevTools > Application > Cookies > `substack.com`.
|
|
61
|
+
3. Copy the values of **`substack.sid`** and **`substack.lli`**.
|
|
62
|
+
|
|
63
|
+
### Step 2: Pass the cookies to the client
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
const client = new SubstackClient({
|
|
67
|
+
substackSid: '<value of substack.sid cookie>',
|
|
68
|
+
substackLli: '<value of substack.lli cookie>',
|
|
69
|
+
publicationUrl: 'https://yoursite.substack.com',
|
|
70
|
+
handle: 'yourhandle' // required for ownProfile()
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Documentation
|
|
75
|
+
|
|
76
|
+
- [Installation Guide](docs/installation.md) - Setup and requirements
|
|
77
|
+
- [QuickStart Tutorial](docs/quickstart.md) - Get started in minutes
|
|
78
|
+
- [API Reference](docs/api-reference.md) - Complete method documentation
|
|
79
|
+
- [Entity Model](docs/entity-model.md) - Modern object-oriented API
|
|
80
|
+
- [Examples](docs/examples.md) - Real-world usage patterns
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import * as t from 'io-ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* io-ts codecs and inferred types for Substack API response shapes.
|
|
5
|
+
*
|
|
6
|
+
* These match the real Substack API responses (not the gateway proxy).
|
|
7
|
+
* Fields are validated loosely — only the fields used by domain entities
|
|
8
|
+
* are required; extra fields from Substack are ignored by io-ts.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
declare const SubstackProfileC: t.IntersectionC<[t.TypeC<{
|
|
12
|
+
id: t.NumberC;
|
|
13
|
+
handle: t.StringC;
|
|
14
|
+
name: t.StringC;
|
|
15
|
+
photo_url: t.StringC;
|
|
16
|
+
}>, t.PartialC<{
|
|
17
|
+
bio: t.UnionC<[t.StringC, t.NullC]>;
|
|
18
|
+
}>]>;
|
|
19
|
+
type SubstackProfile = t.TypeOf<typeof SubstackProfileC>;
|
|
20
|
+
declare const SubstackFeedItemPostC: t.IntersectionC<[t.TypeC<{
|
|
21
|
+
id: t.NumberC;
|
|
22
|
+
title: t.StringC;
|
|
23
|
+
slug: t.StringC;
|
|
24
|
+
post_date: t.StringC;
|
|
25
|
+
}>, t.PartialC<{
|
|
26
|
+
subtitle: t.UnionC<[t.StringC, t.NullC]>;
|
|
27
|
+
truncated_body_text: t.UnionC<[t.StringC, t.NullC]>;
|
|
28
|
+
body_html: t.UnionC<[t.StringC, t.NullC]>;
|
|
29
|
+
canonical_url: t.UnionC<[t.StringC, t.NullC]>;
|
|
30
|
+
reactions: t.UnionC<[t.RecordC<t.StringC, t.NumberC>, t.NullC]>;
|
|
31
|
+
restacks: t.UnionC<[t.NumberC, t.NullC]>;
|
|
32
|
+
cover_image: t.UnionC<[t.StringC, t.NullC]>;
|
|
33
|
+
publication_id: t.NumberC;
|
|
34
|
+
comment_count: t.NumberC;
|
|
35
|
+
type: t.StringC;
|
|
36
|
+
}>]>;
|
|
37
|
+
type SubstackFeedItemPost = t.TypeOf<typeof SubstackFeedItemPostC>;
|
|
38
|
+
declare const SubstackFeedCommentC: t.IntersectionC<[t.TypeC<{
|
|
39
|
+
id: t.NumberC;
|
|
40
|
+
body: t.StringC;
|
|
41
|
+
user_id: t.NumberC;
|
|
42
|
+
date: t.StringC;
|
|
43
|
+
}>, t.PartialC<{
|
|
44
|
+
name: t.UnionC<[t.StringC, t.NullC]>;
|
|
45
|
+
handle: t.UnionC<[t.StringC, t.NullC]>;
|
|
46
|
+
photo_url: t.UnionC<[t.StringC, t.NullC]>;
|
|
47
|
+
body_json: t.UnknownC;
|
|
48
|
+
publication_id: t.UnionC<[t.NumberC, t.NullC]>;
|
|
49
|
+
post_id: t.UnionC<[t.NumberC, t.NullC]>;
|
|
50
|
+
type: t.StringC;
|
|
51
|
+
reaction_count: t.NumberC;
|
|
52
|
+
reactions: t.UnionC<[t.RecordC<t.StringC, t.NumberC>, t.NullC]>;
|
|
53
|
+
restacks: t.NumberC;
|
|
54
|
+
children_count: t.NumberC;
|
|
55
|
+
attachments: t.ArrayC<t.UnknownC>;
|
|
56
|
+
ancestor_path: t.StringC;
|
|
57
|
+
edited_at: t.UnionC<[t.StringC, t.NullC]>;
|
|
58
|
+
reply_minimum_role: t.UnionC<[t.StringC, t.NullC]>;
|
|
59
|
+
}>]>;
|
|
60
|
+
type SubstackFeedComment = t.TypeOf<typeof SubstackFeedCommentC>;
|
|
61
|
+
declare const SubstackFeedItemC: t.IntersectionC<[t.TypeC<{
|
|
62
|
+
entity_key: t.StringC;
|
|
63
|
+
type: t.StringC;
|
|
64
|
+
}>, t.PartialC<{
|
|
65
|
+
publication: t.UnionC<[t.IntersectionC<[t.TypeC<{
|
|
66
|
+
id: t.NumberC;
|
|
67
|
+
subdomain: t.StringC;
|
|
68
|
+
name: t.StringC;
|
|
69
|
+
}>, t.PartialC<{
|
|
70
|
+
custom_domain: t.UnionC<[t.StringC, t.NullC]>;
|
|
71
|
+
logo_url: t.UnionC<[t.StringC, t.NullC]>;
|
|
72
|
+
author_id: t.NumberC;
|
|
73
|
+
}>]>, t.NullC]>;
|
|
74
|
+
post: t.UnionC<[t.IntersectionC<[t.TypeC<{
|
|
75
|
+
id: t.NumberC;
|
|
76
|
+
title: t.StringC;
|
|
77
|
+
slug: t.StringC;
|
|
78
|
+
post_date: t.StringC;
|
|
79
|
+
}>, t.PartialC<{
|
|
80
|
+
subtitle: t.UnionC<[t.StringC, t.NullC]>;
|
|
81
|
+
truncated_body_text: t.UnionC<[t.StringC, t.NullC]>;
|
|
82
|
+
body_html: t.UnionC<[t.StringC, t.NullC]>;
|
|
83
|
+
canonical_url: t.UnionC<[t.StringC, t.NullC]>;
|
|
84
|
+
reactions: t.UnionC<[t.RecordC<t.StringC, t.NumberC>, t.NullC]>;
|
|
85
|
+
restacks: t.UnionC<[t.NumberC, t.NullC]>;
|
|
86
|
+
cover_image: t.UnionC<[t.StringC, t.NullC]>;
|
|
87
|
+
publication_id: t.NumberC;
|
|
88
|
+
comment_count: t.NumberC;
|
|
89
|
+
type: t.StringC;
|
|
90
|
+
}>]>, t.NullC]>;
|
|
91
|
+
comment: t.UnionC<[t.IntersectionC<[t.TypeC<{
|
|
92
|
+
id: t.NumberC;
|
|
93
|
+
body: t.StringC;
|
|
94
|
+
user_id: t.NumberC;
|
|
95
|
+
date: t.StringC;
|
|
96
|
+
}>, t.PartialC<{
|
|
97
|
+
name: t.UnionC<[t.StringC, t.NullC]>;
|
|
98
|
+
handle: t.UnionC<[t.StringC, t.NullC]>;
|
|
99
|
+
photo_url: t.UnionC<[t.StringC, t.NullC]>;
|
|
100
|
+
body_json: t.UnknownC;
|
|
101
|
+
publication_id: t.UnionC<[t.NumberC, t.NullC]>;
|
|
102
|
+
post_id: t.UnionC<[t.NumberC, t.NullC]>;
|
|
103
|
+
type: t.StringC;
|
|
104
|
+
reaction_count: t.NumberC;
|
|
105
|
+
reactions: t.UnionC<[t.RecordC<t.StringC, t.NumberC>, t.NullC]>;
|
|
106
|
+
restacks: t.NumberC;
|
|
107
|
+
children_count: t.NumberC;
|
|
108
|
+
attachments: t.ArrayC<t.UnknownC>;
|
|
109
|
+
ancestor_path: t.StringC;
|
|
110
|
+
edited_at: t.UnionC<[t.StringC, t.NullC]>;
|
|
111
|
+
reply_minimum_role: t.UnionC<[t.StringC, t.NullC]>;
|
|
112
|
+
}>]>, t.NullC]>;
|
|
113
|
+
}>]>;
|
|
114
|
+
type SubstackFeedItem = t.TypeOf<typeof SubstackFeedItemC>;
|
|
115
|
+
declare const SubstackFullPostC: t.IntersectionC<[t.TypeC<{
|
|
116
|
+
id: t.NumberC;
|
|
117
|
+
title: t.StringC;
|
|
118
|
+
slug: t.StringC;
|
|
119
|
+
post_date: t.StringC;
|
|
120
|
+
}>, t.PartialC<{
|
|
121
|
+
subtitle: t.UnionC<[t.StringC, t.NullC]>;
|
|
122
|
+
body_html: t.UnionC<[t.StringC, t.NullC]>;
|
|
123
|
+
truncated_body_text: t.UnionC<[t.StringC, t.NullC]>;
|
|
124
|
+
canonical_url: t.UnionC<[t.StringC, t.NullC]>;
|
|
125
|
+
reactions: t.UnionC<[t.RecordC<t.StringC, t.NumberC>, t.NullC]>;
|
|
126
|
+
restacks: t.UnionC<[t.NumberC, t.NullC]>;
|
|
127
|
+
cover_image: t.UnionC<[t.StringC, t.NullC]>;
|
|
128
|
+
publication_id: t.NumberC;
|
|
129
|
+
type: t.StringC;
|
|
130
|
+
is_published: t.BooleanC;
|
|
131
|
+
comment_count: t.NumberC;
|
|
132
|
+
}>]>;
|
|
133
|
+
type SubstackFullPost = t.TypeOf<typeof SubstackFullPostC>;
|
|
134
|
+
interface SubstackComment {
|
|
135
|
+
id: number;
|
|
136
|
+
body: string;
|
|
137
|
+
body_json?: unknown;
|
|
138
|
+
publication_id?: number | null;
|
|
139
|
+
post_id?: number | null;
|
|
140
|
+
user_id?: number;
|
|
141
|
+
ancestor_path?: string;
|
|
142
|
+
type?: string;
|
|
143
|
+
date?: string;
|
|
144
|
+
name?: string | null;
|
|
145
|
+
handle?: string | null;
|
|
146
|
+
photo_url?: string | null;
|
|
147
|
+
reaction_count?: number;
|
|
148
|
+
reactions?: Record<string, number> | null;
|
|
149
|
+
restacks?: number;
|
|
150
|
+
deleted?: boolean;
|
|
151
|
+
children?: SubstackComment[];
|
|
152
|
+
}
|
|
153
|
+
declare const SubstackCreateNoteResponseC: t.TypeC<{
|
|
154
|
+
id: t.NumberC;
|
|
155
|
+
}>;
|
|
156
|
+
type SubstackCreateNoteResponse = t.TypeOf<typeof SubstackCreateNoteResponseC>;
|
|
157
|
+
|
|
158
|
+
type RequestScope = 'global' | 'publication' | {
|
|
159
|
+
subdomain: string;
|
|
160
|
+
};
|
|
161
|
+
declare class HttpClient {
|
|
162
|
+
private client;
|
|
163
|
+
private readonly headers;
|
|
164
|
+
private readonly publicationBaseUrl;
|
|
165
|
+
private lastRequestTime;
|
|
166
|
+
private readonly minInterval;
|
|
167
|
+
constructor(config: {
|
|
168
|
+
substackSid: string;
|
|
169
|
+
substackLli: string;
|
|
170
|
+
publicationUrl: string;
|
|
171
|
+
maxRequestsPerSecond?: number;
|
|
172
|
+
});
|
|
173
|
+
private getClient;
|
|
174
|
+
private throttle;
|
|
175
|
+
private resolveBaseUrl;
|
|
176
|
+
private buildUrl;
|
|
177
|
+
private parseResponseData;
|
|
178
|
+
get<T>(path: string, params?: Record<string, string | number | boolean | undefined>, scope?: RequestScope): Promise<T>;
|
|
179
|
+
post<T>(path: string, data?: unknown, scope?: RequestScope): Promise<T>;
|
|
180
|
+
put<T>(path: string, data?: unknown, scope?: RequestScope): Promise<T>;
|
|
181
|
+
delete(path: string, scope?: RequestScope): Promise<void>;
|
|
182
|
+
close(): Promise<void>;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
interface PaginatedPosts {
|
|
186
|
+
posts: SubstackFeedItem[];
|
|
187
|
+
nextCursor?: string | null;
|
|
188
|
+
}
|
|
189
|
+
declare class PostService {
|
|
190
|
+
private readonly client;
|
|
191
|
+
constructor(client: HttpClient);
|
|
192
|
+
getPostBySlug(slug: string, subdomain?: string): Promise<SubstackFullPost>;
|
|
193
|
+
getPostsForProfile(userId: number, options?: {
|
|
194
|
+
cursor?: string;
|
|
195
|
+
}): Promise<PaginatedPosts>;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
interface PaginatedNotes {
|
|
199
|
+
notes: SubstackFeedComment[];
|
|
200
|
+
nextCursor?: string | null;
|
|
201
|
+
}
|
|
202
|
+
declare class NoteService {
|
|
203
|
+
private readonly client;
|
|
204
|
+
constructor(client: HttpClient);
|
|
205
|
+
getNotesForProfile(userId: number, options?: {
|
|
206
|
+
cursor?: string;
|
|
207
|
+
}): Promise<PaginatedNotes>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
declare class ProfileService {
|
|
211
|
+
private readonly client;
|
|
212
|
+
constructor(client: HttpClient);
|
|
213
|
+
getOwnProfile(handle: string): Promise<SubstackProfile>;
|
|
214
|
+
getProfileBySlug(handle: string): Promise<SubstackProfile>;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
declare class CommentService {
|
|
218
|
+
private readonly client;
|
|
219
|
+
constructor(client: HttpClient);
|
|
220
|
+
getCommentsForPost(postId: number, subdomain?: string): Promise<SubstackComment[]>;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
interface FollowingUser {
|
|
224
|
+
id: number;
|
|
225
|
+
handle: string;
|
|
226
|
+
}
|
|
227
|
+
declare class FollowingService {
|
|
228
|
+
private readonly client;
|
|
229
|
+
constructor(client: HttpClient);
|
|
230
|
+
getFollowing(): Promise<FollowingUser[]>;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
declare class NewNoteService {
|
|
234
|
+
private readonly client;
|
|
235
|
+
constructor(client: HttpClient);
|
|
236
|
+
publishNote(content: string, attachmentIds?: string[]): Promise<SubstackCreateNoteResponse>;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
declare class Comment {
|
|
240
|
+
private readonly rawData;
|
|
241
|
+
readonly id: number;
|
|
242
|
+
readonly body: string;
|
|
243
|
+
readonly date?: string;
|
|
244
|
+
readonly authorName?: string;
|
|
245
|
+
readonly authorHandle?: string;
|
|
246
|
+
readonly reactionCount?: number;
|
|
247
|
+
constructor(rawData: SubstackComment);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
interface Post {
|
|
251
|
+
readonly id: number;
|
|
252
|
+
readonly title: string;
|
|
253
|
+
readonly subtitle: string;
|
|
254
|
+
readonly body: string;
|
|
255
|
+
readonly truncatedBody: string;
|
|
256
|
+
readonly publishedAt: Date;
|
|
257
|
+
comments(options?: {
|
|
258
|
+
limit?: number;
|
|
259
|
+
}): AsyncIterable<Comment>;
|
|
260
|
+
like(): Promise<void>;
|
|
261
|
+
addComment(data: {
|
|
262
|
+
body: string;
|
|
263
|
+
}): Promise<Comment>;
|
|
264
|
+
}
|
|
265
|
+
declare class PreviewPost implements Post {
|
|
266
|
+
private readonly commentService;
|
|
267
|
+
private readonly postService;
|
|
268
|
+
readonly id: number;
|
|
269
|
+
readonly title: string;
|
|
270
|
+
readonly subtitle: string;
|
|
271
|
+
readonly body: string;
|
|
272
|
+
readonly truncatedBody: string;
|
|
273
|
+
readonly publishedAt: Date;
|
|
274
|
+
readonly slug: string;
|
|
275
|
+
readonly publicationSubdomain?: string;
|
|
276
|
+
constructor(rawData: SubstackFeedItemPost, commentService: CommentService, postService: PostService, publicationSubdomain?: string);
|
|
277
|
+
fullPost(): Promise<FullPost>;
|
|
278
|
+
comments(options?: {
|
|
279
|
+
limit?: number;
|
|
280
|
+
}): AsyncIterable<Comment>;
|
|
281
|
+
like(): Promise<void>;
|
|
282
|
+
addComment(_data: {
|
|
283
|
+
body: string;
|
|
284
|
+
}): Promise<Comment>;
|
|
285
|
+
}
|
|
286
|
+
declare class FullPost implements Post {
|
|
287
|
+
private readonly commentService;
|
|
288
|
+
private readonly publicationSubdomain?;
|
|
289
|
+
readonly id: number;
|
|
290
|
+
readonly title: string;
|
|
291
|
+
readonly subtitle: string;
|
|
292
|
+
readonly body: string;
|
|
293
|
+
readonly truncatedBody: string;
|
|
294
|
+
readonly publishedAt: Date;
|
|
295
|
+
readonly htmlBody: string;
|
|
296
|
+
readonly slug: string;
|
|
297
|
+
readonly createdAt: Date;
|
|
298
|
+
readonly reactions?: Record<string, number>;
|
|
299
|
+
readonly restacks?: number;
|
|
300
|
+
readonly coverImage?: string;
|
|
301
|
+
readonly url: string;
|
|
302
|
+
constructor(rawData: SubstackFullPost, commentService: CommentService, publicationSubdomain?: string | undefined);
|
|
303
|
+
comments(options?: {
|
|
304
|
+
limit?: number;
|
|
305
|
+
}): AsyncIterable<Comment>;
|
|
306
|
+
like(): Promise<void>;
|
|
307
|
+
addComment(_data: {
|
|
308
|
+
body: string;
|
|
309
|
+
}): Promise<Comment>;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
declare class Note {
|
|
313
|
+
private readonly rawData;
|
|
314
|
+
readonly id: number;
|
|
315
|
+
readonly body: string;
|
|
316
|
+
readonly likesCount: number;
|
|
317
|
+
readonly author: {
|
|
318
|
+
id: number;
|
|
319
|
+
name: string;
|
|
320
|
+
handle: string;
|
|
321
|
+
avatarUrl: string;
|
|
322
|
+
};
|
|
323
|
+
readonly publishedAt: Date;
|
|
324
|
+
constructor(rawData: SubstackFeedComment);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
declare class Profile {
|
|
328
|
+
protected readonly rawData: SubstackProfile;
|
|
329
|
+
protected readonly postService: PostService;
|
|
330
|
+
protected readonly noteService: NoteService;
|
|
331
|
+
protected readonly commentService: CommentService;
|
|
332
|
+
protected readonly perPage: number;
|
|
333
|
+
readonly id: number;
|
|
334
|
+
readonly slug: string;
|
|
335
|
+
readonly handle: string;
|
|
336
|
+
readonly name: string;
|
|
337
|
+
readonly url: string;
|
|
338
|
+
readonly avatarUrl: string;
|
|
339
|
+
readonly bio?: string;
|
|
340
|
+
constructor(rawData: SubstackProfile, postService: PostService, noteService: NoteService, commentService: CommentService, perPage: number);
|
|
341
|
+
posts(options?: {
|
|
342
|
+
limit?: number;
|
|
343
|
+
}): AsyncIterable<PreviewPost>;
|
|
344
|
+
notes(options?: {
|
|
345
|
+
limit?: number;
|
|
346
|
+
}): AsyncIterable<Note>;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
declare class OwnProfile extends Profile {
|
|
350
|
+
private readonly profileService;
|
|
351
|
+
private readonly followingService;
|
|
352
|
+
private readonly newNoteService;
|
|
353
|
+
constructor(rawData: SubstackProfile, postService: PostService, noteService: NoteService, commentService: CommentService, profileService: ProfileService, followingService: FollowingService, newNoteService: NewNoteService, perPage: number);
|
|
354
|
+
publishNote(content: string, options?: {
|
|
355
|
+
attachmentIds?: string[];
|
|
356
|
+
}): Promise<SubstackCreateNoteResponse>;
|
|
357
|
+
following(options?: {
|
|
358
|
+
limit?: number;
|
|
359
|
+
}): AsyncIterable<Profile>;
|
|
360
|
+
notes(options?: {
|
|
361
|
+
limit?: number;
|
|
362
|
+
}): AsyncIterable<Note>;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Configuration interfaces for the Substack API client
|
|
367
|
+
*/
|
|
368
|
+
interface SubstackConfig {
|
|
369
|
+
substackSid: string;
|
|
370
|
+
substackLli: string;
|
|
371
|
+
publicationUrl: string;
|
|
372
|
+
handle?: string;
|
|
373
|
+
perPage?: number;
|
|
374
|
+
maxRequestsPerSecond?: number;
|
|
375
|
+
}
|
|
376
|
+
interface PaginationParams {
|
|
377
|
+
limit?: number;
|
|
378
|
+
offset?: number;
|
|
379
|
+
}
|
|
380
|
+
interface SearchParams extends PaginationParams {
|
|
381
|
+
query: string;
|
|
382
|
+
sort?: 'top' | 'new';
|
|
383
|
+
author?: string;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Domain interfaces for iterator options and user-facing types
|
|
388
|
+
*/
|
|
389
|
+
interface PostsIteratorOptions {
|
|
390
|
+
limit?: number;
|
|
391
|
+
}
|
|
392
|
+
interface CommentsIteratorOptions {
|
|
393
|
+
postId?: number;
|
|
394
|
+
limit?: number;
|
|
395
|
+
}
|
|
396
|
+
interface NotesIteratorOptions {
|
|
397
|
+
limit?: number;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
declare class SubstackClient {
|
|
401
|
+
private readonly client;
|
|
402
|
+
private readonly postService;
|
|
403
|
+
private readonly noteService;
|
|
404
|
+
private readonly profileService;
|
|
405
|
+
private readonly commentService;
|
|
406
|
+
private readonly followingService;
|
|
407
|
+
private readonly connectivityService;
|
|
408
|
+
private readonly newNoteService;
|
|
409
|
+
private readonly perPage;
|
|
410
|
+
private readonly handle?;
|
|
411
|
+
constructor(config: SubstackConfig);
|
|
412
|
+
testConnectivity(): Promise<boolean>;
|
|
413
|
+
ownProfile(): Promise<OwnProfile>;
|
|
414
|
+
profileForHandle(handle: string): Promise<Profile>;
|
|
415
|
+
postForSlug(slug: string, publicationSubdomain?: string): Promise<FullPost>;
|
|
416
|
+
close(): Promise<void>;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export { Comment, FullPost, Note, OwnProfile, PreviewPost, Profile, SubstackClient };
|
|
420
|
+
export type { CommentsIteratorOptions, NotesIteratorOptions, PaginationParams, PostsIteratorOptions, SearchParams, SubstackConfig, SubstackCreateNoteResponse };
|