Skip to content

Commit

Permalink
refactor: InputFile; closes #704
Browse files Browse the repository at this point in the history
  • Loading branch information
wojpawlik committed Dec 18, 2024
1 parent 92ec9ff commit ff4ebcb
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 200 deletions.
65 changes: 39 additions & 26 deletions src/types.deno.ts → src/core/input_file.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// === Needed imports
import { basename } from "https://deno.land/[email protected]/path/basename.ts";

// TODO(@wojpawlik) remove after #720
import {
type ApiMethods as ApiMethodsF,
type InputMedia as InputMediaF,
Expand All @@ -19,6 +20,7 @@ import {
export const isDeno = typeof Deno !== "undefined";

// === Export all API types
// TODO(@wojpawlik) remove after #720
export * from "https://raw.githubusercontent.com/grammyjs/types/refs/heads/v2/mod.ts";

/** A value, or a potentially async function supplying that value */
Expand Down Expand Up @@ -66,7 +68,6 @@ export class InputFile {
| URL
| URLLike
| Uint8Array
| ReadableStream<Uint8Array>
| Iterable<Uint8Array>
| AsyncIterable<Uint8Array>
>,
Expand All @@ -82,62 +83,73 @@ export class InputFile {
if ("path" in file && typeof file.path === "string") {
return basename(file.path);
}
if ("url" in file) return basename(file.url);
if ("url" in file && file.url) file = new URL(file.url);
if (file instanceof File) return file.name;
if (!(file instanceof URL)) return undefined;
if (file.pathname !== "/") {
const filename = basename(file.pathname);
if (filename) return filename;
}
return basename(file.hostname);
}

async *[Symbol.asyncIterator](): AsyncIterable<Uint8Array> {
yield* await this.toRaw();
}

/**
* Internal method. Do not use.
* @internal use `[Symbol.asyncIterator]()` instead.
*
* Converts this instance into a binary representation that can be sent to
* the Bot API server in the request body.
*/
async toRaw(): Promise<
Uint8Array | Iterable<Uint8Array> | AsyncIterable<Uint8Array>
Iterable<Uint8Array> | AsyncIterable<Uint8Array>
> {
if (this.consumed) {
throw new Error("Cannot reuse InputFile data source!");
}
const data = this.fileData;
// Handle local files
if ("path" in data) {
if (!isDeno) {
throw new Error(
"Reading files by path requires a Deno environment",
);
}
const file = await Deno.open(data.path);
return file.readable[Symbol.asyncIterator]();
if (data instanceof Uint8Array) return [data];
// Mark streams and iterators as consumed and return them as-is
if (Symbol.asyncIterator in data || Symbol.iterator in data) {
this.consumed = true;
return data;
}
// Handle local files
if ("path" in data) return await readFile(data.path);
if (data instanceof Blob) return data.stream();
if (isDenoFile(data)) return data.readable[Symbol.asyncIterator]();
if (isDenoFile(data)) {
this.consumed = true;
return data.readable;
}
// Handle Response objects
if (data instanceof Response) {
if (data.body === null) throw new Error(`No response body!`);
this.consumed = true;
return data.body;
}
// Handle URL and URLLike objects
if (data instanceof URL) return await fetchFile(data);
if ("url" in data) return await fetchFile(data.url);
// Return buffers as-is
if (data instanceof Uint8Array) return data;
if ("url" in data) return await fetchFile(new URL(data.url));
// Unwrap supplier functions
if (typeof data === "function") {
return new InputFile(await data()).toRaw();
}
// Mark streams and iterators as consumed and return them as-is
this.consumed = true;
return data;
return new InputFile(await data()).toRaw();
}
}

async function readFile(path: string): Promise<AsyncIterable<Uint8Array>> {
if (isDeno) {
const file = await Deno.open(path);
return file.readable;
}
const fs = await import("node:fs");
return fs.createReadStream(path);
}

async function fetchFile(
url: string | URL,
): Promise<AsyncIterable<Uint8Array>> {
async function fetchFile(url: URL): Promise<AsyncIterable<Uint8Array>> {
// https://github.com/nodejs/undici/issues/2751
if (url.protocol === "file") return await readFile(url.pathname);

const { body } = await fetch(url);
if (body === null) {
throw new Error(`Download failed, no response body from '${url}'`);
Expand All @@ -149,6 +161,7 @@ function isDenoFile(data: unknown): data is Deno.FsFile {
}

// === Export InputFile types
// TODO(@wojpawlik) remove after #720
/** Wrapper type to bundle all methods of the Telegram API */
export type ApiMethods = ApiMethodsF<InputFile>;

Expand Down
171 changes: 0 additions & 171 deletions src/types.node.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from "./types.deno.ts";
export * from "./core/input_file.ts";
4 changes: 2 additions & 2 deletions test/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ Deno.test({
const bytes = new Uint8Array([65, 66, 67]);
const file = new InputFile(bytes);
const data = await file.toRaw();
assertInstanceOf(data, Uint8Array);
assertEquals(data, bytes);
assertInstanceOf(data, Array);
assertEquals(data, [bytes]);
},
});

Expand Down

0 comments on commit ff4ebcb

Please sign in to comment.