Skip to content

Instantly share code, notes, and snippets.

@wojpawlik
Last active March 28, 2023 10:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wojpawlik/2b1b226d52d675ec246c6f8abdab81ef to your computer and use it in GitHub Desktop.
Save wojpawlik/2b1b226d52d675ec246c6f8abdab81ef to your computer and use it in GitHub Desktop.
import type { Client, Opts, Ret } from "./deps.ts";
export type Api = {
[M in keyof Opts]: (
payload: Opts[M],
signal?: AbortSignal,
) => Promise<Ret[M]>;
};
export const createApi = (client: Client) =>
new Proxy({}, {
get:
<M extends keyof Opts>(_: unknown, method: M) =>
async (payload: Opts[M], signal?: AbortSignal) => {
const result = await client.call({ method, payload, signal });
if (!result.ok) throw result;
return result.result;
},
}) as Api;
import { createApi } from "./api.ts";
import type { Middleware } from "./compose.ts";
import type { Context } from "./context.ts";
import type { Client, Update } from "./deps.ts";
export async function noop() {}
export async function createBot(
client: Client,
middleware: Middleware<Context>,
) {
const api = createApi(client);
const handler = middleware(noop);
const botInfo = await api.getMe({});
const handleUpdate = (update: Update) => handler({ update, api, botInfo });
return { api, botInfo, client, handleUpdate }
}
// deno-lint-ignore-file ban-types
import type { Update } from "https://esm.sh/typegram@^3.12";
import type { Context } from "./context.ts";
type Handler<C extends Context> = (ctx: C) => Promise<void>;
type EndoFunction<T> = (t: T) => T;
export type Extension<C extends Context, E extends object> = (ctx: C) => E;
export type Filter<U extends Update> = (update: Update) => update is U;
export type Middleware<C extends Context> = EndoFunction<Handler<C>>;
export function compose<T>(...fns: EndoFunction<T>[]): EndoFunction<T> {
return (next: T) => fns.reduceRight((t, fn) => fn(t), next);
}
export function on<C extends Context, U extends Update>(
filter: Filter<U>,
middleware: Middleware<C & { update: U }>,
): Middleware<C> {
return (next) => {
const handler = middleware(next);
return (ctx) =>
filter(ctx.update) ? handler(ctx as C & { update: U }) : next(ctx);
};
}
export function using<C extends Context, E extends object>(
extension: Extension<C, E>,
middleware: Middleware<C & E>,
): Middleware<C> {
return (next) => {
const handler = middleware(next);
return (ctx) => handler(Object.assign(Object.create(ctx), extension(ctx)));
};
}
import type { Api } from "./api.ts";
import type { Update, UserFromGetMe } from "./deps.ts";
import type { Deunionize } from "./deunionize.ts";
export interface Context {
readonly update: Deunionize<Update>;
readonly api: Api;
readonly botInfo: UserFromGetMe;
}
export * from "https://esm.sh/@telegraf/client@^0.6.1";
// deno-lint-ignore-file ban-types
export type UnionKeys<T> = T extends unknown ? keyof T : never;
type AddOptionalKeys<K extends PropertyKey> = { readonly [P in K]?: never };
/** @see https://millsp.github.io/ts-toolbelt/modules/union_strict.html */
export type Deunionize<
B extends object | undefined,
T extends B = B,
> = T extends object ? T & AddOptionalKeys<Exclude<UnionKeys<B>, keyof T>> : T;
// deno-lint-ignore-file ban-types
import type {
CommonMessageBundle,
Message,
Update,
} from "https://esm.sh/typegram@^3.12";
import type { Deunionize, UnionKeys } from "./deunionize.ts";
type DistinctKeys<T extends object> = Exclude<UnionKeys<T>, keyof T>;
type Keyed<T extends object, K extends DistinctKeys<T>> =
& Record<K, {}>
& Deunionize<Record<K, {}>, T>;
export const message =
<K extends DistinctKeys<Message>, Ks extends K[]>(...keys: Ks) =>
(
update: Update,
): update is Update.MessageUpdate<Keyed<Message, Ks[number]>> => {
if (!("message" in update)) return false;
for (const key of keys) {
if (!(key in update.message)) return false;
}
return true;
};
export const editedMessage =
<K extends DistinctKeys<CommonMessageBundle>, Ks extends K[]>(...keys: Ks) =>
(
update: Update,
): update is Update.EditedMessageUpdate<
Keyed<CommonMessageBundle, Ks[number]>
> => {
if (!("edited_message" in update)) return false;
for (const key of keys) {
if (!(key in update.edited_message)) return false;
}
return true;
};
export const channelPost =
<K extends DistinctKeys<Message>, Ks extends K[]>(...keys: Ks) =>
(
update: Update,
): update is Update.ChannelPostUpdate<Keyed<Message, Ks[number]>> => {
if (!("channel_post" in update)) return false;
for (const key of keys) {
if (!(key in update.channel_post)) return false;
}
return true;
};
export const editedChannelPost =
<K extends DistinctKeys<CommonMessageBundle>, Ks extends K[]>(...keys: Ks) =>
(
update: Update,
): update is Update.EditedChannelPostUpdate<
Keyed<CommonMessageBundle, Ks[number]>
> => {
if (!("edited_channel_post" in update)) return false;
for (const key of keys) {
if (!(key in update.edited_channel_post)) return false;
}
return true;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment