@ever-works/bluesky-connector-plugin 1.0.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.
@@ -0,0 +1,166 @@
1
+ import { IConnectorPlugin, IEventSourcePlugin, PluginCategory, ConnectorMetadata, JsonSchema, ChannelTargetConfig, ConnectorCallOptions, ChannelVerification, ChannelSendInput, ChannelSendResult, EventSourcePullInput, EventSourcePullResult } from '@ever-works/plugin';
2
+
3
+ /**
4
+ * Payload text cap for ingested envelopes. Envelope payloads are
5
+ * size-capped platform-side (32 KB serialized); a post body never needs
6
+ * more than this to be useful in Memory/Activities.
7
+ */
8
+ declare const BLUESKY_EVENT_TEXT_MAX_CHARS = 4000;
9
+ /** Items requested per Bluesky page (notifications and feed alike). */
10
+ declare const BLUESKY_PULL_PAGE_SIZE = 50;
11
+ /**
12
+ * Historical backfill bound: at most this many pages per phase
13
+ * (notifications, then the account's own posts) during the opt-in
14
+ * first-pull backfill, so a busy account can never turn activation
15
+ * into an unbounded crawl.
16
+ */
17
+ declare const BLUESKY_BACKFILL_MAX_PAGES = 10;
18
+ /** Default AT Protocol PDS when the operator does not override it. */
19
+ declare const BLUESKY_DEFAULT_SERVICE = "https://bsky.social";
20
+ /** Clamp the opt-in backfill window to the supported 0–90 day range. */
21
+ declare function clampBackfillDays(value: unknown): number;
22
+ /**
23
+ * `at://<repo>/app.bsky.feed.post/<rkey>` → the public bsky.app
24
+ * permalink. Uses the author handle when known (nicer links) and falls
25
+ * back to the repo DID. Returns undefined for anything that is not a
26
+ * post AT-URI, so callers simply omit `sourceUrl`.
27
+ */
28
+ declare function postUrlFromAtUri(uri: string | undefined, handle?: string): string | undefined;
29
+ /** Minimal shape of an actor we consume. */
30
+ interface BlueskyActor {
31
+ did?: string;
32
+ handle?: string;
33
+ displayName?: string;
34
+ }
35
+ /** Minimal shape of a notification we consume. */
36
+ interface BlueskyNotification {
37
+ uri?: string;
38
+ cid?: string;
39
+ author?: BlueskyActor;
40
+ reason?: string;
41
+ reasonSubject?: string;
42
+ record?: {
43
+ text?: string;
44
+ };
45
+ isRead?: boolean;
46
+ indexedAt?: string;
47
+ }
48
+ /** Minimal shape of an author-feed item we consume. */
49
+ interface BlueskyFeedItem {
50
+ post?: {
51
+ uri?: string;
52
+ cid?: string;
53
+ author?: BlueskyActor;
54
+ record?: {
55
+ text?: string;
56
+ createdAt?: string;
57
+ };
58
+ replyCount?: number;
59
+ repostCount?: number;
60
+ likeCount?: number;
61
+ indexedAt?: string;
62
+ };
63
+ }
64
+ /** The subset of the `AtpAgent` surface this plugin calls. */
65
+ interface BlueskyAgentLike {
66
+ login(input: {
67
+ identifier: string;
68
+ password: string;
69
+ }): Promise<{
70
+ data?: {
71
+ did?: string;
72
+ handle?: string;
73
+ };
74
+ }>;
75
+ post(record: Record<string, unknown>): Promise<{
76
+ uri?: string;
77
+ cid?: string;
78
+ }>;
79
+ listNotifications(params: Record<string, unknown>): Promise<{
80
+ data?: {
81
+ notifications?: BlueskyNotification[];
82
+ cursor?: string;
83
+ };
84
+ }>;
85
+ getAuthorFeed(params: Record<string, unknown>): Promise<{
86
+ data?: {
87
+ feed?: BlueskyFeedItem[];
88
+ cursor?: string;
89
+ };
90
+ }>;
91
+ }
92
+ /** Resolved credentials for one call. */
93
+ interface BlueskyCredentials {
94
+ identifier: string;
95
+ appPassword: string;
96
+ service: string;
97
+ }
98
+ /**
99
+ * Resolve credentials from the per-connection target config, falling
100
+ * back to the resolved plugin settings. Returns undefined (never a
101
+ * partial) so callers fail loudly with one clear message.
102
+ */
103
+ declare function resolveCredentials(config: ChannelTargetConfig, options: ConnectorCallOptions): BlueskyCredentials | undefined;
104
+ /**
105
+ * Bluesky (AT Protocol) social connector — first-party native connector.
106
+ *
107
+ * Outbound: `send` publishes a post — or a threaded reply when the
108
+ * target config carries `replyToUri`/`replyToCid` — through the
109
+ * official `@atproto/api` SDK using an app password (never the account
110
+ * password).
111
+ *
112
+ * Event source: `pullEvents` sweeps notifications (mentions, replies,
113
+ * likes, reposts, follows) then the connected account's own posts since
114
+ * the watermark, normalized into `IngestedEventEnvelope`s
115
+ * (`bluesky.notification` / `bluesky.post`, bsky.app permalink as
116
+ * `sourceUrl`) for the platform's event-ingest spine. Neither API can
117
+ * filter by time, so each phase is windowed client-side newest-first
118
+ * and stops at the first item older than the watermark. The opt-in
119
+ * historical backfill (`backfillDays`, default 0 = off, max 90) widens
120
+ * the FIRST pull's window only, with a per-phase page bound.
121
+ * Re-delivery across overlapping windows is fine — the ingest pipeline
122
+ * dedupes on `(source, sourceEventId)`.
123
+ *
124
+ * Unconfigured is always LOUD: `verifyConnection` reports the missing
125
+ * credentials, `send` throws, and `pullEvents` throws
126
+ * `EventSourceNotConfiguredError`. Nothing silently no-ops.
127
+ */
128
+ declare class BlueskyConnectorPlugin implements IConnectorPlugin, IEventSourcePlugin {
129
+ readonly id = "bluesky-connector";
130
+ readonly name = "Bluesky Connector";
131
+ readonly version = "1.0.0";
132
+ readonly category: PluginCategory;
133
+ readonly capabilities: readonly ["connector", "connector-bluesky", "event-source"];
134
+ readonly providerName = "bluesky";
135
+ readonly connector: ConnectorMetadata;
136
+ readonly settingsSchema: JsonSchema;
137
+ private readonly idempotencyCache;
138
+ onLoad(): Promise<void>;
139
+ onUnload(): Promise<void>;
140
+ /** Testable seam — specs stub this; production uses the real SDK. */
141
+ protected createAgent(service: string): BlueskyAgentLike;
142
+ /**
143
+ * Build an authenticated agent. The PDS URL is operator-supplied, so
144
+ * it passes the shared SSRF guard before any request is issued.
145
+ */
146
+ private connect;
147
+ verifyConnection(config: ChannelTargetConfig, options: ConnectorCallOptions): Promise<ChannelVerification>;
148
+ /** Outbound leg — publish a post (or a threaded reply). */
149
+ send(payload: ChannelSendInput, options: ConnectorCallOptions): Promise<ChannelSendResult>;
150
+ /**
151
+ * Pull one page of the current sweep phase (notifications → own
152
+ * posts) since the effective watermark, normalized to envelopes. The
153
+ * returned cursor resumes the same phase (AT Protocol page cursor) or
154
+ * advances to the next; no cursor means the sweep is done.
155
+ *
156
+ * First pull (epoch/absent watermark, no cursor): the window is
157
+ * `now - backfillDays` when the opt-in backfill is on, otherwise
158
+ * `now` — history stays untouched unless the user asked for it.
159
+ */
160
+ pullEvents(input: EventSourcePullInput): Promise<EventSourcePullResult>;
161
+ private normalizeNotification;
162
+ private normalizePost;
163
+ }
164
+ declare const blueskyConnectorPlugin: BlueskyConnectorPlugin;
165
+
166
+ export { BLUESKY_BACKFILL_MAX_PAGES, BLUESKY_DEFAULT_SERVICE, BLUESKY_EVENT_TEXT_MAX_CHARS, BLUESKY_PULL_PAGE_SIZE, BlueskyConnectorPlugin, blueskyConnectorPlugin, clampBackfillDays, BlueskyConnectorPlugin as default, postUrlFromAtUri, resolveCredentials };
@@ -0,0 +1,166 @@
1
+ import { IConnectorPlugin, IEventSourcePlugin, PluginCategory, ConnectorMetadata, JsonSchema, ChannelTargetConfig, ConnectorCallOptions, ChannelVerification, ChannelSendInput, ChannelSendResult, EventSourcePullInput, EventSourcePullResult } from '@ever-works/plugin';
2
+
3
+ /**
4
+ * Payload text cap for ingested envelopes. Envelope payloads are
5
+ * size-capped platform-side (32 KB serialized); a post body never needs
6
+ * more than this to be useful in Memory/Activities.
7
+ */
8
+ declare const BLUESKY_EVENT_TEXT_MAX_CHARS = 4000;
9
+ /** Items requested per Bluesky page (notifications and feed alike). */
10
+ declare const BLUESKY_PULL_PAGE_SIZE = 50;
11
+ /**
12
+ * Historical backfill bound: at most this many pages per phase
13
+ * (notifications, then the account's own posts) during the opt-in
14
+ * first-pull backfill, so a busy account can never turn activation
15
+ * into an unbounded crawl.
16
+ */
17
+ declare const BLUESKY_BACKFILL_MAX_PAGES = 10;
18
+ /** Default AT Protocol PDS when the operator does not override it. */
19
+ declare const BLUESKY_DEFAULT_SERVICE = "https://bsky.social";
20
+ /** Clamp the opt-in backfill window to the supported 0–90 day range. */
21
+ declare function clampBackfillDays(value: unknown): number;
22
+ /**
23
+ * `at://<repo>/app.bsky.feed.post/<rkey>` → the public bsky.app
24
+ * permalink. Uses the author handle when known (nicer links) and falls
25
+ * back to the repo DID. Returns undefined for anything that is not a
26
+ * post AT-URI, so callers simply omit `sourceUrl`.
27
+ */
28
+ declare function postUrlFromAtUri(uri: string | undefined, handle?: string): string | undefined;
29
+ /** Minimal shape of an actor we consume. */
30
+ interface BlueskyActor {
31
+ did?: string;
32
+ handle?: string;
33
+ displayName?: string;
34
+ }
35
+ /** Minimal shape of a notification we consume. */
36
+ interface BlueskyNotification {
37
+ uri?: string;
38
+ cid?: string;
39
+ author?: BlueskyActor;
40
+ reason?: string;
41
+ reasonSubject?: string;
42
+ record?: {
43
+ text?: string;
44
+ };
45
+ isRead?: boolean;
46
+ indexedAt?: string;
47
+ }
48
+ /** Minimal shape of an author-feed item we consume. */
49
+ interface BlueskyFeedItem {
50
+ post?: {
51
+ uri?: string;
52
+ cid?: string;
53
+ author?: BlueskyActor;
54
+ record?: {
55
+ text?: string;
56
+ createdAt?: string;
57
+ };
58
+ replyCount?: number;
59
+ repostCount?: number;
60
+ likeCount?: number;
61
+ indexedAt?: string;
62
+ };
63
+ }
64
+ /** The subset of the `AtpAgent` surface this plugin calls. */
65
+ interface BlueskyAgentLike {
66
+ login(input: {
67
+ identifier: string;
68
+ password: string;
69
+ }): Promise<{
70
+ data?: {
71
+ did?: string;
72
+ handle?: string;
73
+ };
74
+ }>;
75
+ post(record: Record<string, unknown>): Promise<{
76
+ uri?: string;
77
+ cid?: string;
78
+ }>;
79
+ listNotifications(params: Record<string, unknown>): Promise<{
80
+ data?: {
81
+ notifications?: BlueskyNotification[];
82
+ cursor?: string;
83
+ };
84
+ }>;
85
+ getAuthorFeed(params: Record<string, unknown>): Promise<{
86
+ data?: {
87
+ feed?: BlueskyFeedItem[];
88
+ cursor?: string;
89
+ };
90
+ }>;
91
+ }
92
+ /** Resolved credentials for one call. */
93
+ interface BlueskyCredentials {
94
+ identifier: string;
95
+ appPassword: string;
96
+ service: string;
97
+ }
98
+ /**
99
+ * Resolve credentials from the per-connection target config, falling
100
+ * back to the resolved plugin settings. Returns undefined (never a
101
+ * partial) so callers fail loudly with one clear message.
102
+ */
103
+ declare function resolveCredentials(config: ChannelTargetConfig, options: ConnectorCallOptions): BlueskyCredentials | undefined;
104
+ /**
105
+ * Bluesky (AT Protocol) social connector — first-party native connector.
106
+ *
107
+ * Outbound: `send` publishes a post — or a threaded reply when the
108
+ * target config carries `replyToUri`/`replyToCid` — through the
109
+ * official `@atproto/api` SDK using an app password (never the account
110
+ * password).
111
+ *
112
+ * Event source: `pullEvents` sweeps notifications (mentions, replies,
113
+ * likes, reposts, follows) then the connected account's own posts since
114
+ * the watermark, normalized into `IngestedEventEnvelope`s
115
+ * (`bluesky.notification` / `bluesky.post`, bsky.app permalink as
116
+ * `sourceUrl`) for the platform's event-ingest spine. Neither API can
117
+ * filter by time, so each phase is windowed client-side newest-first
118
+ * and stops at the first item older than the watermark. The opt-in
119
+ * historical backfill (`backfillDays`, default 0 = off, max 90) widens
120
+ * the FIRST pull's window only, with a per-phase page bound.
121
+ * Re-delivery across overlapping windows is fine — the ingest pipeline
122
+ * dedupes on `(source, sourceEventId)`.
123
+ *
124
+ * Unconfigured is always LOUD: `verifyConnection` reports the missing
125
+ * credentials, `send` throws, and `pullEvents` throws
126
+ * `EventSourceNotConfiguredError`. Nothing silently no-ops.
127
+ */
128
+ declare class BlueskyConnectorPlugin implements IConnectorPlugin, IEventSourcePlugin {
129
+ readonly id = "bluesky-connector";
130
+ readonly name = "Bluesky Connector";
131
+ readonly version = "1.0.0";
132
+ readonly category: PluginCategory;
133
+ readonly capabilities: readonly ["connector", "connector-bluesky", "event-source"];
134
+ readonly providerName = "bluesky";
135
+ readonly connector: ConnectorMetadata;
136
+ readonly settingsSchema: JsonSchema;
137
+ private readonly idempotencyCache;
138
+ onLoad(): Promise<void>;
139
+ onUnload(): Promise<void>;
140
+ /** Testable seam — specs stub this; production uses the real SDK. */
141
+ protected createAgent(service: string): BlueskyAgentLike;
142
+ /**
143
+ * Build an authenticated agent. The PDS URL is operator-supplied, so
144
+ * it passes the shared SSRF guard before any request is issued.
145
+ */
146
+ private connect;
147
+ verifyConnection(config: ChannelTargetConfig, options: ConnectorCallOptions): Promise<ChannelVerification>;
148
+ /** Outbound leg — publish a post (or a threaded reply). */
149
+ send(payload: ChannelSendInput, options: ConnectorCallOptions): Promise<ChannelSendResult>;
150
+ /**
151
+ * Pull one page of the current sweep phase (notifications → own
152
+ * posts) since the effective watermark, normalized to envelopes. The
153
+ * returned cursor resumes the same phase (AT Protocol page cursor) or
154
+ * advances to the next; no cursor means the sweep is done.
155
+ *
156
+ * First pull (epoch/absent watermark, no cursor): the window is
157
+ * `now - backfillDays` when the opt-in backfill is on, otherwise
158
+ * `now` — history stays untouched unless the user asked for it.
159
+ */
160
+ pullEvents(input: EventSourcePullInput): Promise<EventSourcePullResult>;
161
+ private normalizeNotification;
162
+ private normalizePost;
163
+ }
164
+ declare const blueskyConnectorPlugin: BlueskyConnectorPlugin;
165
+
166
+ export { BLUESKY_BACKFILL_MAX_PAGES, BLUESKY_DEFAULT_SERVICE, BLUESKY_EVENT_TEXT_MAX_CHARS, BLUESKY_PULL_PAGE_SIZE, BlueskyConnectorPlugin, blueskyConnectorPlugin, clampBackfillDays, BlueskyConnectorPlugin as default, postUrlFromAtUri, resolveCredentials };