UNPKG

@gramio/scenes

Version:

Scenes plugin for GramIO

359 lines (353 loc) 10.2 kB
'use strict'; var storage = require('@gramio/storage'); var gramio = require('gramio'); var middlewareIo = require('middleware-io'); function getSceneEnter(context, storage, key, allowedScenes) { return async (scene, ...args) => { if (!allowedScenes.includes(scene.name)) throw new Error( `You should register this scene (${scene.name}) in plugin options (scenes: ${allowedScenes.join( ", " )})` ); const sceneParams = { name: scene.name, state: {}, params: args[0], stepId: 0, previousStepId: 0, firstTime: true }; await storage.set(key, sceneParams); context.scene = getInActiveSceneHandler( context, storage, sceneParams, scene, key, allowedScenes ); await scene.compose(context, async () => { const sceneData = await storage.get(key); await storage.set(key, { ...sceneData, firstTime: false }); }); }; } function getSceneExit(storage, sceneData, key) { return () => { sceneData.firstTime = false; return storage.delete(key); }; } async function getSceneHandlers(context, storage, withCurrentScene, scenes, allowedScenes) { const key = `@gramio/scenes:${context.from?.id ?? 0}`; const enterExit = { enter: getSceneEnter(context, storage, key, allowedScenes), exit: () => storage.delete(key) }; if (withCurrentScene) { const sceneData = await storage.get(key); if (!sceneData) return enterExit; const scene = scenes.find((x) => x.name === sceneData.name); if (!scene) return enterExit; return getPossibleInSceneHandlers( context, storage, sceneData, scene, key, allowedScenes ); } return enterExit; } function getInActiveSceneHandler(context, storage, sceneData, scene, key, allowedScenes) { const stepDerives = getStepDerives( context, storage, sceneData, scene, key, allowedScenes ); return { state: sceneData.state, params: sceneData.params, step: stepDerives, update: async (state, options = { step: sceneData.stepId + 1 }) => { sceneData.state = Object.assign(sceneData.state, state); if (options?.step !== void 0) await stepDerives.go(options.step, options.firstTime); else await storage.set(key, sceneData); return state; }, enter: getSceneEnter(context, storage, key, allowedScenes), exit: getSceneExit(storage, sceneData, key), reenter: async () => getSceneEnter( context, storage, key, allowedScenes )(scene, sceneData.params) }; } function getStepDerives(context, storage, storageData, scene, key, allowedScenes) { async function go(stepId, firstTime = true) { storageData.previousStepId = storageData.stepId; storageData.stepId = stepId; storageData.firstTime = firstTime; context.scene = getInActiveSceneHandler( context, storage, storageData, scene, key, allowedScenes ); await scene.run(context, storage, key, storageData); } return { id: storageData.stepId, previousId: storageData.previousStepId, firstTime: storageData.firstTime, go, next: () => go(storageData.stepId + 1), previous: () => go(storageData.stepId - 1) }; } function getInUnknownScene(context, storage, sceneData, scene, key, allowedScenes) { return { ...getInActiveSceneHandler( context, storage, sceneData, scene, key, allowedScenes ), // @ts-expect-error is: (scene2) => scene2.name === sceneData.name }; } function getPossibleInSceneHandlers(context, storage, sceneData, scene, key, allowedScenes) { return { current: getInUnknownScene( context, storage, sceneData, scene, key, allowedScenes ), enter: getSceneEnter(context, storage, key, allowedScenes), exit: getSceneExit(storage, sceneData, key), // @ts-expect-error PRIVATE KEY "~": { data: sceneData } }; } function validateScenes(scenes) { const names = /* @__PURE__ */ new Set(); for (const scene of scenes) { if (names.has(scene.name)) { throw new Error(`Duplicate scene name detected: ${scene.name}`); } names.add(scene.name); } } class Scene { /** @internal */ "~" = { params: {}, state: {}, composer: new gramio.Composer() }; name; stepsCount = 0; constructor(name) { this.name = name; } params() { return this; } state() { return this; } /** * ! ** At the moment, it can only pick up types** */ extend(plugin) { return this; } on(updateName, handler) { this["~"].composer.on(updateName, handler); return this; } use(handler) { this["~"].composer.use(handler); return this; } step(updateName, handler) { const stepId = this.stepsCount++; if (Array.isArray(updateName) || typeof updateName === "string") { if (!handler) throw new Error("You must specify handler as the second argument"); return this.use(async (context, next) => { if (context.is(updateName) && context.scene.step.id === stepId) return handler(context, next); if (context.scene.step.id > stepId) return await next(); }); } return this.use(async (context, next) => { if (context.scene.step.id === stepId) return updateName(context, next); if (context.scene.step.id > stepId) return await next(); }); } ask(key, validator, firstTimeMessage) { return this.step(["callback_query", "message"], async (context, next) => { if (context.scene.step.firstTime) return context.send(firstTimeMessage); let result = validator["~standard"].validate( context.is("message") ? context.text : context.data ); if (result instanceof Promise) result = await result; if (result.issues) return context.send(result.issues[0].message); return context.scene.update({ [key]: result.value }); }); } async compose(context, onNext) { await this["~"].composer.composed(context, middlewareIo.noopNext); onNext?.(); } async run(context, storage, key, data) { return this.compose(context, async () => { if (data.firstTime) await storage.set(key, { ...data, firstTime: false }); }); } } function scenesDerives(scenesOrOptions, optionsRaw) { const options = Array.isArray(scenesOrOptions) ? optionsRaw : scenesOrOptions; const storage$1 = options?.storage ?? storage.inMemoryStorage(); const scenes2 = Array.isArray(scenesOrOptions) ? scenesOrOptions : options?.scenes; const withCurrentScene = options?.withCurrentScene ?? false; if (withCurrentScene && !scenes2?.length) throw new Error("scenes is required when withCurrentScene is true"); if (scenes2?.length) validateScenes(scenes2); const allowedScenes = scenes2?.map((x) => x.name) ?? []; return new gramio.Plugin("@gramio/scenes:derives").derive( // TODO: move it to separate array. but for now it casted to just string[] or readonly array (derive won't work with readonly) [ "message", "callback_query", "channel_post", "chat_join_request", "chosen_inline_result", "inline_query", "web_app_data", "successful_payment", "video_chat_started", "video_chat_ended", "video_chat_scheduled", "video_chat_participants_invited", "passport_data", "new_chat_title", "new_chat_photo", "pinned_message", // "poll_answer", "pre_checkout_query", "proximity_alert_triggered", "shipping_query", "group_chat_created", "delete_chat_photo", "location", "invoice", "message_auto_delete_timer_changed", "migrate_from_chat_id", "migrate_to_chat_id", "new_chat_members", "chat_shared" ], async (context) => { return { scene: await getSceneHandlers( context, storage$1, withCurrentScene, scenes2 ?? [], allowedScenes ) }; } ); } function scenes(scenes2, options) { const storage$1 = options?.storage ?? storage.inMemoryStorage(); validateScenes(scenes2); const allowedScenes = scenes2.map((x) => x.name); return new gramio.Plugin("@gramio/scenes").on( [ "message", "callback_query", "channel_post", "chat_join_request", "chosen_inline_result", "inline_query", "web_app_data", "successful_payment", "video_chat_started", "video_chat_ended", "video_chat_scheduled", "video_chat_participants_invited", "passport_data", "new_chat_title", "new_chat_photo", "pinned_message", // "poll_answer", "pre_checkout_query", "proximity_alert_triggered", "shipping_query", "group_chat_created", "delete_chat_photo", "location", "invoice", "message_auto_delete_timer_changed", "migrate_from_chat_id", "migrate_to_chat_id", "new_chat_members", "chat_shared" ], async (context, next) => { const key = `@gramio/scenes:${context.from?.id ?? 0}`; const sceneData = "scene" in context && typeof context.scene === "object" && context.scene && "~" in context.scene && typeof context.scene["~"] === "object" && context.scene["~"] && "data" in context.scene["~"] ? context.scene["~"].data : await storage$1.get(key); if (!sceneData) return next(); const scene = scenes2.find((x) => x.name === sceneData.name); if (!scene) return next(); const ctx = context; ctx.scene = getInActiveSceneHandler( ctx, storage$1, sceneData, scene, key, allowedScenes ); return scene.run(context, storage$1, key, sceneData); } ).derive(["message", "callback_query"], async (context) => { return { scene: await getSceneHandlers( context, storage$1, false, scenes2, allowedScenes ) }; }); } exports.Scene = Scene; exports.scenes = scenes; exports.scenesDerives = scenesDerives;