@classytic/social 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/CHANGELOG.md +65 -0
- package/LICENSE +21 -0
- package/README.md +368 -0
- package/dist/base-Bw7e52V8.mjs +246 -0
- package/dist/base-Bw7e52V8.mjs.map +1 -0
- package/dist/base-DBtKFiSX.d.mts +226 -0
- package/dist/base-DBtKFiSX.d.mts.map +1 -0
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/client/index.d.mts +44 -0
- package/dist/client/index.d.mts.map +1 -0
- package/dist/client/index.mjs +154 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/common/index.d.mts +3 -0
- package/dist/common/index.mjs +7 -0
- package/dist/contracts-Cdwa4zlg.d.mts +121 -0
- package/dist/contracts-Cdwa4zlg.d.mts.map +1 -0
- package/dist/contracts-lCa069IK.mjs +221 -0
- package/dist/contracts-lCa069IK.mjs.map +1 -0
- package/dist/env-Bl0cwwjC.mjs +955 -0
- package/dist/env-Bl0cwwjC.mjs.map +1 -0
- package/dist/env-DxOZHf0p.d.mts +394 -0
- package/dist/env-DxOZHf0p.d.mts.map +1 -0
- package/dist/errors-Cm6LeKf7.mjs +32 -0
- package/dist/errors-Cm6LeKf7.mjs.map +1 -0
- package/dist/facebook-l_4CghaA.mjs +95 -0
- package/dist/facebook-l_4CghaA.mjs.map +1 -0
- package/dist/http-DpcLSR1M.mjs +197 -0
- package/dist/http-DpcLSR1M.mjs.map +1 -0
- package/dist/index.d.mts +42 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +71 -0
- package/dist/index.mjs.map +1 -0
- package/dist/instagram-BGaeUFU2.mjs +90 -0
- package/dist/instagram-BGaeUFU2.mjs.map +1 -0
- package/dist/linkedin-70whtVKa.mjs +101 -0
- package/dist/linkedin-70whtVKa.mjs.map +1 -0
- package/dist/meta-D3vcJU1c.mjs +126 -0
- package/dist/meta-D3vcJU1c.mjs.map +1 -0
- package/dist/pkce-jq5II68b.mjs +72 -0
- package/dist/pkce-jq5II68b.mjs.map +1 -0
- package/dist/polling-DZ1apXtA.mjs +25 -0
- package/dist/polling-DZ1apXtA.mjs.map +1 -0
- package/dist/providers/facebook.d.mts +135 -0
- package/dist/providers/facebook.d.mts.map +1 -0
- package/dist/providers/facebook.mjs +450 -0
- package/dist/providers/facebook.mjs.map +1 -0
- package/dist/providers/instagram.d.mts +122 -0
- package/dist/providers/instagram.d.mts.map +1 -0
- package/dist/providers/instagram.mjs +496 -0
- package/dist/providers/instagram.mjs.map +1 -0
- package/dist/providers/linkedin.d.mts +145 -0
- package/dist/providers/linkedin.d.mts.map +1 -0
- package/dist/providers/linkedin.mjs +574 -0
- package/dist/providers/linkedin.mjs.map +1 -0
- package/dist/providers/reddit.d.mts +102 -0
- package/dist/providers/reddit.d.mts.map +1 -0
- package/dist/providers/reddit.mjs +657 -0
- package/dist/providers/reddit.mjs.map +1 -0
- package/dist/providers/telegram.d.mts +139 -0
- package/dist/providers/telegram.d.mts.map +1 -0
- package/dist/providers/telegram.mjs +517 -0
- package/dist/providers/telegram.mjs.map +1 -0
- package/dist/providers/tiktok.d.mts +116 -0
- package/dist/providers/tiktok.d.mts.map +1 -0
- package/dist/providers/tiktok.mjs +676 -0
- package/dist/providers/tiktok.mjs.map +1 -0
- package/dist/providers/twitter.d.mts +150 -0
- package/dist/providers/twitter.d.mts.map +1 -0
- package/dist/providers/twitter.mjs +628 -0
- package/dist/providers/twitter.mjs.map +1 -0
- package/dist/providers/whatsapp.d.mts +79 -0
- package/dist/providers/whatsapp.d.mts.map +1 -0
- package/dist/providers/whatsapp.mjs +376 -0
- package/dist/providers/whatsapp.mjs.map +1 -0
- package/dist/providers/youtube.d.mts +153 -0
- package/dist/providers/youtube.d.mts.map +1 -0
- package/dist/providers/youtube.mjs +902 -0
- package/dist/providers/youtube.mjs.map +1 -0
- package/dist/reddit-B10kS4Se.mjs +126 -0
- package/dist/reddit-B10kS4Se.mjs.map +1 -0
- package/dist/schemas/index.d.mts +819 -0
- package/dist/schemas/index.d.mts.map +1 -0
- package/dist/schemas/index.mjs +31 -0
- package/dist/schemas/index.mjs.map +1 -0
- package/dist/security-BXhfebWm.d.mts +338 -0
- package/dist/security-BXhfebWm.d.mts.map +1 -0
- package/dist/shared-Fvc6xQku.mjs +100 -0
- package/dist/shared-Fvc6xQku.mjs.map +1 -0
- package/dist/telegram-FaUHpZgB.mjs +107 -0
- package/dist/telegram-FaUHpZgB.mjs.map +1 -0
- package/dist/tiktok-B_bMk4G-.mjs +94 -0
- package/dist/tiktok-B_bMk4G-.mjs.map +1 -0
- package/dist/twitter-BC22zfuc.mjs +98 -0
- package/dist/twitter-BC22zfuc.mjs.map +1 -0
- package/dist/types-BFE4psYI.d.mts +102 -0
- package/dist/types-BFE4psYI.d.mts.map +1 -0
- package/dist/types-Bv27tcT0.d.mts +230 -0
- package/dist/types-Bv27tcT0.d.mts.map +1 -0
- package/dist/types-BwkKyqpi.d.mts +253 -0
- package/dist/types-BwkKyqpi.d.mts.map +1 -0
- package/dist/types-CJrHMDV9.mjs +27 -0
- package/dist/types-CJrHMDV9.mjs.map +1 -0
- package/dist/types-ClbVc2rc.d.mts +117 -0
- package/dist/types-ClbVc2rc.d.mts.map +1 -0
- package/dist/types-D91N16Ym.d.mts +242 -0
- package/dist/types-D91N16Ym.d.mts.map +1 -0
- package/dist/types-DfLp_ibQ.d.mts +178 -0
- package/dist/types-DfLp_ibQ.d.mts.map +1 -0
- package/dist/types-DfjDgEoJ.d.mts +88 -0
- package/dist/types-DfjDgEoJ.d.mts.map +1 -0
- package/dist/types-Dp5Z9VBr.mjs +23 -0
- package/dist/types-Dp5Z9VBr.mjs.map +1 -0
- package/dist/types-hriBJTsU.d.mts +129 -0
- package/dist/types-hriBJTsU.d.mts.map +1 -0
- package/dist/types-rn6UuLL8.d.mts +184 -0
- package/dist/types-rn6UuLL8.d.mts.map +1 -0
- package/dist/whatsapp-CFp7ryR4.mjs +101 -0
- package/dist/whatsapp-CFp7ryR4.mjs.map +1 -0
- package/dist/youtube-Bs0fdY7H.mjs +98 -0
- package/dist/youtube-Bs0fdY7H.mjs.map +1 -0
- package/package.json +148 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { g as UploadResult } from "./base-DBtKFiSX.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/common/contracts.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Provider authentication context. The shape varies by `authType`:
|
|
6
|
+
* - oauth2: `{ accessToken, ...tokens }` from a stored OAuthTokens record.
|
|
7
|
+
* - token / api_key: `{ accessToken: <bot/api token> }`.
|
|
8
|
+
*
|
|
9
|
+
* Providers may also need credentials (clientId/clientSecret) — pass via `creds`.
|
|
10
|
+
*/
|
|
11
|
+
interface ProviderAuth {
|
|
12
|
+
accessToken: string;
|
|
13
|
+
refreshToken?: string;
|
|
14
|
+
/** Provider-specific account / page / channel identifier. */
|
|
15
|
+
resourceId?: string;
|
|
16
|
+
/** Decrypted credential metadata (clientId, appSecret, etc.) for OAuth2 providers. */
|
|
17
|
+
creds?: Record<string, unknown>;
|
|
18
|
+
/** Extra OAuth fields (open_id, expires_at, etc.). */
|
|
19
|
+
extras?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
interface UnifiedMedia {
|
|
22
|
+
type: 'image' | 'video';
|
|
23
|
+
/** Public URL of the asset. Providers that require buffer uploads will fetch this. */
|
|
24
|
+
url: string;
|
|
25
|
+
/** Optional alt text / caption. */
|
|
26
|
+
alt?: string;
|
|
27
|
+
}
|
|
28
|
+
interface UnifiedPostInput {
|
|
29
|
+
/** Body text / caption. Required for text-only posts. */
|
|
30
|
+
text?: string;
|
|
31
|
+
/** Optional media attachments (single or multi-image / video). */
|
|
32
|
+
media?: UnifiedMedia[];
|
|
33
|
+
/** Optional URL for link-share posts. */
|
|
34
|
+
link?: string;
|
|
35
|
+
/** Optional schedule timestamp (ISO 8601). Provider must support scheduling. */
|
|
36
|
+
scheduledAt?: string | Date;
|
|
37
|
+
/** Provider-specific overrides — escape hatch for advanced consumers. */
|
|
38
|
+
raw?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
interface UnifiedPost {
|
|
41
|
+
/** Stable provider-side post identifier. */
|
|
42
|
+
id: string;
|
|
43
|
+
/** Permalink to the post on the provider's site. */
|
|
44
|
+
url?: string | null;
|
|
45
|
+
/** Body text / caption, when available. */
|
|
46
|
+
text?: string;
|
|
47
|
+
createdAt?: string;
|
|
48
|
+
/** Engagement metrics, when available. */
|
|
49
|
+
metrics?: {
|
|
50
|
+
likes?: number;
|
|
51
|
+
replies?: number;
|
|
52
|
+
shares?: number;
|
|
53
|
+
views?: number;
|
|
54
|
+
};
|
|
55
|
+
/** Original provider response for advanced consumers. */
|
|
56
|
+
raw?: Record<string, unknown>;
|
|
57
|
+
}
|
|
58
|
+
interface ListPostsOptions {
|
|
59
|
+
cursor?: string;
|
|
60
|
+
limit?: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Capability: create and manage posts.
|
|
64
|
+
*/
|
|
65
|
+
interface PostingProvider {
|
|
66
|
+
createPost(auth: ProviderAuth, input: UnifiedPostInput): Promise<UploadResult>;
|
|
67
|
+
deletePost?(auth: ProviderAuth, id: string): Promise<void>;
|
|
68
|
+
getPost?(auth: ProviderAuth, id: string): Promise<UnifiedPost>;
|
|
69
|
+
listPosts?(auth: ProviderAuth, opts?: ListPostsOptions): Promise<{
|
|
70
|
+
items: UnifiedPost[];
|
|
71
|
+
nextCursor?: string;
|
|
72
|
+
}>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Capability: messaging (Telegram, WhatsApp, Twitter DMs).
|
|
76
|
+
*/
|
|
77
|
+
interface MessagingProvider {
|
|
78
|
+
sendTextMessage(auth: ProviderAuth, recipient: string, text: string): Promise<{
|
|
79
|
+
id: string;
|
|
80
|
+
}>;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Capability: media upload (video, photo).
|
|
84
|
+
*/
|
|
85
|
+
interface UploadingProvider {
|
|
86
|
+
uploadMedia(auth: ProviderAuth, media: UnifiedMedia, opts?: {
|
|
87
|
+
caption?: string;
|
|
88
|
+
}): Promise<UploadResult>;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Capability declaration — paired with `getCapabilities()` on each provider so
|
|
92
|
+
* consumers can introspect what each provider supports.
|
|
93
|
+
*/
|
|
94
|
+
interface ProviderCapabilities {
|
|
95
|
+
/** Authentication strategy. */
|
|
96
|
+
auth: 'oauth2' | 'token' | 'api_key';
|
|
97
|
+
/** Whether the provider supports text post publishing. */
|
|
98
|
+
posting: boolean;
|
|
99
|
+
/** Whether the provider supports media (photo/video) upload. */
|
|
100
|
+
upload: boolean;
|
|
101
|
+
/** Whether the provider supports messaging. */
|
|
102
|
+
messaging: boolean;
|
|
103
|
+
/** Whether the provider supports scheduled publishing. */
|
|
104
|
+
scheduling: boolean;
|
|
105
|
+
/** Whether the provider supports post deletion. */
|
|
106
|
+
deletion: boolean;
|
|
107
|
+
/** Whether the provider supports listing existing posts. */
|
|
108
|
+
listing: boolean;
|
|
109
|
+
/** Whether the provider supports analytics / insights. */
|
|
110
|
+
analytics: boolean;
|
|
111
|
+
/** Whether the provider supports sandbox/production environments. */
|
|
112
|
+
environments: boolean;
|
|
113
|
+
/** Whether OAuth uses PKCE. */
|
|
114
|
+
pkce: boolean;
|
|
115
|
+
}
|
|
116
|
+
declare function isPostingProvider(p: unknown): p is PostingProvider;
|
|
117
|
+
declare function isMessagingProvider(p: unknown): p is MessagingProvider;
|
|
118
|
+
declare function isUploadingProvider(p: unknown): p is UploadingProvider;
|
|
119
|
+
//#endregion
|
|
120
|
+
export { ProviderCapabilities as a, UnifiedPostInput as c, isPostingProvider as d, isUploadingProvider as f, ProviderAuth as i, UploadingProvider as l, MessagingProvider as n, UnifiedMedia as o, PostingProvider as r, UnifiedPost as s, ListPostsOptions as t, isMessagingProvider as u };
|
|
121
|
+
//# sourceMappingURL=contracts-Cdwa4zlg.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts-Cdwa4zlg.d.mts","names":[],"sources":["../src/common/contracts.ts"],"mappings":";;;;AAmCA;;;;;;UAXiB,YAAA;EACf,WAAA;EACA,YAAA;EAiBe;EAff,UAAA;;EAEA,KAAA,GAAQ,MAAA;EAqBe;EAnBvB,MAAA,GAAS,MAAA;AAAA;AAAA,UAGM,YAAA;EACf,IAAA;EAWA;EATA,GAAA;EAWA;EATA,GAAA;AAAA;AAAA,UAGe,gBAAA;EAUT;EARN,IAAA;EAQY;EANZ,KAAA,GAAQ,YAAA;EASkB;EAP1B,IAAA;EAuBY;EArBZ,WAAA,YAAuB,IAAA;EASvB;EAPA,GAAA,GAAM,MAAA;AAAA;AAAA,UAGS,WAAA;EAUb;EARF,EAAA;EAUE;EARF,GAAA;EAYA;EAVA,IAAA;EACA,SAAA;EASY;EAPZ,OAAA;IACE,KAAA;IACA,OAAA;IACA,MAAA;IACA,KAAA;EAAA;EAc4B;EAX9B,GAAA,GAAM,MAAA;AAAA;AAAA,UAGS,gBAAA;EACf,MAAA;EACA,KAAA;AAAA;;;;UAMe,eAAA;EACf,UAAA,CAAW,IAAA,EAAM,YAAA,EAAc,KAAA,EAAO,gBAAA,GAAmB,OAAA,CAAQ,YAAA;EACjE,UAAA,EAAY,IAAA,EAAM,YAAA,EAAc,EAAA,WAAa,OAAA;EAC7C,OAAA,EAAS,IAAA,EAAM,YAAA,EAAc,EAAA,WAAa,OAAA,CAAQ,WAAA;EAClD,SAAA,EAAW,IAAA,EAAM,YAAA,EAAc,IAAA,GAAO,gBAAA,GAAmB,OAAA;IAAU,KAAA,EAAO,WAAA;IAAe,UAAA;EAAA;AAAA;;;;UAM1E,iBAAA;EACf,eAAA,CAAgB,IAAA,EAAM,YAAA,EAAc,SAAA,UAAmB,IAAA,WAAe,OAAA;IAAU,EAAA;EAAA;AAAA;;;;UAMjE,iBAAA;EACf,WAAA,CAAY,IAAA,EAAM,YAAA,EAAc,KAAA,EAAO,YAAA,EAAc,IAAA;IAAS,OAAA;EAAA,IAAqB,OAAA,CAAQ,YAAA;AAAA;;;;;UAO5E,oBAAA;EArB0C;EAuBzD,IAAA;EAvB0E;EAyB1E,OAAA;EAzBmG;EA2BnG,MAAA;EArBe;EAuBf,SAAA;;EAEA,UAAA;EAxBA;EA0BA,QAAA;EA1BgB;EA4BhB,OAAA;EA5BuD;EA8BvD,SAAA;EA9BgF;EAgChF,YAAA;EAhCkF;EAkClF,IAAA;AAAA;AAAA,iBAKc,iBAAA,CAAkB,CAAA,YAAa,CAAA,IAAK,eAAA;AAAA,iBAIpC,mBAAA,CAAoB,CAAA,YAAa,CAAA,IAAK,iBAAA;AAAA,iBAItC,mBAAA,CAAoB,CAAA,YAAa,CAAA,IAAK,iBAAA"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { t as SocialError } from "./errors-Cm6LeKf7.mjs";
|
|
2
|
+
import { t as httpRequest } from "./http-DpcLSR1M.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/common/oauth/oauth2.ts
|
|
5
|
+
/**
|
|
6
|
+
* Exchange an authorization code for an access token.
|
|
7
|
+
*
|
|
8
|
+
* The body is `application/x-www-form-urlencoded` per RFC 6749 §4.1.3. Client
|
|
9
|
+
* credentials may be sent in the body (`client_secret`) or via Basic auth — the
|
|
10
|
+
* latter is required by some providers (Reddit). Use `basicAuth: true` to opt in.
|
|
11
|
+
*
|
|
12
|
+
* @throws SocialError on HTTP failure (rate-limit, server error, parse error).
|
|
13
|
+
*/
|
|
14
|
+
async function oauth2ExchangeCode(provider, params) {
|
|
15
|
+
const body = {
|
|
16
|
+
grant_type: "authorization_code",
|
|
17
|
+
code: params.code,
|
|
18
|
+
redirect_uri: params.redirectUri,
|
|
19
|
+
client_id: params.clientId,
|
|
20
|
+
...params.codeVerifier ? { code_verifier: params.codeVerifier } : {},
|
|
21
|
+
...params.extra ?? {}
|
|
22
|
+
};
|
|
23
|
+
const headers = { ...params.headers ?? {} };
|
|
24
|
+
if (params.basicAuth && params.clientSecret) headers.Authorization = `Basic ${btoa(`${params.clientId}:${params.clientSecret}`)}`;
|
|
25
|
+
else if (params.clientSecret) body.client_secret = params.clientSecret;
|
|
26
|
+
const { data } = await httpRequest(provider, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
url: params.tokenUrl,
|
|
29
|
+
urlencoded: body,
|
|
30
|
+
headers,
|
|
31
|
+
parseError: (raw) => {
|
|
32
|
+
if (raw && typeof raw === "object") {
|
|
33
|
+
const r = raw;
|
|
34
|
+
return {
|
|
35
|
+
message: r.error_description || r.error || "OAuth token exchange failed",
|
|
36
|
+
errorCode: r.error ?? null
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
return normalizeTokens(data);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Refresh an access token using a refresh token (RFC 6749 §6).
|
|
46
|
+
*/
|
|
47
|
+
async function oauth2RefreshToken(provider, params) {
|
|
48
|
+
const body = {
|
|
49
|
+
grant_type: "refresh_token",
|
|
50
|
+
refresh_token: params.refreshToken,
|
|
51
|
+
client_id: params.clientId,
|
|
52
|
+
...params.extra ?? {}
|
|
53
|
+
};
|
|
54
|
+
const headers = { ...params.headers ?? {} };
|
|
55
|
+
if (params.basicAuth && params.clientSecret) headers.Authorization = `Basic ${btoa(`${params.clientId}:${params.clientSecret}`)}`;
|
|
56
|
+
else if (params.clientSecret) body.client_secret = params.clientSecret;
|
|
57
|
+
const { data } = await httpRequest(provider, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
url: params.tokenUrl,
|
|
60
|
+
urlencoded: body,
|
|
61
|
+
headers,
|
|
62
|
+
parseError: (raw) => {
|
|
63
|
+
if (raw && typeof raw === "object") {
|
|
64
|
+
const r = raw;
|
|
65
|
+
return {
|
|
66
|
+
message: r.error_description || r.error || "OAuth token refresh failed",
|
|
67
|
+
errorCode: r.error ?? null
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return normalizeTokens(data);
|
|
74
|
+
}
|
|
75
|
+
function normalizeTokens(raw) {
|
|
76
|
+
return {
|
|
77
|
+
access_token: raw.access_token,
|
|
78
|
+
refresh_token: raw.refresh_token,
|
|
79
|
+
expires_in: raw.expires_in,
|
|
80
|
+
token_type: raw.token_type ?? "Bearer",
|
|
81
|
+
scope: raw.scope,
|
|
82
|
+
...raw
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Build an OAuth 2.0 authorization URL (RFC 6749 §4.1.1).
|
|
87
|
+
*/
|
|
88
|
+
function buildAuthUrl(authUrl, params) {
|
|
89
|
+
const scope = Array.isArray(params.scope) ? params.scope.join(params.scopeSeparator ?? " ") : params.scope;
|
|
90
|
+
return `${authUrl}?${new URLSearchParams({
|
|
91
|
+
response_type: params.responseType ?? "code",
|
|
92
|
+
client_id: params.clientId,
|
|
93
|
+
redirect_uri: params.redirectUri,
|
|
94
|
+
state: params.state,
|
|
95
|
+
scope,
|
|
96
|
+
...params.codeChallenge ? {
|
|
97
|
+
code_challenge: params.codeChallenge,
|
|
98
|
+
code_challenge_method: params.codeChallengeMethod ?? "S256"
|
|
99
|
+
} : {},
|
|
100
|
+
...params.extra ?? {}
|
|
101
|
+
}).toString()}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/common/paginate.ts
|
|
106
|
+
function paginate(fetchPage, opts = {}) {
|
|
107
|
+
const maxPages = opts.maxPages ?? 100;
|
|
108
|
+
const maxItems = opts.maxItems;
|
|
109
|
+
async function* iterate() {
|
|
110
|
+
let cursor = void 0;
|
|
111
|
+
let pages = 0;
|
|
112
|
+
let yielded = 0;
|
|
113
|
+
do {
|
|
114
|
+
const page = await fetchPage(cursor);
|
|
115
|
+
for (const item of page.items) {
|
|
116
|
+
if (maxItems !== void 0 && yielded >= maxItems) return;
|
|
117
|
+
yield item;
|
|
118
|
+
yielded++;
|
|
119
|
+
}
|
|
120
|
+
cursor = page.nextCursor;
|
|
121
|
+
pages++;
|
|
122
|
+
} while (cursor && pages < maxPages);
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
[Symbol.asyncIterator]: iterate,
|
|
126
|
+
async toArray() {
|
|
127
|
+
const out = [];
|
|
128
|
+
for await (const item of iterate()) out.push(item);
|
|
129
|
+
return out;
|
|
130
|
+
},
|
|
131
|
+
async take(n) {
|
|
132
|
+
const out = [];
|
|
133
|
+
for await (const item of iterate()) {
|
|
134
|
+
if (out.length >= n) break;
|
|
135
|
+
out.push(item);
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/common/security.ts
|
|
144
|
+
/**
|
|
145
|
+
* Security helpers used at provider boundaries.
|
|
146
|
+
*/
|
|
147
|
+
/**
|
|
148
|
+
* Validate that a URL is safe to fetch on the server side.
|
|
149
|
+
*
|
|
150
|
+
* Rejects:
|
|
151
|
+
* - Non-HTTP(S) schemes (file://, data://, gopher://, etc.)
|
|
152
|
+
* - Localhost / loopback (127.0.0.0/8, ::1)
|
|
153
|
+
* - RFC 1918 private ranges (10/8, 172.16/12, 192.168/16)
|
|
154
|
+
* - Link-local (169.254/16) — blocks AWS/GCP metadata endpoints
|
|
155
|
+
* - Multicast / reserved blocks
|
|
156
|
+
*
|
|
157
|
+
* **Note:** This is a literal-IP guard; it does not resolve DNS. For complete
|
|
158
|
+
* protection (DNS-rebinding), validate again after resolution at fetch time.
|
|
159
|
+
*/
|
|
160
|
+
function assertPublicHttpUrl(provider, raw, context = "url") {
|
|
161
|
+
let url;
|
|
162
|
+
try {
|
|
163
|
+
url = new URL(raw);
|
|
164
|
+
} catch {
|
|
165
|
+
throw new SocialError(provider, `Invalid ${context}: not a valid URL`, { statusCode: 400 });
|
|
166
|
+
}
|
|
167
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") throw new SocialError(provider, `Invalid ${context}: only http(s) URLs are allowed`, { statusCode: 400 });
|
|
168
|
+
if (isLiteralPrivateHost(url.hostname)) throw new SocialError(provider, `Invalid ${context}: private/loopback addresses are not allowed`, { statusCode: 400 });
|
|
169
|
+
return url;
|
|
170
|
+
}
|
|
171
|
+
function isLiteralPrivateHost(hostname) {
|
|
172
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
173
|
+
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
174
|
+
if (host === "::1" || host === "0:0:0:0:0:0:0:1") return true;
|
|
175
|
+
if (host.startsWith("fe80:") || host.startsWith("fc") || host.startsWith("fd")) return true;
|
|
176
|
+
const m = host.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
|
|
177
|
+
if (!m) return false;
|
|
178
|
+
const [, a, b] = m.map(Number);
|
|
179
|
+
if (a === 127) return true;
|
|
180
|
+
if (a === 10) return true;
|
|
181
|
+
if (a === 0) return true;
|
|
182
|
+
if (a === 169 && b === 254) return true;
|
|
183
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
184
|
+
if (a === 192 && b === 168) return true;
|
|
185
|
+
if (a >= 224) return true;
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Redact secrets from arbitrary objects for safe logging.
|
|
190
|
+
* Replaces values for keys matching common secret patterns with `'[REDACTED]'`.
|
|
191
|
+
*/
|
|
192
|
+
function redactSecrets(input) {
|
|
193
|
+
const SECRET_KEY = /token|secret|password|api[_-]?key|credential|cookie|authorization/i;
|
|
194
|
+
function walk(v, depth) {
|
|
195
|
+
if (depth > 10 || v === null || v === void 0) return v;
|
|
196
|
+
if (Array.isArray(v)) return v.map((item) => walk(item, depth + 1));
|
|
197
|
+
if (typeof v === "object") {
|
|
198
|
+
const out = {};
|
|
199
|
+
for (const [k, val] of Object.entries(v)) out[k] = SECRET_KEY.test(k) ? "[REDACTED]" : walk(val, depth + 1);
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
return v;
|
|
203
|
+
}
|
|
204
|
+
return walk(input, 0);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region src/common/contracts.ts
|
|
209
|
+
function isPostingProvider(p) {
|
|
210
|
+
return !!p && typeof p.createPost === "function";
|
|
211
|
+
}
|
|
212
|
+
function isMessagingProvider(p) {
|
|
213
|
+
return !!p && typeof p.sendTextMessage === "function";
|
|
214
|
+
}
|
|
215
|
+
function isUploadingProvider(p) {
|
|
216
|
+
return !!p && typeof p.uploadMedia === "function";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
//#endregion
|
|
220
|
+
export { redactSecrets as a, oauth2ExchangeCode as c, assertPublicHttpUrl as i, oauth2RefreshToken as l, isPostingProvider as n, paginate as o, isUploadingProvider as r, buildAuthUrl as s, isMessagingProvider as t };
|
|
221
|
+
//# sourceMappingURL=contracts-lCa069IK.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts-lCa069IK.mjs","names":[],"sources":["../src/common/oauth/oauth2.ts","../src/common/paginate.ts","../src/common/security.ts","../src/common/contracts.ts"],"sourcesContent":["/**\n * Standard OAuth 2.0 helpers (RFC 6749).\n *\n * Covers code-for-token exchange and refresh-token grants for providers that\n * follow the spec closely (Twitter, Reddit, LinkedIn, TikTok). Provider-specific\n * variants (Meta two-step long-lived) live in `meta.ts`.\n */\n\nimport type { OAuthTokens } from '../../base.js';\nimport { httpRequest } from '../http.js';\n\nexport interface OAuth2ExchangeParams {\n /** Token endpoint URL. */\n tokenUrl: string;\n /** Authorization code from the redirect callback. */\n code: string;\n clientId: string;\n /** Required for confidential clients; sent via Basic auth or body. */\n clientSecret?: string;\n redirectUri: string;\n /** PKCE verifier (S256 or plain). Required by Twitter, TikTok, etc. */\n codeVerifier?: string;\n /** Use HTTP Basic auth for client credentials (Reddit, Twitter confidential). */\n basicAuth?: boolean;\n /** Additional body params to merge in. */\n extra?: Record<string, string>;\n /** Additional headers (e.g., User-Agent for Reddit). */\n headers?: Record<string, string>;\n}\n\nexport interface OAuth2RefreshParams {\n tokenUrl: string;\n refreshToken: string;\n clientId: string;\n clientSecret?: string;\n basicAuth?: boolean;\n extra?: Record<string, string>;\n headers?: Record<string, string>;\n}\n\n/**\n * Exchange an authorization code for an access token.\n *\n * The body is `application/x-www-form-urlencoded` per RFC 6749 §4.1.3. Client\n * credentials may be sent in the body (`client_secret`) or via Basic auth — the\n * latter is required by some providers (Reddit). Use `basicAuth: true` to opt in.\n *\n * @throws SocialError on HTTP failure (rate-limit, server error, parse error).\n */\nexport async function oauth2ExchangeCode(\n provider: string,\n params: OAuth2ExchangeParams,\n): Promise<OAuthTokens> {\n const body: Record<string, string> = {\n grant_type: 'authorization_code',\n code: params.code,\n redirect_uri: params.redirectUri,\n client_id: params.clientId,\n ...(params.codeVerifier ? { code_verifier: params.codeVerifier } : {}),\n ...(params.extra ?? {}),\n };\n\n const headers: Record<string, string> = { ...(params.headers ?? {}) };\n\n if (params.basicAuth && params.clientSecret) {\n headers.Authorization = `Basic ${btoa(`${params.clientId}:${params.clientSecret}`)}`;\n } else if (params.clientSecret) {\n body.client_secret = params.clientSecret;\n }\n\n const { data } = await httpRequest<Record<string, unknown>>(provider, {\n method: 'POST',\n url: params.tokenUrl,\n urlencoded: body,\n headers,\n parseError: (raw) => {\n if (raw && typeof raw === 'object') {\n const r = raw as Record<string, unknown>;\n return {\n message: (r.error_description as string) || (r.error as string) || 'OAuth token exchange failed',\n errorCode: (r.error as string) ?? null,\n };\n }\n return null;\n },\n });\n\n return normalizeTokens(data);\n}\n\n/**\n * Refresh an access token using a refresh token (RFC 6749 §6).\n */\nexport async function oauth2RefreshToken(\n provider: string,\n params: OAuth2RefreshParams,\n): Promise<OAuthTokens> {\n const body: Record<string, string> = {\n grant_type: 'refresh_token',\n refresh_token: params.refreshToken,\n client_id: params.clientId,\n ...(params.extra ?? {}),\n };\n\n const headers: Record<string, string> = { ...(params.headers ?? {}) };\n\n if (params.basicAuth && params.clientSecret) {\n headers.Authorization = `Basic ${btoa(`${params.clientId}:${params.clientSecret}`)}`;\n } else if (params.clientSecret) {\n body.client_secret = params.clientSecret;\n }\n\n const { data } = await httpRequest<Record<string, unknown>>(provider, {\n method: 'POST',\n url: params.tokenUrl,\n urlencoded: body,\n headers,\n parseError: (raw) => {\n if (raw && typeof raw === 'object') {\n const r = raw as Record<string, unknown>;\n return {\n message: (r.error_description as string) || (r.error as string) || 'OAuth token refresh failed',\n errorCode: (r.error as string) ?? null,\n };\n }\n return null;\n },\n });\n\n return normalizeTokens(data);\n}\n\nfunction normalizeTokens(raw: Record<string, unknown>): OAuthTokens {\n return {\n access_token: raw.access_token as string,\n refresh_token: raw.refresh_token as string | undefined,\n expires_in: raw.expires_in as number | undefined,\n token_type: (raw.token_type as string | undefined) ?? 'Bearer',\n scope: raw.scope as string | undefined,\n ...raw,\n };\n}\n\n/**\n * Build an OAuth 2.0 authorization URL (RFC 6749 §4.1.1).\n */\nexport function buildAuthUrl(authUrl: string, params: {\n clientId: string;\n redirectUri: string;\n state: string;\n scope: string | string[];\n /** Default `code` for authorization-code grant. */\n responseType?: string;\n /** Scope separator — space (default) or comma (Facebook). */\n scopeSeparator?: ' ' | ',';\n /** PKCE challenge (S256 or plain). */\n codeChallenge?: string;\n codeChallengeMethod?: 'S256' | 'plain';\n /** Provider-specific extras (access_type, prompt, etc.). */\n extra?: Record<string, string>;\n}): string {\n const scope = Array.isArray(params.scope)\n ? params.scope.join(params.scopeSeparator ?? ' ')\n : params.scope;\n const search = new URLSearchParams({\n response_type: params.responseType ?? 'code',\n client_id: params.clientId,\n redirect_uri: params.redirectUri,\n state: params.state,\n scope,\n ...(params.codeChallenge ? {\n code_challenge: params.codeChallenge,\n code_challenge_method: params.codeChallengeMethod ?? 'S256',\n } : {}),\n ...(params.extra ?? {}),\n });\n return `${authUrl}?${search.toString()}`;\n}\n","/**\n * Generic pagination helper.\n *\n * Wraps a per-page fetcher in an `AsyncIterable` so consumers can do:\n *\n * for await (const post of paginate(fetchPage)) { ... }\n *\n * Use `.toArray()` for the simple case, or `.take(n)` to bound results.\n */\n\nexport interface Page<T> {\n items: T[];\n /** Opaque cursor for the next page, or `undefined` when exhausted. */\n nextCursor?: string;\n}\n\nexport interface PaginateOptions {\n /** Hard cap on items returned across pages. */\n maxItems?: number;\n /** Hard cap on page fetches (defense against infinite loops). Default 100. */\n maxPages?: number;\n}\n\nexport interface PaginatedIterable<T> extends AsyncIterable<T> {\n toArray(): Promise<T[]>;\n take(n: number): Promise<T[]>;\n}\n\nexport function paginate<T>(\n fetchPage: (cursor: string | undefined) => Promise<Page<T>>,\n opts: PaginateOptions = {},\n): PaginatedIterable<T> {\n const maxPages = opts.maxPages ?? 100;\n const maxItems = opts.maxItems;\n\n async function* iterate(): AsyncGenerator<T> {\n let cursor: string | undefined = undefined;\n let pages = 0;\n let yielded = 0;\n do {\n const page = await fetchPage(cursor);\n for (const item of page.items) {\n if (maxItems !== undefined && yielded >= maxItems) return;\n yield item;\n yielded++;\n }\n cursor = page.nextCursor;\n pages++;\n } while (cursor && pages < maxPages);\n }\n\n const iterable: PaginatedIterable<T> = {\n [Symbol.asyncIterator]: iterate,\n async toArray() {\n const out: T[] = [];\n for await (const item of iterate()) out.push(item);\n return out;\n },\n async take(n: number) {\n const out: T[] = [];\n for await (const item of iterate()) {\n if (out.length >= n) break;\n out.push(item);\n }\n return out;\n },\n };\n return iterable;\n}\n","/**\n * Security helpers used at provider boundaries.\n */\n\nimport { SocialError } from '../errors.js';\n\n/**\n * Validate that a URL is safe to fetch on the server side.\n *\n * Rejects:\n * - Non-HTTP(S) schemes (file://, data://, gopher://, etc.)\n * - Localhost / loopback (127.0.0.0/8, ::1)\n * - RFC 1918 private ranges (10/8, 172.16/12, 192.168/16)\n * - Link-local (169.254/16) — blocks AWS/GCP metadata endpoints\n * - Multicast / reserved blocks\n *\n * **Note:** This is a literal-IP guard; it does not resolve DNS. For complete\n * protection (DNS-rebinding), validate again after resolution at fetch time.\n */\nexport function assertPublicHttpUrl(provider: string, raw: string, context = 'url'): URL {\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new SocialError(provider, `Invalid ${context}: not a valid URL`, { statusCode: 400 });\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new SocialError(provider, `Invalid ${context}: only http(s) URLs are allowed`, { statusCode: 400 });\n }\n if (isLiteralPrivateHost(url.hostname)) {\n throw new SocialError(provider, `Invalid ${context}: private/loopback addresses are not allowed`, { statusCode: 400 });\n }\n return url;\n}\n\nfunction isLiteralPrivateHost(hostname: string): boolean {\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, '');\n if (host === 'localhost' || host.endsWith('.localhost')) return true;\n // IPv6 loopback / link-local / unique-local\n if (host === '::1' || host === '0:0:0:0:0:0:0:1') return true;\n if (host.startsWith('fe80:') || host.startsWith('fc') || host.startsWith('fd')) return true;\n // IPv4 dotted quad?\n const m = host.match(/^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)$/);\n if (!m) return false;\n const [, a, b] = m.map(Number) as [number, number, number, number, number];\n if (a === 127) return true; // 127.0.0.0/8\n if (a === 10) return true; // 10.0.0.0/8\n if (a === 0) return true; // 0.0.0.0/8\n if (a === 169 && b === 254) return true; // 169.254.0.0/16 (link-local, AWS metadata)\n if (a === 172 && b! >= 16 && b! <= 31) return true; // 172.16.0.0/12\n if (a === 192 && b === 168) return true; // 192.168.0.0/16\n if (a >= 224) return true; // 224.0.0.0/4 multicast, 240.0.0.0/4 reserved\n return false;\n}\n\n/**\n * Redact secrets from arbitrary objects for safe logging.\n * Replaces values for keys matching common secret patterns with `'[REDACTED]'`.\n */\nexport function redactSecrets(input: unknown): unknown {\n const SECRET_KEY = /token|secret|password|api[_-]?key|credential|cookie|authorization/i;\n function walk(v: unknown, depth: number): unknown {\n if (depth > 10 || v === null || v === undefined) return v;\n if (Array.isArray(v)) return v.map(item => walk(item, depth + 1));\n if (typeof v === 'object') {\n const out: Record<string, unknown> = {};\n for (const [k, val] of Object.entries(v as Record<string, unknown>)) {\n out[k] = SECRET_KEY.test(k) ? '[REDACTED]' : walk(val, depth + 1);\n }\n return out;\n }\n return v;\n }\n return walk(input, 0);\n}\n","/**\n * Unified provider contracts.\n *\n * These interfaces define a *thin façade* over per-provider methods so that\n * registry consumers can dispatch generically without knowing each provider's\n * exact signature. Providers opt into capabilities by implementing the matching\n * interface.\n *\n * Example:\n * const provider = registry.getProvider('facebook');\n * if (isPostingProvider(provider)) {\n * await provider.createPost(auth, { text: 'hello' });\n * }\n */\n\nimport type { OAuthTokens, UploadResult } from '../base.js';\n\n/**\n * Provider authentication context. The shape varies by `authType`:\n * - oauth2: `{ accessToken, ...tokens }` from a stored OAuthTokens record.\n * - token / api_key: `{ accessToken: <bot/api token> }`.\n *\n * Providers may also need credentials (clientId/clientSecret) — pass via `creds`.\n */\nexport interface ProviderAuth {\n accessToken: string;\n refreshToken?: string;\n /** Provider-specific account / page / channel identifier. */\n resourceId?: string;\n /** Decrypted credential metadata (clientId, appSecret, etc.) for OAuth2 providers. */\n creds?: Record<string, unknown>;\n /** Extra OAuth fields (open_id, expires_at, etc.). */\n extras?: Record<string, unknown>;\n}\n\nexport interface UnifiedMedia {\n type: 'image' | 'video';\n /** Public URL of the asset. Providers that require buffer uploads will fetch this. */\n url: string;\n /** Optional alt text / caption. */\n alt?: string;\n}\n\nexport interface UnifiedPostInput {\n /** Body text / caption. Required for text-only posts. */\n text?: string;\n /** Optional media attachments (single or multi-image / video). */\n media?: UnifiedMedia[];\n /** Optional URL for link-share posts. */\n link?: string;\n /** Optional schedule timestamp (ISO 8601). Provider must support scheduling. */\n scheduledAt?: string | Date;\n /** Provider-specific overrides — escape hatch for advanced consumers. */\n raw?: Record<string, unknown>;\n}\n\nexport interface UnifiedPost {\n /** Stable provider-side post identifier. */\n id: string;\n /** Permalink to the post on the provider's site. */\n url?: string | null;\n /** Body text / caption, when available. */\n text?: string;\n createdAt?: string;\n /** Engagement metrics, when available. */\n metrics?: {\n likes?: number;\n replies?: number;\n shares?: number;\n views?: number;\n };\n /** Original provider response for advanced consumers. */\n raw?: Record<string, unknown>;\n}\n\nexport interface ListPostsOptions {\n cursor?: string;\n limit?: number;\n}\n\n/**\n * Capability: create and manage posts.\n */\nexport interface PostingProvider {\n createPost(auth: ProviderAuth, input: UnifiedPostInput): Promise<UploadResult>;\n deletePost?(auth: ProviderAuth, id: string): Promise<void>;\n getPost?(auth: ProviderAuth, id: string): Promise<UnifiedPost>;\n listPosts?(auth: ProviderAuth, opts?: ListPostsOptions): Promise<{ items: UnifiedPost[]; nextCursor?: string }>;\n}\n\n/**\n * Capability: messaging (Telegram, WhatsApp, Twitter DMs).\n */\nexport interface MessagingProvider {\n sendTextMessage(auth: ProviderAuth, recipient: string, text: string): Promise<{ id: string }>;\n}\n\n/**\n * Capability: media upload (video, photo).\n */\nexport interface UploadingProvider {\n uploadMedia(auth: ProviderAuth, media: UnifiedMedia, opts?: { caption?: string }): Promise<UploadResult>;\n}\n\n/**\n * Capability declaration — paired with `getCapabilities()` on each provider so\n * consumers can introspect what each provider supports.\n */\nexport interface ProviderCapabilities {\n /** Authentication strategy. */\n auth: 'oauth2' | 'token' | 'api_key';\n /** Whether the provider supports text post publishing. */\n posting: boolean;\n /** Whether the provider supports media (photo/video) upload. */\n upload: boolean;\n /** Whether the provider supports messaging. */\n messaging: boolean;\n /** Whether the provider supports scheduled publishing. */\n scheduling: boolean;\n /** Whether the provider supports post deletion. */\n deletion: boolean;\n /** Whether the provider supports listing existing posts. */\n listing: boolean;\n /** Whether the provider supports analytics / insights. */\n analytics: boolean;\n /** Whether the provider supports sandbox/production environments. */\n environments: boolean;\n /** Whether OAuth uses PKCE. */\n pkce: boolean;\n}\n\n// ─── Type guards ────────────────────────────────────────────────────────────\n\nexport function isPostingProvider(p: unknown): p is PostingProvider {\n return !!p && typeof (p as { createPost?: unknown }).createPost === 'function';\n}\n\nexport function isMessagingProvider(p: unknown): p is MessagingProvider {\n return !!p && typeof (p as { sendTextMessage?: unknown }).sendTextMessage === 'function';\n}\n\nexport function isUploadingProvider(p: unknown): p is UploadingProvider {\n return !!p && typeof (p as { uploadMedia?: unknown }).uploadMedia === 'function';\n}\n\n// Re-export for clarity\nexport type { OAuthTokens, UploadResult };\n"],"mappings":";;;;;;;;;;;;;AAiDA,eAAsB,mBACpB,UACA,QACsB;CACtB,MAAM,OAA+B;EACnC,YAAY;EACZ,MAAM,OAAO;EACb,cAAc,OAAO;EACrB,WAAW,OAAO;EAClB,GAAI,OAAO,eAAe,EAAE,eAAe,OAAO,cAAc,GAAG,EAAE;EACrE,GAAI,OAAO,SAAS,EAAE;EACvB;CAED,MAAM,UAAkC,EAAE,GAAI,OAAO,WAAW,EAAE,EAAG;AAErE,KAAI,OAAO,aAAa,OAAO,aAC7B,SAAQ,gBAAgB,SAAS,KAAK,GAAG,OAAO,SAAS,GAAG,OAAO,eAAe;UACzE,OAAO,aAChB,MAAK,gBAAgB,OAAO;CAG9B,MAAM,EAAE,SAAS,MAAM,YAAqC,UAAU;EACpE,QAAQ;EACR,KAAK,OAAO;EACZ,YAAY;EACZ;EACA,aAAa,QAAQ;AACnB,OAAI,OAAO,OAAO,QAAQ,UAAU;IAClC,MAAM,IAAI;AACV,WAAO;KACL,SAAU,EAAE,qBAAiC,EAAE,SAAoB;KACnE,WAAY,EAAE,SAAoB;KACnC;;AAEH,UAAO;;EAEV,CAAC;AAEF,QAAO,gBAAgB,KAAK;;;;;AAM9B,eAAsB,mBACpB,UACA,QACsB;CACtB,MAAM,OAA+B;EACnC,YAAY;EACZ,eAAe,OAAO;EACtB,WAAW,OAAO;EAClB,GAAI,OAAO,SAAS,EAAE;EACvB;CAED,MAAM,UAAkC,EAAE,GAAI,OAAO,WAAW,EAAE,EAAG;AAErE,KAAI,OAAO,aAAa,OAAO,aAC7B,SAAQ,gBAAgB,SAAS,KAAK,GAAG,OAAO,SAAS,GAAG,OAAO,eAAe;UACzE,OAAO,aAChB,MAAK,gBAAgB,OAAO;CAG9B,MAAM,EAAE,SAAS,MAAM,YAAqC,UAAU;EACpE,QAAQ;EACR,KAAK,OAAO;EACZ,YAAY;EACZ;EACA,aAAa,QAAQ;AACnB,OAAI,OAAO,OAAO,QAAQ,UAAU;IAClC,MAAM,IAAI;AACV,WAAO;KACL,SAAU,EAAE,qBAAiC,EAAE,SAAoB;KACnE,WAAY,EAAE,SAAoB;KACnC;;AAEH,UAAO;;EAEV,CAAC;AAEF,QAAO,gBAAgB,KAAK;;AAG9B,SAAS,gBAAgB,KAA2C;AAClE,QAAO;EACL,cAAc,IAAI;EAClB,eAAe,IAAI;EACnB,YAAY,IAAI;EAChB,YAAa,IAAI,cAAqC;EACtD,OAAO,IAAI;EACX,GAAG;EACJ;;;;;AAMH,SAAgB,aAAa,SAAiB,QAcnC;CACT,MAAM,QAAQ,MAAM,QAAQ,OAAO,MAAM,GACrC,OAAO,MAAM,KAAK,OAAO,kBAAkB,IAAI,GAC/C,OAAO;AAaX,QAAO,GAAG,QAAQ,GAZH,IAAI,gBAAgB;EACjC,eAAe,OAAO,gBAAgB;EACtC,WAAW,OAAO;EAClB,cAAc,OAAO;EACrB,OAAO,OAAO;EACd;EACA,GAAI,OAAO,gBAAgB;GACzB,gBAAgB,OAAO;GACvB,uBAAuB,OAAO,uBAAuB;GACtD,GAAG,EAAE;EACN,GAAI,OAAO,SAAS,EAAE;EACvB,CAAC,CAC0B,UAAU;;;;;ACpJxC,SAAgB,SACd,WACA,OAAwB,EAAE,EACJ;CACtB,MAAM,WAAW,KAAK,YAAY;CAClC,MAAM,WAAW,KAAK;CAEtB,gBAAgB,UAA6B;EAC3C,IAAI,SAA6B;EACjC,IAAI,QAAQ;EACZ,IAAI,UAAU;AACd,KAAG;GACD,MAAM,OAAO,MAAM,UAAU,OAAO;AACpC,QAAK,MAAM,QAAQ,KAAK,OAAO;AAC7B,QAAI,aAAa,UAAa,WAAW,SAAU;AACnD,UAAM;AACN;;AAEF,YAAS,KAAK;AACd;WACO,UAAU,QAAQ;;AAmB7B,QAhBuC;GACpC,OAAO,gBAAgB;EACxB,MAAM,UAAU;GACd,MAAM,MAAW,EAAE;AACnB,cAAW,MAAM,QAAQ,SAAS,CAAE,KAAI,KAAK,KAAK;AAClD,UAAO;;EAET,MAAM,KAAK,GAAW;GACpB,MAAM,MAAW,EAAE;AACnB,cAAW,MAAM,QAAQ,SAAS,EAAE;AAClC,QAAI,IAAI,UAAU,EAAG;AACrB,QAAI,KAAK,KAAK;;AAEhB,UAAO;;EAEV;;;;;;;;;;;;;;;;;;;;;AC/CH,SAAgB,oBAAoB,UAAkB,KAAa,UAAU,OAAY;CACvF,IAAI;AACJ,KAAI;AACF,QAAM,IAAI,IAAI,IAAI;SACZ;AACN,QAAM,IAAI,YAAY,UAAU,WAAW,QAAQ,oBAAoB,EAAE,YAAY,KAAK,CAAC;;AAE7F,KAAI,IAAI,aAAa,WAAW,IAAI,aAAa,SAC/C,OAAM,IAAI,YAAY,UAAU,WAAW,QAAQ,kCAAkC,EAAE,YAAY,KAAK,CAAC;AAE3G,KAAI,qBAAqB,IAAI,SAAS,CACpC,OAAM,IAAI,YAAY,UAAU,WAAW,QAAQ,+CAA+C,EAAE,YAAY,KAAK,CAAC;AAExH,QAAO;;AAGT,SAAS,qBAAqB,UAA2B;CACvD,MAAM,OAAO,SAAS,aAAa,CAAC,QAAQ,YAAY,GAAG;AAC3D,KAAI,SAAS,eAAe,KAAK,SAAS,aAAa,CAAE,QAAO;AAEhE,KAAI,SAAS,SAAS,SAAS,kBAAmB,QAAO;AACzD,KAAI,KAAK,WAAW,QAAQ,IAAI,KAAK,WAAW,KAAK,IAAI,KAAK,WAAW,KAAK,CAAE,QAAO;CAEvF,MAAM,IAAI,KAAK,MAAM,+BAA+B;AACpD,KAAI,CAAC,EAAG,QAAO;CACf,MAAM,GAAG,GAAG,KAAK,EAAE,IAAI,OAAO;AAC9B,KAAI,MAAM,IAAK,QAAO;AACtB,KAAI,MAAM,GAAI,QAAO;AACrB,KAAI,MAAM,EAAG,QAAO;AACpB,KAAI,MAAM,OAAO,MAAM,IAAK,QAAO;AACnC,KAAI,MAAM,OAAO,KAAM,MAAM,KAAM,GAAI,QAAO;AAC9C,KAAI,MAAM,OAAO,MAAM,IAAK,QAAO;AACnC,KAAI,KAAK,IAAK,QAAO;AACrB,QAAO;;;;;;AAOT,SAAgB,cAAc,OAAyB;CACrD,MAAM,aAAa;CACnB,SAAS,KAAK,GAAY,OAAwB;AAChD,MAAI,QAAQ,MAAM,MAAM,QAAQ,MAAM,OAAW,QAAO;AACxD,MAAI,MAAM,QAAQ,EAAE,CAAE,QAAO,EAAE,KAAI,SAAQ,KAAK,MAAM,QAAQ,EAAE,CAAC;AACjE,MAAI,OAAO,MAAM,UAAU;GACzB,MAAM,MAA+B,EAAE;AACvC,QAAK,MAAM,CAAC,GAAG,QAAQ,OAAO,QAAQ,EAA6B,CACjE,KAAI,KAAK,WAAW,KAAK,EAAE,GAAG,eAAe,KAAK,KAAK,QAAQ,EAAE;AAEnE,UAAO;;AAET,SAAO;;AAET,QAAO,KAAK,OAAO,EAAE;;;;;AC4DvB,SAAgB,kBAAkB,GAAkC;AAClE,QAAO,CAAC,CAAC,KAAK,OAAQ,EAA+B,eAAe;;AAGtE,SAAgB,oBAAoB,GAAoC;AACtE,QAAO,CAAC,CAAC,KAAK,OAAQ,EAAoC,oBAAoB;;AAGhF,SAAgB,oBAAoB,GAAoC;AACtE,QAAO,CAAC,CAAC,KAAK,OAAQ,EAAgC,gBAAgB"}
|