Skip to content

Commit

Permalink
Support creating InputFile instances from URLs (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
KnorpelSenf authored Nov 8, 2021
1 parent df30972 commit b763434
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 5 deletions.
42 changes: 40 additions & 2 deletions src/platform.deno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ export const streamFile = isDeno
// === Base configuration for `fetch` calls
export const baseFetchConfig = {};

/** Something that looks like a URL. */
interface URLLike {
/**
* Identifier of the resouce. Must be in a format that can be parsed by the
* URL constructor.
*/
url: string;
}

// === InputFile handling and File augmenting
// Accessor for file data in `InputFile` instances
export const inputFileData = Symbol("InputFile data");
Expand All @@ -46,7 +55,8 @@ export const inputFileData = Symbol("InputFile data");
* Reference](https://core.telegram.org/bots/api#inputfile).
*/
export class InputFile {
public readonly [inputFileData]: ConstructorParameters<typeof InputFile>[0];
private consumed = false;
private readonly fileData: ConstructorParameters<typeof InputFile>[0];
/**
* Optional name of the constructed `InputFile` instance.
*
Expand All @@ -64,17 +74,45 @@ export class InputFile {
constructor(
file:
| string
| URL
| URLLike
| Uint8Array
| ReadableStream<Uint8Array>
| AsyncIterable<Uint8Array>,
filename?: string,
) {
this[inputFileData] = file;
this.fileData = file;
if (filename === undefined && typeof file === "string") {
filename = basename(file);
}
this.filename = filename;
}
get [inputFileData]() {
if (this.consumed) {
throw new Error("Cannot reuse InputFile data source!");
}
let data = this.fileData;
if (
typeof data === "object" && ("url" in data || data instanceof URL)
) {
data = fetchFile(data instanceof URL ? data : data.url);
} else if (
typeof data !== "string" && (!(data instanceof Uint8Array))
) {
this.consumed = false;
}
return data;
}
}

async function* fetchFile(url: string | URL): AsyncIterable<Uint8Array> {
const { body } = await fetch(url);
if (body === null) {
throw new Error(
`Download failed, no response body from '${url}'`,
);
}
yield* body;
}

// === Export InputFile types
Expand Down
55 changes: 52 additions & 3 deletions src/platform.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Agent } from "https";
import { basename } from "path";
import { Readable } from "stream";
import type { ReadStream } from "fs";
import { URL } from "url";

// === Export all API types
export * from "@grammyjs/types";
Expand All @@ -24,6 +25,14 @@ export const baseFetchConfig = {
agent: new Agent({ keepAlive: true }),
};

/** Something that looks like a URL. */
interface URLLike {
/**
* Identifier of the resouce. Must be in a format that can be parsed by the
* URL constructor.
*/
url: string;
}
// === InputFile handling and File augmenting
// Accessor for file data in `InputFile` instances
export const inputFileData = Symbol("InputFile data");
Expand All @@ -36,7 +45,8 @@ export const inputFileData = Symbol("InputFile data");
* Reference](https://core.telegram.org/bots/api#inputfile).
*/
export class InputFile {
public readonly [inputFileData]: ConstructorParameters<typeof InputFile>[0];
private consumed = false;
private readonly fileData: ConstructorParameters<typeof InputFile>[0];
/**
* Optional name of the constructed `InputFile` instance.
*
Expand All @@ -52,15 +62,54 @@ export class InputFile {
* @param filename Optional name of the file
*/
constructor(
file: string | Uint8Array | ReadStream | AsyncIterable<Uint8Array>,
file:
| string
| URL
| URLLike
| Uint8Array
| ReadStream
| AsyncIterable<Uint8Array>,
filename?: string,
) {
this[inputFileData] = file;
this.fileData = file;
if (filename === undefined && typeof file === "string") {
filename = basename(file);
}
this.filename = filename;
}
get [inputFileData]() {
if (this.consumed) {
throw new Error("Cannot reuse InputFile data source!");
}
let data = this.fileData;
if (
typeof data === "object" && ("url" in data || data instanceof URL)
) {
data = fetchFile(data instanceof URL ? data : data.url);
} else if (
typeof data !== "string" && (!(data instanceof Uint8Array))
) {
this.consumed = false;
}
return data;
}
}

async function* fetchFile(url: string | URL): AsyncIterable<Uint8Array> {
const { body } = await fetch(url);
if (body === null) {
throw new Error(
`Download failed, no response body from '${url}'`,
);
}
for await (const chunk of body) {
if (typeof chunk === "string") {
throw new Error(
`Could not transfer file, received string data instead of bytes from '${url}'`,
);
}
yield chunk;
}
}

// === Export InputFile types
Expand Down

0 comments on commit b763434

Please sign in to comment.