target-supabase-sdk 0.2.17 → 0.2.19
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/README.md +28 -0
- package/dist/browser.d.ts +2 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +1 -1
- package/dist/chunks/{task-queue-DufG7PlX.js → task-queue-CUa40Vb5.js} +117 -2
- package/dist/chunks/task-queue-CUa40Vb5.js.map +1 -0
- package/dist/node/telegram/api-client.d.ts +12 -0
- package/dist/node/telegram/api-client.d.ts.map +1 -0
- package/dist/node/telegram/chunk-locator.d.ts +10 -0
- package/dist/node/telegram/chunk-locator.d.ts.map +1 -0
- package/dist/node/telegram/chunk.d.ts +4 -0
- package/dist/node/telegram/chunk.d.ts.map +1 -0
- package/dist/node/telegram/index.d.ts +8 -0
- package/dist/node/telegram/index.d.ts.map +1 -0
- package/dist/node/telegram/storage-provider.d.ts +32 -0
- package/dist/node/telegram/storage-provider.d.ts.map +1 -0
- package/dist/node/telegram/types.d.ts +53 -0
- package/dist/node/telegram/types.d.ts.map +1 -0
- package/dist/node.d.ts +4 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +538 -7
- package/dist/node.js.map +1 -1
- package/dist/shared/feed/feed.build.d.ts +33 -0
- package/dist/shared/feed/feed.build.d.ts.map +1 -0
- package/dist/shared/feed/feed.interface.d.ts +35 -0
- package/dist/shared/feed/feed.interface.d.ts.map +1 -0
- package/dist/shared/feed/feed.name.d.ts +8 -0
- package/dist/shared/feed/feed.name.d.ts.map +1 -0
- package/dist/shared/feed/feed.query.d.ts +8 -0
- package/dist/shared/feed/feed.query.d.ts.map +1 -0
- package/dist/shared/feed/index.d.ts +13 -0
- package/dist/shared/feed/index.d.ts.map +1 -0
- package/dist/shared/feed/node/feed.access.d.ts +34 -0
- package/dist/shared/feed/node/feed.access.d.ts.map +1 -0
- package/dist/shared/feed/node/feed.service.d.ts +23 -0
- package/dist/shared/feed/node/feed.service.d.ts.map +1 -0
- package/dist/shared/feed/node/index.d.ts +11 -0
- package/dist/shared/feed/node/index.d.ts.map +1 -0
- package/package.json +8 -1
- package/src/browser.ts +8 -1
- package/src/node/telegram/api-client.ts +146 -0
- package/src/node/telegram/chunk-locator.ts +23 -0
- package/src/node/telegram/chunk.ts +33 -0
- package/src/node/telegram/index.ts +20 -0
- package/src/node/telegram/storage-provider.ts +182 -0
- package/src/node/telegram/types.ts +82 -0
- package/src/node.ts +37 -0
- package/src/shared/feed/README.md +163 -0
- package/src/shared/feed/feed.build.ts +101 -0
- package/src/shared/feed/feed.interface.ts +50 -0
- package/src/shared/feed/feed.name.ts +20 -0
- package/src/shared/feed/feed.query.ts +13 -0
- package/src/shared/feed/index.ts +13 -0
- package/src/shared/feed/node/feed.access.ts +134 -0
- package/src/shared/feed/node/feed.service.ts +183 -0
- package/src/shared/feed/node/index.ts +11 -0
- package/dist/chunks/task-queue-DufG7PlX.js.map +0 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { TargetDraft } from "../../core.interface";
|
|
2
|
+
import type { Link } from "../../link/link.interface";
|
|
3
|
+
import { type FeedSource, type FeedValue } from "./feed.interface";
|
|
4
|
+
export interface BuildFeedLinkDraftInput {
|
|
5
|
+
value: FeedValue;
|
|
6
|
+
/** Dots are normalized to spaces in {@link buildFeedLinkName} before joining with `feedKey`. */
|
|
7
|
+
platformName: string;
|
|
8
|
+
feedKey: string;
|
|
9
|
+
source: FeedSource;
|
|
10
|
+
locator: string;
|
|
11
|
+
/**
|
|
12
|
+
* Written to `original.storageProvider`. Must be empty when `source` is `.public`;
|
|
13
|
+
* required when `source` is `.local` or `.oss`.
|
|
14
|
+
*/
|
|
15
|
+
storageProvider?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
preview?: string;
|
|
18
|
+
/** Locators for related resources of the same feed; stored on `original.twinList`. */
|
|
19
|
+
twinList?: string[];
|
|
20
|
+
tagList?: string[];
|
|
21
|
+
}
|
|
22
|
+
/** Trim entries and drop empties; `undefined` when input omitted. */
|
|
23
|
+
export declare function normalizeFeedTwinList(twinList?: string[]): string[] | undefined;
|
|
24
|
+
/** `original.storageProvider` from optional input and required `source` tier. */
|
|
25
|
+
export declare function resolveFeedOriginalStorageProvider(source: FeedSource, storageProvider?: string): string;
|
|
26
|
+
/** Merge user tags with required `source`; strip any other feed source tags from user input. */
|
|
27
|
+
export declare function buildFeedTagList(source: FeedSource, tagList?: string[]): string[];
|
|
28
|
+
/**
|
|
29
|
+
* Assemble a feed {@link Link} draft (`category=link`, `loaderKey === value`).
|
|
30
|
+
* Does not validate locator reachability or write to Supabase.
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildFeedLinkDraft(input: BuildFeedLinkDraftInput): TargetDraft<Link>;
|
|
33
|
+
//# sourceMappingURL=feed.build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed.build.d.ts","sourceRoot":"","sources":["../../../src/shared/feed/feed.build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAGH,KAAK,UAAU,EACf,KAAK,SAAS,EAGjB,MAAM,kBAAkB,CAAC;AAG1B,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,SAAS,CAAC;IACjB,gGAAgG;IAChG,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,qEAAqE;AACrE,wBAAgB,qBAAqB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,SAAS,CAK/E;AAED,iFAAiF;AACjF,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,UAAU,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAcvG;AAED,gGAAgG;AAChG,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAKjF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,WAAW,CAAC,IAAI,CAAC,CA+BpF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feed Link protocol — Link rows with value/loaderKey in {@link FEED_VALUES}.
|
|
3
|
+
* No separate Target category; category remains `link`.
|
|
4
|
+
*/
|
|
5
|
+
export declare const FEED_VALUES: readonly ["feed.image", "feed.audio", "feed.video", "feed.image-list"];
|
|
6
|
+
/** Discriminator stored in both `Link.value` and `details.loaderKey`. */
|
|
7
|
+
export type FeedValue = (typeof FEED_VALUES)[number];
|
|
8
|
+
/**
|
|
9
|
+
* Feed lifecycle tier — exactly one must appear in `tagList` (via `buildFeedTagList`).
|
|
10
|
+
*
|
|
11
|
+
* Leading `.` marks **system** tags (distinct from user-defined `tagList` entries).
|
|
12
|
+
*/
|
|
13
|
+
export declare const FEED_SOURCE_TAGS: readonly [".public", ".local", ".oss"];
|
|
14
|
+
export type FeedSource = (typeof FEED_SOURCE_TAGS)[number];
|
|
15
|
+
/** `registerFeed` only transitions `.public` → `.local` or `.oss`. */
|
|
16
|
+
export declare const FEED_INGEST_SOURCES: readonly [".local", ".oss"];
|
|
17
|
+
export type FeedIngestSource = (typeof FEED_INGEST_SOURCES)[number];
|
|
18
|
+
export declare function isFeedIngestSource(value: string): value is FeedIngestSource;
|
|
19
|
+
/** `Link.details.original` for feed Links. */
|
|
20
|
+
export interface FeedOriginal {
|
|
21
|
+
/**
|
|
22
|
+
* Backend / host id when {@link FeedSource} is `.local` or `.oss` (e.g. `LOCAL_STORAGE_PROVIDER`).
|
|
23
|
+
* Empty string when source is `.public`.
|
|
24
|
+
*/
|
|
25
|
+
storageProvider: string;
|
|
26
|
+
locator: string;
|
|
27
|
+
/**
|
|
28
|
+
* Locators for sibling resources of the same feed (e.g. other media types).
|
|
29
|
+
* Omitted when not supplied on create.
|
|
30
|
+
*/
|
|
31
|
+
twinList?: string[];
|
|
32
|
+
}
|
|
33
|
+
export declare function isFeedValue(value: string): value is FeedValue;
|
|
34
|
+
export declare function isFeedSource(value: string): value is FeedSource;
|
|
35
|
+
//# sourceMappingURL=feed.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed.interface.d.ts","sourceRoot":"","sources":["../../../src/shared/feed/feed.interface.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,WAAW,wEAAyE,CAAC;AAElG,yEAAyE;AACzE,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,wCAAyC,CAAC;AAEvE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,6BAA8B,CAAC;AAE/D,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,gBAAgB,CAE3E;AAED,8CAA8C;AAC9C,MAAM,WAAW,YAAY;IACzB;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,SAAS,CAE7D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,UAAU,CAE/D"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Normalize `platformName` so `Link.name` stays `{platform}.{feedKey}` with a single separator dot. */
|
|
2
|
+
export declare function sanitizeFeedPlatformName(platformName: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Default dedup `Link.name` for feed Links: `{platformName}.{feedKey}`.
|
|
5
|
+
* Dots in `platformName` are replaced with spaces before joining (see {@link sanitizeFeedPlatformName}).
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildFeedLinkName(platformName: string, feedKey: string): string;
|
|
8
|
+
//# sourceMappingURL=feed.name.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed.name.d.ts","sourceRoot":"","sources":["../../../src/shared/feed/feed.name.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAU/E"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { QueryFilter } from "../../core.interface";
|
|
2
|
+
import type { FeedValue } from "./feed.interface";
|
|
3
|
+
/** Dedup key: `category=link` + `value` + `name` (matches register / find). */
|
|
4
|
+
export declare function feedLinkDedupFilters(input: {
|
|
5
|
+
value: FeedValue;
|
|
6
|
+
name: string;
|
|
7
|
+
}): QueryFilter[];
|
|
8
|
+
//# sourceMappingURL=feed.query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed.query.d.ts","sourceRoot":"","sources":["../../../src/shared/feed/feed.query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,EAAE,CAO7F"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feed Link — browser-safe public barrel only.
|
|
3
|
+
*
|
|
4
|
+
* Node-only code lives in `./node/` and is exported from `src/node.ts`,
|
|
5
|
+
* never from this file.
|
|
6
|
+
*/
|
|
7
|
+
export type { BuildFeedLinkDraftInput } from "./feed.build";
|
|
8
|
+
export { buildFeedLinkDraft } from "./feed.build";
|
|
9
|
+
export type { FeedOriginal, FeedSource, FeedValue } from "./feed.interface";
|
|
10
|
+
export { FEED_VALUES, isFeedValue } from "./feed.interface";
|
|
11
|
+
export { buildFeedLinkName } from "./feed.name";
|
|
12
|
+
export { feedLinkDedupFilters } from "./feed.query";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/feed/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { FeedValue } from "../feed.interface";
|
|
2
|
+
export interface CheckFeedLocalAvailabilityInput {
|
|
3
|
+
value: FeedValue;
|
|
4
|
+
storageProvider: string;
|
|
5
|
+
locator: string;
|
|
6
|
+
/** This host id (e.g. `process.env.LOCAL_STORAGE_PROVIDER`). */
|
|
7
|
+
localStorageProvider: string;
|
|
8
|
+
}
|
|
9
|
+
export interface FeedAvailabilityResult {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
/** Absolute path when the locator was resolved on this host. */
|
|
12
|
+
absolutePath?: string;
|
|
13
|
+
reason?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Local reachability for feed ingest (`.local` source).
|
|
17
|
+
*
|
|
18
|
+
* - Requires `storageProvider === localStorageProvider`.
|
|
19
|
+
* - `locator` must be an absolute path.
|
|
20
|
+
* - `feed.image-list`: directory exists.
|
|
21
|
+
* - Other feed values: non-empty file.
|
|
22
|
+
*
|
|
23
|
+
* **Node-only**
|
|
24
|
+
*/
|
|
25
|
+
export declare function checkFeedLocalAvailability(input: CheckFeedLocalAvailabilityInput): Promise<FeedAvailabilityResult>;
|
|
26
|
+
/**
|
|
27
|
+
* Remote reachability for feed ingest (`.oss` source) via HTTP(S).
|
|
28
|
+
*
|
|
29
|
+
* Tries `HEAD`, then `GET` with `Range: bytes=0-0` when `HEAD` is not allowed.
|
|
30
|
+
*
|
|
31
|
+
* **Node-only**
|
|
32
|
+
*/
|
|
33
|
+
export declare function checkFeedOssAvailability(locator: string): Promise<FeedAvailabilityResult>;
|
|
34
|
+
//# sourceMappingURL=feed.access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed.access.d.ts","sourceRoot":"","sources":["../../../../src/shared/feed/node/feed.access.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,WAAW,+BAA+B;IAC5C,KAAK,EAAE,SAAS,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,oBAAoB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,sBAAsB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAUD;;;;;;;;;GASG;AACH,wBAAsB,0BAA0B,CAC5C,KAAK,EAAE,+BAA+B,GACvC,OAAO,CAAC,sBAAsB,CAAC,CA0CjC;AAoBD;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAsB/F"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type FeedIngestSource } from "../feed.interface";
|
|
2
|
+
export interface RegisterFeedInput {
|
|
3
|
+
/** Existing feed Link row id (must still be `.public` unless already ingested with same payload). */
|
|
4
|
+
id: string;
|
|
5
|
+
source: FeedIngestSource;
|
|
6
|
+
storageProvider: string;
|
|
7
|
+
locator: string;
|
|
8
|
+
/** Required when `source` is `.local` (e.g. `process.env.LOCAL_STORAGE_PROVIDER`). Ignored for `.oss`. */
|
|
9
|
+
localStorageProvider?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface RegisterFeedResult {
|
|
12
|
+
id: string;
|
|
13
|
+
/** `false` when row was already ingested with the same source, storageProvider, and locator. */
|
|
14
|
+
updated: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Ingest a `.public` feed Link: validate locator, then promote `tagList` source to `.local` or `.oss`
|
|
18
|
+
* and update `details.original.storageProvider` + `locator`.
|
|
19
|
+
*
|
|
20
|
+
* **Node-only** — under `shared/feed/node/`; do not import from the browser entry.
|
|
21
|
+
*/
|
|
22
|
+
export declare function registerFeed(input: RegisterFeedInput): Promise<RegisterFeedResult>;
|
|
23
|
+
//# sourceMappingURL=feed.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed.service.d.ts","sourceRoot":"","sources":["../../../../src/shared/feed/node/feed.service.ts"],"names":[],"mappings":"AAKA,OAAO,EACH,KAAK,gBAAgB,EAKxB,MAAM,mBAAmB,CAAC;AAK3B,MAAM,WAAW,iBAAiB;IAC9B,qGAAqG;IACrG,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,gBAAgB,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,0GAA0G;IAC1G,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,kBAAkB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,gGAAgG;IAChG,OAAO,EAAE,OAAO,CAAC;CACpB;AAmDD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA+FxF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-only feed surface — for `src/node.ts` / `target-supabase-sdk/node` only.
|
|
3
|
+
* Never re-export from `../index.ts` or `browser.ts`.
|
|
4
|
+
*/
|
|
5
|
+
export type { FeedIngestSource } from "../feed.interface";
|
|
6
|
+
export { FEED_INGEST_SOURCES, isFeedIngestSource } from "../feed.interface";
|
|
7
|
+
export type { CheckFeedLocalAvailabilityInput, FeedAvailabilityResult } from "./feed.access";
|
|
8
|
+
export { checkFeedLocalAvailability, checkFeedOssAvailability } from "./feed.access";
|
|
9
|
+
export type { RegisterFeedInput, RegisterFeedResult } from "./feed.service";
|
|
10
|
+
export { registerFeed } from "./feed.service";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/shared/feed/node/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,YAAY,EAAE,+BAA+B,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC7F,OAAO,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACrF,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "target-supabase-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.19",
|
|
4
4
|
"description": "Supabase client initializer and data access APIs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "KyLoc20",
|
|
@@ -69,8 +69,14 @@
|
|
|
69
69
|
"@supabase/postgrest-js": "^1.19.0",
|
|
70
70
|
"@supabase/supabase-js": "^2.50.0",
|
|
71
71
|
"lodash-es": "^4.17.21",
|
|
72
|
+
"undici": "^7.10.0",
|
|
72
73
|
"zod": "^3.23.0"
|
|
73
74
|
},
|
|
75
|
+
"peerDependenciesMeta": {
|
|
76
|
+
"undici": {
|
|
77
|
+
"optional": true
|
|
78
|
+
}
|
|
79
|
+
},
|
|
74
80
|
"devDependencies": {
|
|
75
81
|
"@biomejs/biome": "^2.5.3",
|
|
76
82
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
@@ -84,6 +90,7 @@
|
|
|
84
90
|
"tslib": "^2.8.1",
|
|
85
91
|
"tsx": "^4.19.4",
|
|
86
92
|
"typescript": "~5.7.2",
|
|
93
|
+
"undici": "^7.10.0",
|
|
87
94
|
"zod": "^3.24.0"
|
|
88
95
|
},
|
|
89
96
|
"publishConfig": {
|
package/src/browser.ts
CHANGED
|
@@ -189,7 +189,14 @@ export {
|
|
|
189
189
|
unregisterService,
|
|
190
190
|
unregisterServiceAtShutdown,
|
|
191
191
|
} from "./service/index";
|
|
192
|
-
|
|
192
|
+
export type { BuildFeedLinkDraftInput, FeedOriginal, FeedSource, FeedValue } from "./shared/feed";
|
|
193
|
+
export {
|
|
194
|
+
buildFeedLinkDraft,
|
|
195
|
+
buildFeedLinkName,
|
|
196
|
+
FEED_VALUES,
|
|
197
|
+
feedLinkDedupFilters,
|
|
198
|
+
isFeedValue,
|
|
199
|
+
} from "./shared/feed";
|
|
193
200
|
export type { FetchInitFactory, FetchRetryOptions } from "./shared/http/fetch-retry";
|
|
194
201
|
export {
|
|
195
202
|
fetchBinaryWithRetry,
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { type Dispatcher, ProxyAgent } from "undici";
|
|
2
|
+
import {
|
|
3
|
+
createLogger,
|
|
4
|
+
type FetchInitFactory,
|
|
5
|
+
fetchBinaryWithRetry,
|
|
6
|
+
fetchWithRetry,
|
|
7
|
+
isRetryableHttpStatus,
|
|
8
|
+
} from "../../browser";
|
|
9
|
+
import type { ResolvedTelegramStorageOptions, TelegramApiResponse } from "./types";
|
|
10
|
+
|
|
11
|
+
const logger = createLogger({ module: "telegram" });
|
|
12
|
+
|
|
13
|
+
let cachedDispatcher: Dispatcher | undefined;
|
|
14
|
+
let cachedProxyUrl: string | undefined;
|
|
15
|
+
|
|
16
|
+
/** Per-request undici dispatcher when proxyUrl is set (Supabase stays direct). */
|
|
17
|
+
function telegramFetchDispatcher(proxyUrl: string | undefined): Dispatcher | undefined {
|
|
18
|
+
if (proxyUrl == null) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
if (cachedDispatcher != null && cachedProxyUrl === proxyUrl) {
|
|
22
|
+
return cachedDispatcher;
|
|
23
|
+
}
|
|
24
|
+
cachedDispatcher = new ProxyAgent(proxyUrl);
|
|
25
|
+
cachedProxyUrl = proxyUrl;
|
|
26
|
+
return cachedDispatcher;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function telegramNetworkHint(proxyUrl: string | undefined): string {
|
|
30
|
+
return proxyUrl
|
|
31
|
+
? ` (proxy=${proxyUrl}, ensure Clash/V2Ray is running)`
|
|
32
|
+
: " (set proxyUrl / TELEGRAM_PROXY if api.telegram.org is blocked)";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CallTelegramOptions {
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
retryAttempts?: number;
|
|
38
|
+
retryBaseMs?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function botApiUrl(options: ResolvedTelegramStorageOptions, method: string): string {
|
|
42
|
+
return `${options.apiBaseUrl}/bot${options.botToken}/${method}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function telegramFileDownloadUrl(options: ResolvedTelegramStorageOptions, filePath: string): string {
|
|
46
|
+
const normalized = filePath.replace(/^\/+/, "");
|
|
47
|
+
return `${options.apiBaseUrl}/file/bot${options.botToken}/${normalized}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function sleep(ms: number): Promise<void> {
|
|
51
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function telegramBackoff(attempt: number, retryBaseMs: number): Promise<void> {
|
|
55
|
+
return sleep(retryBaseMs * 2 ** (attempt - 1));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Bot API JSON call — transport retry via SDK fetch; outer loop retries Telegram `{ ok: false }`. */
|
|
59
|
+
export async function callTelegramApi<T>(
|
|
60
|
+
options: ResolvedTelegramStorageOptions,
|
|
61
|
+
method: string,
|
|
62
|
+
initOrFactory: RequestInit | FetchInitFactory,
|
|
63
|
+
callOptions: CallTelegramOptions = {},
|
|
64
|
+
): Promise<T> {
|
|
65
|
+
const timeoutMs = callOptions.timeoutMs ?? options.fetchTimeoutMs;
|
|
66
|
+
const maxAttempts = callOptions.retryAttempts ?? 1;
|
|
67
|
+
const retryBaseMs = callOptions.retryBaseMs ?? options.uploadRetryBaseMs;
|
|
68
|
+
const url = botApiUrl(options, method);
|
|
69
|
+
const label = `Telegram ${method}`;
|
|
70
|
+
const dispatcher = telegramFetchDispatcher(options.proxyUrl);
|
|
71
|
+
const hint = telegramNetworkHint(options.proxyUrl);
|
|
72
|
+
|
|
73
|
+
let lastError: Error | null = null;
|
|
74
|
+
|
|
75
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
76
|
+
const response = await fetchWithRetry(url, initOrFactory, {
|
|
77
|
+
label,
|
|
78
|
+
timeoutMs,
|
|
79
|
+
maxAttempts: attempt === 1 ? maxAttempts : 1,
|
|
80
|
+
retryBaseMs,
|
|
81
|
+
dispatcher,
|
|
82
|
+
hint,
|
|
83
|
+
logger,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
let payload: TelegramApiResponse<T>;
|
|
87
|
+
try {
|
|
88
|
+
payload = (await response.json()) as TelegramApiResponse<T>;
|
|
89
|
+
} catch {
|
|
90
|
+
lastError = new Error(`${label}: invalid JSON response (HTTP ${response.status})`);
|
|
91
|
+
if (attempt < maxAttempts && isRetryableHttpStatus(response.status)) {
|
|
92
|
+
await telegramBackoff(attempt, retryBaseMs);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
throw lastError;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!payload.ok) {
|
|
99
|
+
const description = payload.description ?? `HTTP ${response.status}`;
|
|
100
|
+
lastError = new Error(`${label}: ${description}`);
|
|
101
|
+
if (attempt < maxAttempts && isRetryableHttpStatus(response.status)) {
|
|
102
|
+
logger.warn("retrying after API error", {
|
|
103
|
+
topic: "telegram",
|
|
104
|
+
data: { method, attempt, description },
|
|
105
|
+
});
|
|
106
|
+
await telegramBackoff(attempt, retryBaseMs);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
throw lastError;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (payload.result == null) {
|
|
113
|
+
throw new Error(`${label}: empty result in response`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (attempt > 1) {
|
|
117
|
+
logger.info("request succeeded after retry", { topic: "telegram", data: { method, attempt } });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return payload.result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
throw lastError ?? new Error(`${label}: request failed`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function telegramDownloadBinary(
|
|
127
|
+
options: ResolvedTelegramStorageOptions,
|
|
128
|
+
url: string,
|
|
129
|
+
label: string,
|
|
130
|
+
callOptions: CallTelegramOptions = {},
|
|
131
|
+
): Promise<ArrayBuffer> {
|
|
132
|
+
const dispatcher = telegramFetchDispatcher(options.proxyUrl);
|
|
133
|
+
return fetchBinaryWithRetry(
|
|
134
|
+
url,
|
|
135
|
+
{ method: "GET" },
|
|
136
|
+
{
|
|
137
|
+
label: `Telegram ${label}`,
|
|
138
|
+
timeoutMs: callOptions.timeoutMs ?? options.fetchTimeoutMs,
|
|
139
|
+
maxAttempts: callOptions.retryAttempts ?? 1,
|
|
140
|
+
retryBaseMs: callOptions.retryBaseMs ?? options.uploadRetryBaseMs,
|
|
141
|
+
dispatcher,
|
|
142
|
+
hint: telegramNetworkHint(options.proxyUrl),
|
|
143
|
+
logger,
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram chunk URL encoding: `${messageId}|${fileId}`.
|
|
3
|
+
* Legacy parcels store bare `file_id` (no messageId → cannot deleteMessage).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const SEPARATOR = "|";
|
|
7
|
+
|
|
8
|
+
export function encodeTelegramChunkUrl(messageId: number, fileId: string): string {
|
|
9
|
+
return `${messageId}${SEPARATOR}${fileId}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function parseTelegramChunkUrl(locator: string): { messageId: number | null; fileId: string } {
|
|
13
|
+
const idx = locator.indexOf(SEPARATOR);
|
|
14
|
+
if (idx <= 0) {
|
|
15
|
+
return { messageId: null, fileId: locator };
|
|
16
|
+
}
|
|
17
|
+
const mid = Number(locator.slice(0, idx));
|
|
18
|
+
const fileId = locator.slice(idx + 1);
|
|
19
|
+
if (!Number.isInteger(mid) || mid <= 0 || fileId === "") {
|
|
20
|
+
return { messageId: null, fileId: locator };
|
|
21
|
+
}
|
|
22
|
+
return { messageId: mid, fileId };
|
|
23
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { callTelegramApi, telegramDownloadBinary, telegramFileDownloadUrl } from "./api-client";
|
|
2
|
+
import type { ResolvedTelegramStorageOptions, TelegramFile } from "./types";
|
|
3
|
+
|
|
4
|
+
/** Download chunk bytes by Telegram `file_id` stored in Chunk.url. */
|
|
5
|
+
export async function downloadTelegramChunk(
|
|
6
|
+
options: ResolvedTelegramStorageOptions,
|
|
7
|
+
fileId: string,
|
|
8
|
+
): Promise<ArrayBuffer> {
|
|
9
|
+
const file = await callTelegramApi<TelegramFile>(
|
|
10
|
+
options,
|
|
11
|
+
`getFile?file_id=${encodeURIComponent(fileId)}`,
|
|
12
|
+
{ method: "GET" },
|
|
13
|
+
{
|
|
14
|
+
retryAttempts: options.uploadRetryAttempts,
|
|
15
|
+
retryBaseMs: options.uploadRetryBaseMs,
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
if (file.file_path == null || file.file_path === "") {
|
|
20
|
+
throw new Error(`Telegram getFile: missing file_path for file_id ${fileId}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return telegramDownloadBinary(
|
|
24
|
+
options,
|
|
25
|
+
telegramFileDownloadUrl(options, file.file_path),
|
|
26
|
+
`download:${fileId.slice(0, 12)}…`,
|
|
27
|
+
{
|
|
28
|
+
timeoutMs: options.uploadTimeoutMs,
|
|
29
|
+
retryAttempts: options.uploadRetryAttempts,
|
|
30
|
+
retryBaseMs: options.uploadRetryBaseMs,
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type { CallTelegramOptions } from "./api-client";
|
|
2
|
+
export { callTelegramApi, telegramDownloadBinary, telegramFileDownloadUrl } from "./api-client";
|
|
3
|
+
export { downloadTelegramChunk } from "./chunk";
|
|
4
|
+
export { encodeTelegramChunkUrl, parseTelegramChunkUrl } from "./chunk-locator";
|
|
5
|
+
export {
|
|
6
|
+
createTelegramStorageProvider,
|
|
7
|
+
PROVIDER_TELEGRAM,
|
|
8
|
+
type TelegramChunkDeleteResult,
|
|
9
|
+
type TelegramStorageProvider,
|
|
10
|
+
} from "./storage-provider";
|
|
11
|
+
export type {
|
|
12
|
+
ResolvedTelegramStorageOptions,
|
|
13
|
+
TelegramApiResponse,
|
|
14
|
+
TelegramDocument,
|
|
15
|
+
TelegramFile,
|
|
16
|
+
TelegramMessage,
|
|
17
|
+
TelegramStorageProviderOptions,
|
|
18
|
+
TelegramUploadResult,
|
|
19
|
+
} from "./types";
|
|
20
|
+
export { resolveTelegramStorageOptions } from "./types";
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type { CreateUploadAdapterOptions, Parcel, StorageProviderModule, UploadTracker } from "../../browser";
|
|
2
|
+
import { isOpaqueChunkUrl } from "../../browser";
|
|
3
|
+
import { callTelegramApi } from "./api-client";
|
|
4
|
+
import { downloadTelegramChunk } from "./chunk";
|
|
5
|
+
import { encodeTelegramChunkUrl, parseTelegramChunkUrl } from "./chunk-locator";
|
|
6
|
+
import {
|
|
7
|
+
type ResolvedTelegramStorageOptions,
|
|
8
|
+
resolveTelegramStorageOptions,
|
|
9
|
+
type TelegramMessage,
|
|
10
|
+
type TelegramStorageProviderOptions,
|
|
11
|
+
type TelegramUploadResult,
|
|
12
|
+
} from "./types";
|
|
13
|
+
|
|
14
|
+
export const PROVIDER_TELEGRAM = "telegram";
|
|
15
|
+
|
|
16
|
+
export interface TelegramChunkDeleteResult {
|
|
17
|
+
deleted: number;
|
|
18
|
+
failed: number;
|
|
19
|
+
skipped: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface TelegramStorageProvider extends StorageProviderModule {
|
|
23
|
+
readonly provider: typeof PROVIDER_TELEGRAM;
|
|
24
|
+
/** Low-level upload for probes/CLIs. */
|
|
25
|
+
uploadChunk(data: ArrayBuffer, filename: string): Promise<TelegramUploadResult>;
|
|
26
|
+
/** Best-effort deleteMessage for rollback / probe cleanup. */
|
|
27
|
+
deleteMessage(messageId: number): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Best-effort delete Telegram messages for parcel chunks.
|
|
30
|
+
* Chunks without an encoded messageId (legacy bare file_id) are skipped.
|
|
31
|
+
*/
|
|
32
|
+
deleteChunkBlobs(parcel: Parcel): Promise<TelegramChunkDeleteResult>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function uploadChunkToTelegram(
|
|
36
|
+
options: ResolvedTelegramStorageOptions,
|
|
37
|
+
data: ArrayBuffer,
|
|
38
|
+
filename: string,
|
|
39
|
+
): Promise<TelegramUploadResult> {
|
|
40
|
+
const sizeMb = (data.byteLength / (1024 * 1024)).toFixed(2);
|
|
41
|
+
|
|
42
|
+
const message = await callTelegramApi<TelegramMessage>(
|
|
43
|
+
options,
|
|
44
|
+
"sendDocument",
|
|
45
|
+
() => {
|
|
46
|
+
const form = new FormData();
|
|
47
|
+
form.append("chat_id", options.chatId);
|
|
48
|
+
form.append("document", new Blob([data]), filename);
|
|
49
|
+
return { method: "POST", body: form };
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
timeoutMs: options.uploadTimeoutMs,
|
|
53
|
+
retryAttempts: options.uploadRetryAttempts,
|
|
54
|
+
retryBaseMs: options.uploadRetryBaseMs,
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const fileId = message.document?.file_id;
|
|
59
|
+
if (fileId == null || fileId === "") {
|
|
60
|
+
throw new Error(`Telegram sendDocument: missing file_id (chunk ${filename}, ${sizeMb} MB)`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { fileId, messageId: message.message_id };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function deleteTelegramMessage(options: ResolvedTelegramStorageOptions, messageId: number): Promise<void> {
|
|
67
|
+
await callTelegramApi<boolean>(
|
|
68
|
+
options,
|
|
69
|
+
"deleteMessage",
|
|
70
|
+
() => {
|
|
71
|
+
const form = new FormData();
|
|
72
|
+
form.append("chat_id", options.chatId);
|
|
73
|
+
form.append("message_id", String(messageId));
|
|
74
|
+
return { method: "POST", body: form };
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
retryAttempts: 3,
|
|
78
|
+
retryBaseMs: 1_000,
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function rollbackTelegramUploads(options: ResolvedTelegramStorageOptions, handles: unknown[]): Promise<void> {
|
|
84
|
+
for (const item of handles) {
|
|
85
|
+
const upload = item as TelegramUploadResult;
|
|
86
|
+
if (upload?.messageId == null) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
await deleteTelegramMessage(options, upload.messageId);
|
|
91
|
+
} catch {
|
|
92
|
+
// Best-effort cleanup
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function assertTelegramParcel(parcel: Parcel): void {
|
|
98
|
+
const chunks = parcel.details.chunkList;
|
|
99
|
+
if (chunks.length === 0) {
|
|
100
|
+
throw new Error("Parcel has no chunks");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const nonTelegram = chunks.filter((chunk) => chunk.provider != null && chunk.provider !== PROVIDER_TELEGRAM);
|
|
104
|
+
if (nonTelegram.length > 0) {
|
|
105
|
+
const providers = [...new Set(nonTelegram.map((chunk) => chunk.provider))];
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Parcel is not telegram-only (found providers: ${providers.join(", ")}). ` +
|
|
108
|
+
"Multi-provider restore is not yet supported for this parcel.",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Build a Telegram {@link StorageProviderModule}. Caller registers it — no side effects.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* import { createStorageProviderRegistry, createTelegramStorageProvider } from "target-supabase-sdk/node";
|
|
119
|
+
* const registry = createStorageProviderRegistry();
|
|
120
|
+
* registry.register(createTelegramStorageProvider({ botToken, chatId, proxyUrl }));
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export function createTelegramStorageProvider(input: TelegramStorageProviderOptions): TelegramStorageProvider {
|
|
124
|
+
const options = resolveTelegramStorageOptions(input);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
provider: PROVIDER_TELEGRAM,
|
|
128
|
+
uploadChunk: (data, filename) => uploadChunkToTelegram(options, data, filename),
|
|
129
|
+
deleteMessage: (messageId) => deleteTelegramMessage(options, messageId),
|
|
130
|
+
async deleteChunkBlobs(parcel) {
|
|
131
|
+
let deleted = 0;
|
|
132
|
+
let failed = 0;
|
|
133
|
+
let skipped = 0;
|
|
134
|
+
for (const chunk of parcel.details.chunkList) {
|
|
135
|
+
if (chunk.provider != null && chunk.provider !== PROVIDER_TELEGRAM) {
|
|
136
|
+
skipped += 1;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const { messageId } = parseTelegramChunkUrl(chunk.url);
|
|
140
|
+
if (messageId == null) {
|
|
141
|
+
skipped += 1;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
await deleteTelegramMessage(options, messageId);
|
|
146
|
+
deleted += 1;
|
|
147
|
+
} catch {
|
|
148
|
+
failed += 1;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return { deleted, failed, skipped };
|
|
152
|
+
},
|
|
153
|
+
createUploadAdapter(adapterOptions?: CreateUploadAdapterOptions) {
|
|
154
|
+
const tracker = adapterOptions?.tracker as UploadTracker<TelegramUploadResult> | undefined;
|
|
155
|
+
return {
|
|
156
|
+
provider: PROVIDER_TELEGRAM,
|
|
157
|
+
async upload(data, pathOrKey) {
|
|
158
|
+
const result = await uploadChunkToTelegram(options, data, pathOrKey);
|
|
159
|
+
tracker?.push(result);
|
|
160
|
+
// Persist messageId so later deleteParcel can call deleteMessage.
|
|
161
|
+
return { url: encodeTelegramChunkUrl(result.messageId, result.fileId) };
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
resolveChunk: (locator) => {
|
|
166
|
+
const { fileId } = parseTelegramChunkUrl(locator);
|
|
167
|
+
return downloadTelegramChunk(options, fileId);
|
|
168
|
+
},
|
|
169
|
+
matchesOpaqueUrl: isOpaqueChunkUrl,
|
|
170
|
+
async probe() {
|
|
171
|
+
try {
|
|
172
|
+
const me = await callTelegramApi<{ username?: string }>(options, "getMe", { method: "GET" });
|
|
173
|
+
return { ok: true as const, detail: { username: me.username ?? "unknown" } };
|
|
174
|
+
} catch (error) {
|
|
175
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
176
|
+
return { ok: false as const, error: message };
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
rollbackUpload: (handles) => rollbackTelegramUploads(options, handles),
|
|
180
|
+
assertParcelRestorable: assertTelegramParcel,
|
|
181
|
+
};
|
|
182
|
+
}
|