diff --git a/.github/workflows/compute.yml b/.github/workflows/compute.yml index 3d204b1..85e12f3 100644 --- a/.github/workflows/compute.yml +++ b/.github/workflows/compute.yml @@ -1,4 +1,4 @@ -name: "the name of the plugin" +name: "Workflow Functions" on: workflow_dispatch: @@ -18,12 +18,24 @@ on: jobs: compute: - name: "plugin name" + name: "Telegram-Bot" runs-on: ubuntu-latest permissions: write-all env: SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} + + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} + BOT_MODE: ${{ secrets.BOT_MODE }} + BOT_ADMINS: ${{ secrets.BOT_ADMINS }} + BOT_WEBHOOK: ${{ secrets.BOT_WEBHOOK }} + BOT_WEBHOOK_SECRET: ${{ secrets.BOT_WEBHOOK_SECRET }} + + TELEGRAM_APP_ID: ${{ secrets.TELEGRAM_APP_ID }} + TELEGRAM_API_HASH: ${{ secrets.TELEGRAM_API_HASH }} + + LOG_LEVEL: ${{ secrets.LOG_LEVEL }} + DEBUG: ${{ secrets.DEBUG }} steps: - uses: actions/checkout@v4 @@ -37,8 +49,20 @@ jobs: run: yarn - name: execute directive - run: npx tsx ./src/main.ts - id: plugin-name + run: npx tsx ./src/workflow-entry.ts + id: telegram-bot env: SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} + + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} + BOT_MODE: ${{ secrets.BOT_MODE }} + BOT_ADMINS: ${{ secrets.BOT_ADMINS }} + BOT_WEBHOOK: ${{ secrets.BOT_WEBHOOK }} + BOT_WEBHOOK_SECRET: ${{ secrets.BOT_WEBHOOK_SECRET }} + + TELEGRAM_APP_ID: ${{ secrets.TELEGRAM_APP_ID }} + TELEGRAM_API_HASH: ${{ secrets.TELEGRAM_API_HASH }} + + LOG_LEVEL: ${{ secrets.LOG_LEVEL }} + DEBUG: ${{ secrets.DEBUG }} \ No newline at end of file diff --git a/package.json b/package.json index 347beff..eeb2289 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,7 @@ "worker": "wrangler dev --env dev --port 3000", "dev": "tsc-watch --onSuccess \"tsx ./src/main.ts\"", "start": "tsx ./src/main.ts", - "deploy": "wrangler deploy --minify src/main.ts", - "build": "npx tsx build/esbuild-build.ts", - "serve": "npx tsx build/esbuild-server.ts" + "deploy": "wrangler deploy --minify src/main.ts" }, "keywords": [ "typescript", @@ -55,6 +53,7 @@ "grammy-guard": "0.5.0", "hono": "^4.5.9", "iso-639-1": "3.1.2", + "octokit": "^4.0.2", "pino": "9.3.2", "pino-pretty": "11.2.2", "telegram": "^2.24.11", diff --git a/src/handlers/callbacks-proxy.ts b/src/handlers/callbacks-proxy.ts index 055eb65..8138c0b 100644 --- a/src/handlers/callbacks-proxy.ts +++ b/src/handlers/callbacks-proxy.ts @@ -1,30 +1,8 @@ -import { Context, SupportedEvents, SupportedEventsU } from "../types"; +import { ProxyCallbacks } from "#root/types/proxy.js"; +import { Context, SupportedEventsU } from "../types"; +import { createChat } from "../workflow-functions/create-chat"; import { closeWorkroom, createWorkroom, reOpenWorkroom } from "./github/workrooms"; -export type CallbackResult = { status: 200 | 201 | 204 | 404 | 500, reason: string; content?: string | Record }; - -/** - * The `Context` type is a generic type defined as `Context`, - * where `TEvent` is a string representing the event name (e.g., "issues.labeled") - * and `TPayload` is the webhook payload type for that event, derived from - * the `SupportedEvents` type map. - * - * The `ProxyCallbacks` type is defined using `Partial` to allow - * optional callbacks for each event type. This is useful because not all events - * may have associated callbacks. - * - * The expected function signature for callbacks looks like this: - * - * ```typescript - * fn(context: Context<"issues.labeled", SupportedEvents["issues.labeled"]>): Promise - * ``` - */ - -type ProxyCallbacks = ProxyTypeHelper; -type ProxyTypeHelper = { - [K in SupportedEventsU]: Array<(context: Context) => Promise>; -}; - /** * Why do we need this wrapper function? * @@ -39,7 +17,6 @@ type ProxyTypeHelper = { function handleCallback(callback: Function, context: Context) { return callback(context); } - /** * The `callbacks` object defines an array of callback functions for each supported event type. * @@ -93,3 +70,43 @@ export function proxyCallbacks(context: Context): ProxyCallbacks { }, }); } + +/** + * These are function which get dispatched by this worker to fire off workflows + * in the repository. We enter through the main `compute.yml` just like a typical + * action plugin would, we forward the same payload that the worker received to + * the workflow the same way that the kernel does. + * + * - First event fires, `issues.labeled` and the worker catches it. + * - The worker then dispatches a workflow to `compute.yml` with the event name as the input. + * - The workflow receives a `issues.labeled` payload but eventName is now WorkflowFunction (`create-telegram-chat`). + * - The workflow then runs the `createChat` function which needs a node env to run. + * + * I.e we're essentially running the first dual action/worker plugin which is + * ideal for telegram-bot as it's a bot that needs to be able to be super flexible. + */ +const workflowCallbacks = { + "issues.labeled": [ + createChat + ] +} as ProxyCallbacks; + + +export function proxyWorkflowCallbacks(context: Context): ProxyCallbacks { + return new Proxy(workflowCallbacks, { + get(target, prop: SupportedEventsU) { + if (!target[prop]) { + context.logger.info(`No callbacks found for event ${prop}`); + return { status: 204, reason: "skipped" }; + } + return (async () => { + try { + return await Promise.all(target[prop].map((callback) => handleCallback(callback, context))); + } catch (er) { + context.logger.error(`Failed to handle event ${prop}`, { er }); + return { status: 500, reason: "failed" }; + } + })(); + }, + }); +} \ No newline at end of file diff --git a/src/handlers/github/workrooms.ts b/src/handlers/github/workrooms.ts index d934e6c..e5a9833 100644 --- a/src/handlers/github/workrooms.ts +++ b/src/handlers/github/workrooms.ts @@ -1,7 +1,8 @@ import { Chat } from "#root/adapters/supabase/helpers/chats.js"; +import { CallbackResult } from "#root/types/proxy.js"; import { TelegramBotSingleton } from "#root/utils/telegram-bot-single.js"; import { Context, SupportedEvents } from "../../types"; -import { CallbackResult } from "../callbacks-proxy"; +import { repositoryDispatch } from "../repository-dispatch"; import { addCommentToIssue } from "./utils/add-comment-to-issues"; /** @@ -20,32 +21,8 @@ import { addCommentToIssue } from "./utils/add-comment-to-issues"; */ export async function createWorkroom(context: Context<"issues.labeled", SupportedEvents["issues.labeled"]>): Promise { - const { logger, config, adapters: { supabase: { chats } } } = context; - const bot = TelegramBotSingleton.getInstance().getBot(); - const title = context.payload.issue.title - const { issue, repository } = context.payload; - const { full_name } = repository; - const [owner, repo] = full_name.split("/"); - - const workroom = await chats.getChatByTaskNodeId(issue.node_id); - - if (workroom) { - logger.debug("Workroom already exists for issue", { title }); - return { status: 404, reason: "workroom_already_exists" }; - } - - logger.info(`Creating workroom for issue ${title}`); - - try { - const forum = await bot.api?.createForumTopic(config.supergroupChatId, title); - await addCommentToIssue(context, `Workroom created: https://t.me/${config.supergroupChatName}/${forum?.message_thread_id}`, owner, repo, issue.number); - await chats.saveChat(forum?.message_thread_id, title, issue.node_id); - return { status: 201, reason: "workroom_created" }; - } catch (er) { - await addCommentToIssue(context, logger.error(`Failed to create workroom for issue ${title}`, { er }).logMessage.diff, owner, repo, issue.number); - return { status: 500, reason: "workroom_creation_failed" }; - } - + await repositoryDispatch(context, "create-telegram-chat").catch(console.error); + return { status: 200, reason: "workflow_dispatched" }; } export async function closeWorkroom(context: Context<"issues.closed", SupportedEvents["issues.closed"]>): Promise { diff --git a/src/handlers/repository-dispatch.ts b/src/handlers/repository-dispatch.ts new file mode 100644 index 0000000..f9d7e3e --- /dev/null +++ b/src/handlers/repository-dispatch.ts @@ -0,0 +1,52 @@ +import { getAppOctokit } from "#root/helpers/authenticated-octokit.js"; +import { PluginContext } from "#root/utils/plugin-context-single.js"; +import { Context } from "../types"; + +/** + * Used by the worker instance to kick off workflows within it's own repository. + * + * These workflows are extensions of the worker allowing for more complex operations + * to be performed outside of Cloudflare Workers' limitations. + * + * @param env The environment variables for the worker instance. These + * will be taken from the repository's secrets. + * @param args The arguments passed to the workflow. + * + */ + +export async function repositoryDispatch(context: Context, workflow: string) { + const inputs = PluginContext.getInstance().getInputs(); + const { logger } = context; + const repository = "telegram--bot"; + const owner = "ubq-testing"; + const branch = "workflows"; + const app = await getAppOctokit(context); + const installation = await app.octokit.rest.apps.getRepoInstallation({ owner, repo: repository }); + + // Set the installation id for the octokit instance + + const octokit = await app.getInstallationOctokit(installation.data.id); + + logger.info(`Dispatching workflow function: ${workflow}`); + + + /** + * We'll hit the main workflow entry and pass in the same inputs so + * that it essentially runs on the same context as the worker. + */ + + Reflect.deleteProperty(inputs, "signature"); + + return await octokit.rest.actions.createWorkflowDispatch({ + owner, + repo: repository, + workflow_id: "compute.yml", + ref: branch, + inputs: { + ...inputs, + eventPayload: JSON.stringify(context.payload), + settings: JSON.stringify(context.config), + } + }); +} + diff --git a/src/helpers/authenticated-octokit.ts b/src/helpers/authenticated-octokit.ts new file mode 100644 index 0000000..7df5d36 --- /dev/null +++ b/src/helpers/authenticated-octokit.ts @@ -0,0 +1,7 @@ +import { App } from "octokit"; +import { Context } from "../types"; + +export async function getAppOctokit(context: Context) { + const { env: { APP_ID, APP_PRIVATE_KEY } } = context; + return new App({ appId: APP_ID, privateKey: APP_PRIVATE_KEY }); +} \ No newline at end of file diff --git a/src/plugin.ts b/src/plugin.ts index 7f84716..e858f2d 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -2,32 +2,16 @@ import { Env, PluginInputs } from "./types"; import { Context } from "./types"; import { PluginContext } from "./utils/plugin-context-single"; import { proxyCallbacks } from "./handlers/callbacks-proxy"; -import { LogReturn } from "@ubiquity-dao/ubiquibot-logger"; -import { addCommentToIssue } from "./handlers/github/utils/add-comment-to-issues"; +import { bubbleUpErrorComment, sanitizeMetadata } from "./utils/errors"; export async function runPlugin(context: Context) { - const { logger, eventName } = context; + const { eventName } = context; try { return proxyCallbacks(context)[eventName] } catch (err) { - let errorMessage; - if (err instanceof LogReturn) { - errorMessage = err; - } else if (err instanceof Error) { - errorMessage = context.logger.error(err.message, { error: err }); - } else { - errorMessage = context.logger.error("An error occurred", { err }); - } - await addCommentToIssue(context, `${errorMessage?.logMessage.diff}\n`); + return bubbleUpErrorComment(context, err) } - - logger.error(`Unsupported event: ${eventName}`); -} - - -function sanitizeMetadata(obj: LogReturn["metadata"]): string { - return JSON.stringify(obj, null, 2).replace(//g, ">").replace(/--/g, "--"); } /** diff --git a/src/types/env.ts b/src/types/env.ts index 1f86fdf..a950a2e 100644 --- a/src/types/env.ts +++ b/src/types/env.ts @@ -18,17 +18,16 @@ const allowedUpdates = T.Object({ export const env = T.Object({ BOT_TOKEN: T.String(), - BOT_MODE: T.String(), - LOG_LEVEL: T.String(), - DEBUG: T.Transform(T.Union([T.String(), T.Boolean()])).Decode((str) => str === "true" || str === "false" ? str === "true" : str).Encode((bool) => bool.toString()), BOT_WEBHOOK: T.String(), BOT_WEBHOOK_SECRET: T.String(), - SERVER_HOST: T.String(), - SERVER_PORT: T.Transform(T.Unknown()).Decode((str) => Number(str)).Encode((num) => num.toString()), BOT_ADMINS: T.Transform(T.Unknown()).Decode((str) => Array.isArray(str) ? str.map(Number) : [Number(str)]).Encode((arr) => arr.toString()), ALLOWED_UPDATES: T.Optional(T.Array(T.KeyOf(allowedUpdates))), SUPABASE_URL: T.String(), SUPABASE_KEY: T.String(), + TELEGRAM_APP_ID: T.Transform(T.Unknown()).Decode((str) => Number(str)).Encode((num) => num.toString()), + TELEGRAM_API_HASH: T.String(), + APP_ID: T.Transform(T.Unknown()).Decode((str) => Number(str)).Encode((num) => num.toString()), + APP_PRIVATE_KEY: T.Transform(T.Unknown()).Decode((str) => String(str)).Encode((str) => str), }); /** diff --git a/src/types/proxy.ts b/src/types/proxy.ts new file mode 100644 index 0000000..e021116 --- /dev/null +++ b/src/types/proxy.ts @@ -0,0 +1,25 @@ +import { Context, SupportedEvents, SupportedEventsU } from "./context"; + +export type CallbackResult = { status: 200 | 201 | 204 | 404 | 500, reason: string; content?: string | Record }; + +/** + * The `Context` type is a generic type defined as `Context`, + * where `TEvent` is a string representing the event name (e.g., "issues.labeled") + * and `TPayload` is the webhook payload type for that event, derived from + * the `SupportedEvents` type map. + * + * The `ProxyCallbacks` object is cast to allow optional callbacks + * for each event type. This is useful because not all events may have associated callbacks. + * As opposed to Partial which could mean an undefined object. + * + * The expected function signature for callbacks looks like this: + * + * ```typescript + * fn(context: Context<"issues.labeled", SupportedEvents["issues.labeled"]>): Promise + * ``` + */ + +type ProxyTypeHelper = { + [K in SupportedEventsU]: Array<(context: Context) => Promise>; +}; +export type ProxyCallbacks = ProxyTypeHelper; \ No newline at end of file diff --git a/src/types/typeguards.ts b/src/types/typeguards.ts index d25d056..5e21818 100644 --- a/src/types/typeguards.ts +++ b/src/types/typeguards.ts @@ -21,3 +21,8 @@ export function isGithubPayload(inputs: any): inputs is PluginInputs { return false; } } + + +export function isIssueLabeledEvent(context: Context): context is Context<"issues.labeled"> { + return context.eventName === "issues.labeled"; +} \ No newline at end of file diff --git a/src/utils/errors.ts b/src/utils/errors.ts index 921330e..530f9de 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -1,6 +1,24 @@ +import { LogReturn } from "@ubiquity-dao/ubiquibot-logger"; +import { Context } from "../types"; +import { addCommentToIssue } from "#root/handlers/github/utils/add-comment-to-issues.js"; export function handleUncaughtError(error: unknown) { console.error(error); const status = 500; return new Response(JSON.stringify({ error }), { status: status, headers: { "content-type": "application/json" } }); +} +export function sanitizeMetadata(obj: LogReturn["metadata"]): string { + return JSON.stringify(obj, null, 2).replace(//g, ">").replace(/--/g, "--"); +} + +export async function bubbleUpErrorComment(context: Context, err: unknown) { + let errorMessage; + if (err instanceof LogReturn) { + errorMessage = err; + } else if (err instanceof Error) { + errorMessage = context.logger.error(err.message, { error: err }); + } else { + errorMessage = context.logger.error("An error occurred", { err }); + } + await addCommentToIssue(context, `${errorMessage?.logMessage.diff}\n`); } \ No newline at end of file diff --git a/src/utils/logger.ts b/src/utils/logger.ts deleted file mode 100644 index 8a25fd1..0000000 --- a/src/utils/logger.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { LogReturn, Logs, Metadata } from "@ubiquity-dao/ubiquibot-logger"; - -export class Logger extends Logs { - updateId: number | string; - - constructor(updateId: number | string) { - super("verbose"); - this.updateId = updateId; - } - - storeUpdateId(updateId: number | string): Logger { - this.updateId = updateId; - return this; - } - - debug(log: string, metadata?: Metadata): LogReturn { - return super.debug(`[update_id: ${this.updateId}] ${log}`, metadata); - } - - error(log: string, metadata?: Metadata): LogReturn { - return super.error(`[update_id: ${this.updateId}] ${log}`, metadata); - } - - fatal(log: string, metadata?: Metadata): LogReturn { - return super.fatal(`[update_id: ${this.updateId}] ${log}`, metadata); - } - - info(log: string, metadata?: Metadata): LogReturn { - return super.info(`[update_id: ${this.updateId}] ${log}`, metadata); - } - - ok(log: string, metadata?: Metadata): LogReturn { - return super.ok(`[update_id: ${this.updateId}] ${log}`, metadata); - } - - verbose(log: string, metadata?: Metadata): LogReturn { - return super.verbose(`[update_id: ${this.updateId}] ${log}`, metadata); - } -} \ No newline at end of file diff --git a/src/utils/plugin-context-single.ts b/src/utils/plugin-context-single.ts index 879b683..d210a75 100644 --- a/src/utils/plugin-context-single.ts +++ b/src/utils/plugin-context-single.ts @@ -34,6 +34,10 @@ export class PluginContext { return PluginContext.instance; } + getInputs(): PluginInputs { + return this.inputs; + } + getContext(): Context { const octokit = new Octokit({ auth: this.inputs.authToken }); const ctx: Context = { diff --git a/src/workflow-entry.ts b/src/workflow-entry.ts index b46765e..f606254 100644 --- a/src/workflow-entry.ts +++ b/src/workflow-entry.ts @@ -1,9 +1,14 @@ import * as core from "@actions/core"; import * as github from "@actions/github"; -import { Octokit } from "@octokit/rest"; import { Value } from "@sinclair/typebox/value"; -import { envSchema, pluginSettingsSchema, PluginInputs, pluginSettingsValidator } from "./types"; -import { plugin } from "./plugin"; +import { envValidator, pluginSettingsSchema, PluginInputs, pluginSettingsValidator } from "./types"; +import { PluginContext } from "./utils/plugin-context-single"; +import { proxyWorkflowCallbacks } from "./handlers/callbacks-proxy"; +import { bubbleUpErrorComment, sanitizeMetadata } from "./utils/errors"; +import dotenv from "dotenv"; +import { LOG_LEVEL } from "@ubiquity-dao/ubiquibot-logger"; +dotenv.config(); + /** * How a GitHub action executes the plugin. @@ -11,8 +16,39 @@ import { plugin } from "./plugin"; export async function run() { const payload = github.context.payload.inputs; - const env = Value.Decode(envSchema, payload.env); - const settings = Value.Decode(pluginSettingsSchema, Value.Default(pluginSettingsSchema, JSON.parse(payload.settings))); + let env, settings; + + const payloadEnv = { + BOT_TOKEN: process.env.BOT_TOKEN, + BOT_MODE: process.env.BOT_MODE, + LOG_LEVEL: process.env.LOG_LEVEL, + DEBUG: process.env.DEBUG, + BOT_WEBHOOK: process.env.BOT_WEBHOOK, + BOT_WEBHOOK_SECRET: process.env.BOT_WEBHOOK_SECRET, + BOT_ADMINS: process.env.BOT_ADMINS, + TELEGRAM_APP_ID: process.env.TELEGRAM_APP_ID, + TELEGRAM_API_HASH: process.env.TELEGRAM_API_HASH, + SUPABASE_URL: process.env.SUPABASE_URL, + SUPABASE_KEY: process.env.SUPABASE_KEY, + APP_PRIVATE_KEY: process.env.APP_PRIVATE_KEY, + APP_ID: process.env.APP_ID, + } + + try { + env = Value.Decode(envValidator.schema, payloadEnv); + } catch (err) { + console.log("Error decoding env: ", err); + } + + try { + settings = Value.Decode(pluginSettingsSchema, Value.Default(pluginSettingsSchema, JSON.parse(payload.settings))); + } catch (err) { + console.log("Error decoding settings: ", err); + } + + if (!(settings && env)) { + throw new Error("Invalid settings or env provided"); + } if (!pluginSettingsValidator.test(settings)) { throw new Error("Invalid settings provided"); @@ -27,24 +63,31 @@ export async function run() { ref: payload.ref, }; - await plugin(inputs, env); + PluginContext.initialize(inputs, env); - return returnDataToKernel(inputs.authToken, inputs.stateId, {}); -} + const context = PluginContext.getInstance().getContext(); -async function returnDataToKernel(repoToken: string, stateId: string, output: object) { - const octokit = new Octokit({ auth: repoToken }); - await octokit.repos.createDispatchEvent({ - owner: github.context.repo.owner, - repo: github.context.repo.repo, - event_type: "return_data_to_ubiquibot_kernel", - client_payload: { - state_id: stateId, - output: JSON.stringify(output), - }, - }); + try { + return proxyWorkflowCallbacks(context)[inputs.eventName]; + } catch (err) { + return bubbleUpErrorComment(context, err) + } } +// Might use this later to receive data back from it's own workflows +// async function returnDataToKernel(repoToken: string, stateId: string, output: object) { +// const octokit = new Octokit({ auth: repoToken }); +// await octokit.repos.createDispatchEvent({ +// owner: github.context.repo.owner, +// repo: github.context.repo.repo, +// event_type: "return_data_to_ubiquibot_kernel", +// client_payload: { +// state_id: stateId, +// output: JSON.stringify(output), +// }, +// }); +// } + run() .then((result) => { core.setOutput("result", result); diff --git a/src/workflow-functions/README.md b/src/workflow-functions/README.md new file mode 100644 index 0000000..4d5c116 --- /dev/null +++ b/src/workflow-functions/README.md @@ -0,0 +1,6 @@ +These are intended to be an extension of the worker's functionalities due to the restrictions of Cloudflare workers. They are not meant to be a replacement for the worker itself. They are meant to be used in conjunction with the worker. + +The worker should `repository_dispatch` to the workflow to trigger it. The workflow will then run it's logic and return the result to the worker if necessary. + + +... \ No newline at end of file diff --git a/src/workflow-functions/bot.ts b/src/workflow-functions/bot.ts new file mode 100644 index 0000000..81befae --- /dev/null +++ b/src/workflow-functions/bot.ts @@ -0,0 +1,74 @@ +import { Context } from '#root/types/context.js'; +import { TelegramClient } from 'telegram'; +import { Api } from 'telegram/tl'; +import { MemorySession } from 'telegram/sessions'; +import { TelegramClientParams } from 'telegram/client/telegramBaseClient'; + +/** + * This is a different client from the worker instance. This requires a Node + * environment to run and is used to interact with the Telegram API as + * opposed to just the Bot API that the worker instance uses. + */ +export class MtProtoSingleton { + private static instance: MtProtoSingleton; + private static client: TelegramClient; + private static api: typeof Api; + + private constructor() { } + + static async initialize(env: Context["env"]) { + if (!MtProtoSingleton.instance) { + MtProtoSingleton.instance = new MtProtoSingleton(); + MtProtoSingleton.api = Api; + MtProtoSingleton.client = await mtProtoInit(env, MtProtoSingleton.api); + } + return MtProtoSingleton.instance; + } + + static getInstance(env: Context["env"]) { + if (!MtProtoSingleton.instance) { + return this.initialize(env) + } + return MtProtoSingleton.instance; + } + + getClient() { + return MtProtoSingleton.client; + } + + getApi() { + return MtProtoSingleton.api; + } +} + +async function mtProtoInit(env: Context["env"], api: typeof Api) { + const { TELEGRAM_API_HASH, TELEGRAM_APP_ID, BOT_TOKEN } = env + + if (!TELEGRAM_API_HASH || !TELEGRAM_APP_ID || !BOT_TOKEN) { + throw new Error("Missing required environment variables for Telegram API") + } + + const session = new MemorySession(); + session.save(); + + const clientParams: TelegramClientParams = { + connectionRetries: 5, + } + + const client = new TelegramClient( + session, + TELEGRAM_APP_ID, + TELEGRAM_API_HASH, + clientParams + ); + await client.connect(); + + client.invoke(new api.auth.ImportBotAuthorization({ + apiId: TELEGRAM_APP_ID, + apiHash: TELEGRAM_API_HASH, + botAuthToken: BOT_TOKEN, + })); + + + return client; +} \ No newline at end of file diff --git a/src/workflow-functions/create-chat.ts b/src/workflow-functions/create-chat.ts new file mode 100644 index 0000000..7269fea --- /dev/null +++ b/src/workflow-functions/create-chat.ts @@ -0,0 +1,31 @@ +import { Context, SupportedEvents } from "#root/types/context"; +import { CallbackResult } from "#root/types/proxy.js"; +import { MtProtoSingleton } from "./bot"; + +export async function createChat(context: Context<"issues.labeled", SupportedEvents["issues.labeled"]>): Promise { + console.log("Creating chat"); + try { + const { payload, env } = context; + const chatName = payload.issue.title; + + const mtProto = await MtProtoSingleton.getInstance(env); + const client = mtProto.getClient(); + const api = mtProto.getApi(); + console.log("Creating chat with name: ", chatName); + const chat = await client.invoke( + new api.channels.CreateChannel({ + title: chatName, + broadcast: true, + about: payload.issue.body || "No description provided", + }) + ); + + console.log("Chat created: ", chat); + + return { status: 200, reason: "chat_created" }; + } catch (er) { + context.logger.error("Failed to create chat", { er }); + return { status: 500, reason: "chat_creation_failed", content: { error: er } }; + } + +} diff --git a/yarn.lock b/yarn.lock index f5eb332..180e8f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2406,6 +2406,65 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@octokit/app@^15.0.0": + version "15.1.0" + resolved "https://registry.yarnpkg.com/@octokit/app/-/app-15.1.0.tgz#b330d8826be088ec8d1d43a59dc27ef57d1232b2" + integrity sha512-TkBr7QgOmE6ORxvIAhDbZsqPkF7RSqTY4pLTtUQCvr6dTXqvi2fFo46q3h1lxlk/sGMQjqyZ0kEahkD/NyzOHg== + dependencies: + "@octokit/auth-app" "^7.0.0" + "@octokit/auth-unauthenticated" "^6.0.0" + "@octokit/core" "^6.1.2" + "@octokit/oauth-app" "^7.0.0" + "@octokit/plugin-paginate-rest" "^11.0.0" + "@octokit/types" "^13.0.0" + "@octokit/webhooks" "^13.0.0" + +"@octokit/auth-app@^7.0.0": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@octokit/auth-app/-/auth-app-7.1.1.tgz#d8916ad01e6ffb0a0a50507aa613e91fe7a49b93" + integrity sha512-kRAd6yelV9OgvlEJE88H0VLlQdZcag9UlLr7dV0YYP37X8PPDvhgiTy66QVhDXdyoT0AleFN2w/qXkPdrSzINg== + dependencies: + "@octokit/auth-oauth-app" "^8.1.0" + "@octokit/auth-oauth-user" "^5.1.0" + "@octokit/request" "^9.1.1" + "@octokit/request-error" "^6.1.1" + "@octokit/types" "^13.4.1" + lru-cache "^10.0.0" + universal-github-app-jwt "^2.2.0" + universal-user-agent "^7.0.0" + +"@octokit/auth-oauth-app@^8.0.0", "@octokit/auth-oauth-app@^8.1.0": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.1.tgz#6204affa6e86f535016799cadf2af9befe5e893c" + integrity sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg== + dependencies: + "@octokit/auth-oauth-device" "^7.0.0" + "@octokit/auth-oauth-user" "^5.0.1" + "@octokit/request" "^9.0.0" + "@octokit/types" "^13.0.0" + universal-user-agent "^7.0.0" + +"@octokit/auth-oauth-device@^7.0.0", "@octokit/auth-oauth-device@^7.0.1": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.1.tgz#7b4f8f97cbcadbe9894d48cde4406dbdef39875a" + integrity sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg== + dependencies: + "@octokit/oauth-methods" "^5.0.0" + "@octokit/request" "^9.0.0" + "@octokit/types" "^13.0.0" + universal-user-agent "^7.0.0" + +"@octokit/auth-oauth-user@^5.0.1", "@octokit/auth-oauth-user@^5.1.0": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.1.tgz#4f1570c6ee15bb9ddc3dcca83308dcaa159e3848" + integrity sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw== + dependencies: + "@octokit/auth-oauth-device" "^7.0.1" + "@octokit/oauth-methods" "^5.0.0" + "@octokit/request" "^9.0.1" + "@octokit/types" "^13.0.0" + universal-user-agent "^7.0.0" + "@octokit/auth-token@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-4.0.0.tgz#40d203ea827b9f17f42a29c6afb93b7745ef80c7" @@ -2416,6 +2475,14 @@ resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-5.1.1.tgz#3bbfe905111332a17f72d80bd0b51a3e2fa2cf07" integrity sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA== +"@octokit/auth-unauthenticated@^6.0.0", "@octokit/auth-unauthenticated@^6.0.0-beta.1": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@octokit/auth-unauthenticated/-/auth-unauthenticated-6.1.0.tgz#de0fe923bb06ed93aea526ab99972a98c546d0bf" + integrity sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ== + dependencies: + "@octokit/request-error" "^6.0.1" + "@octokit/types" "^13.0.0" + "@octokit/core@^5.0.1": version "5.2.0" resolved "https://registry.yarnpkg.com/@octokit/core/-/core-5.2.0.tgz#ddbeaefc6b44a39834e1bb2e58a49a117672a7ea" @@ -2429,7 +2496,7 @@ before-after-hook "^2.2.0" universal-user-agent "^6.0.0" -"@octokit/core@^6.1.2": +"@octokit/core@^6.0.0", "@octokit/core@^6.1.2": version "6.1.2" resolved "https://registry.yarnpkg.com/@octokit/core/-/core-6.1.2.tgz#20442d0a97c411612da206411e356014d1d1bd17" integrity sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg== @@ -2476,6 +2543,35 @@ "@octokit/types" "^13.0.0" universal-user-agent "^7.0.0" +"@octokit/oauth-app@^7.0.0": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@octokit/oauth-app/-/oauth-app-7.1.3.tgz#a0f256dd185e7c00bfbc3e6bc3c5aad66e42c609" + integrity sha512-EHXbOpBkSGVVGF1W+NLMmsnSsJRkcrnVmDKt0TQYRBb6xWfWzoi9sBD4DIqZ8jGhOWO/V8t4fqFyJ4vDQDn9bg== + dependencies: + "@octokit/auth-oauth-app" "^8.0.0" + "@octokit/auth-oauth-user" "^5.0.1" + "@octokit/auth-unauthenticated" "^6.0.0-beta.1" + "@octokit/core" "^6.0.0" + "@octokit/oauth-authorization-url" "^7.0.0" + "@octokit/oauth-methods" "^5.0.0" + "@types/aws-lambda" "^8.10.83" + universal-user-agent "^7.0.0" + +"@octokit/oauth-authorization-url@^7.0.0": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz#0e17c2225eb66b58ec902d02b6f1315ffe9ff04b" + integrity sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA== + +"@octokit/oauth-methods@^5.0.0": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@octokit/oauth-methods/-/oauth-methods-5.1.2.tgz#fd31d2a69f4c91d1abc1ed1814dda5252c697e02" + integrity sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g== + dependencies: + "@octokit/oauth-authorization-url" "^7.0.0" + "@octokit/request" "^9.1.0" + "@octokit/request-error" "^6.1.0" + "@octokit/types" "^13.0.0" + "@octokit/openapi-types@^20.0.0": version "20.0.0" resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-20.0.0.tgz#9ec2daa0090eeb865ee147636e0c00f73790c6e5" @@ -2491,6 +2587,11 @@ resolved "https://registry.yarnpkg.com/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-8.3.0.tgz#a7a4da00c0f27f7f5708eb3fcebefa08f8d51125" integrity sha512-vKLsoR4xQxg4Z+6rU/F65ItTUz/EXbD+j/d4mlq2GW8TsA4Tc8Kdma2JTAAJ5hrKWUQzkR/Esn2fjsqiVRYaQg== +"@octokit/plugin-paginate-graphql@^5.0.0": + version "5.2.2" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-5.2.2.tgz#54e2afef55bb204eb945a891b85a169b9ddad1f8" + integrity sha512-7znSVvlNAOJisCqAnjN1FtEziweOHSjPGAuc5W58NeGNAr/ZB57yCsjQbXDlWsVryA7hHQaEQPcBbJYFawlkyg== + "@octokit/plugin-paginate-rest@^11.0.0": version "11.3.3" resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.3.tgz#efc97ba66aae6797e2807a082f99b9cfc0e05aba" @@ -2524,6 +2625,23 @@ dependencies: "@octokit/types" "^13.5.0" +"@octokit/plugin-retry@^7.0.0": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-retry/-/plugin-retry-7.1.1.tgz#a84483e4afdd068dd71da81abe206a9e442c1288" + integrity sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw== + dependencies: + "@octokit/request-error" "^6.0.0" + "@octokit/types" "^13.0.0" + bottleneck "^2.15.3" + +"@octokit/plugin-throttling@^9.0.0": + version "9.3.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-throttling/-/plugin-throttling-9.3.1.tgz#5648165e1e70e861625f3a16af6c55cafe861061" + integrity sha512-Qd91H4liUBhwLB2h6jZ99bsxoQdhgPk6TdwnClPyTBSDAdviGPceViEgUwj+pcQDmB/rfAXAXK7MTochpHM3yQ== + dependencies: + "@octokit/types" "^13.0.0" + bottleneck "^2.15.3" + "@octokit/request-error@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-5.1.0.tgz#ee4138538d08c81a60be3f320cd71063064a3b30" @@ -2533,7 +2651,7 @@ deprecation "^2.0.0" once "^1.4.0" -"@octokit/request-error@^6.0.1": +"@octokit/request-error@^6.0.0", "@octokit/request-error@^6.0.1", "@octokit/request-error@^6.1.0", "@octokit/request-error@^6.1.1": version "6.1.4" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-6.1.4.tgz#ad96e29148d19edc2ba8009fc2b5a24a36c90f16" integrity sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg== @@ -2550,7 +2668,7 @@ "@octokit/types" "^13.1.0" universal-user-agent "^6.0.0" -"@octokit/request@^9.0.0": +"@octokit/request@^9.0.0", "@octokit/request@^9.0.1", "@octokit/request@^9.1.0", "@octokit/request@^9.1.1": version "9.1.3" resolved "https://registry.yarnpkg.com/@octokit/request/-/request-9.1.3.tgz#42b693bc06238f43af3c037ebfd35621c6457838" integrity sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA== @@ -2577,7 +2695,7 @@ dependencies: "@octokit/openapi-types" "^20.0.0" -"@octokit/types@^13.0.0", "@octokit/types@^13.1.0", "@octokit/types@^13.5.0": +"@octokit/types@^13.0.0", "@octokit/types@^13.1.0", "@octokit/types@^13.4.1", "@octokit/types@^13.5.0": version "13.5.0" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.5.0.tgz#4796e56b7b267ebc7c921dcec262b3d5bfb18883" integrity sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ== @@ -2589,7 +2707,7 @@ resolved "https://registry.yarnpkg.com/@octokit/webhooks-methods/-/webhooks-methods-5.1.0.tgz#13b6c08f89902c1ab0ddf31c6eeeec9c2772cfe6" integrity sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ== -"@octokit/webhooks@13.3.0": +"@octokit/webhooks@13.3.0", "@octokit/webhooks@^13.0.0": version "13.3.0" resolved "https://registry.yarnpkg.com/@octokit/webhooks/-/webhooks-13.3.0.tgz#fd5d54d47c789c75d60a00eb04e982152d7c654a" integrity sha512-TUkJLtI163Bz5+JK0O+zDkQpn4gKwN+BovclUvCj6pI/6RXrFqQvUMRS2M+Rt8Rv0qR3wjoMoOPmpJKeOh0nBg== @@ -2711,6 +2829,11 @@ "@supabase/realtime-js" "2.10.2" "@supabase/storage-js" "2.7.0" +"@types/aws-lambda@^8.10.83": + version "8.10.145" + resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.145.tgz#b2d31a987f4888e5553ff1819f57cafa475594d9" + integrity sha512-dtByW6WiFk5W5Jfgz1VM+YPA21xMXTuSFoLYIDY0L44jDLLflVPtZkYuu3/YxpGcvjzKFBZLU+GyKjR0HOYtyw== + "@types/babel__core@^7.1.14": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -3518,6 +3641,11 @@ blake3-wasm@^2.1.5: resolved "https://registry.yarnpkg.com/blake3-wasm/-/blake3-wasm-2.1.5.tgz#b22dbb84bc9419ed0159caa76af4b1b132e6ba52" integrity sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g== +bottleneck@^2.15.3: + version "2.19.5" + resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91" + integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -6625,6 +6753,11 @@ loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lru-cache@^10.0.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -7014,6 +7147,22 @@ object.values@^1.1.6, object.values@^1.1.7, object.values@^1.2.0: define-properties "^1.2.1" es-object-atoms "^1.0.0" +octokit@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/octokit/-/octokit-4.0.2.tgz#775d68d363cdaec69d7b73d3dc82ae909d30f59b" + integrity sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg== + dependencies: + "@octokit/app" "^15.0.0" + "@octokit/core" "^6.0.0" + "@octokit/oauth-app" "^7.0.0" + "@octokit/plugin-paginate-graphql" "^5.0.0" + "@octokit/plugin-paginate-rest" "^11.0.0" + "@octokit/plugin-rest-endpoint-methods" "^13.0.0" + "@octokit/plugin-retry" "^7.0.0" + "@octokit/plugin-throttling" "^9.0.0" + "@octokit/request-error" "^6.0.0" + "@octokit/types" "^13.0.0" + on-exit-leak-free@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" @@ -8565,6 +8714,11 @@ unicorn-magic@^0.1.0: resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.1.0.tgz#1bb9a51c823aaf9d73a8bfcd3d1a23dde94b0ce4" integrity sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ== +universal-github-app-jwt@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/universal-github-app-jwt/-/universal-github-app-jwt-2.2.0.tgz#dc6c8929e76f1996a766ba2a08fb420f73365d77" + integrity sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ== + universal-user-agent@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.1.tgz#15f20f55da3c930c57bddbf1734c6654d5fd35aa"