@gramio/scenes
Version:
Scenes plugin for GramIO
359 lines (353 loc) • 10.2 kB
JavaScript
;
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;