it's library/toolkit for easier chat-bot state managment/persistion
currently only telegram API supported, further extension for discrod API possible
- How to use
- Setup
- How it works
- How changes in state handled
- Actions
- Avaliable actions
- Modifing actions
- Other features
- Notification / Interrupt state (TODO)
- Inline keyboard handler
- Adjusting global state
Describe state using actions like say
, expect
, switchState
and others
export async function mainState() {
await say('Enter a')
const a = Number(await expect_())
await say('Enter b')
const b = Number(await expect_())
await say("Reuslt is " + (a + b))
await switchState('mainState')
// or
await stateSwitcher.mainState()
}
And then add that state to AllStates to safe typing
declare module 'chat-toolkit' {
interface AllStates {
mainState: typeof mainState
}
}
You need to specify what is the entry point state and what should run first
that's done at setup, see this for details
const handler
= createTelegramHandler({
bot,
allStates,
defaultState: 'mainState'
}, dbParams)
Currently this npm relies on prisma (in future planned ability to chose over other orms)
and it assumes you have enabled your prismaSchemaFolder
in schema.prisma
and it lies in prisma/schema/
folder
generator client {
provider = "prisma-client-js"
previewFeatures = ["typedSql", "prismaSchemaFolder"]
}
Then you need to run
npx chat-toolkit setup
which would create needed models in your project folder
After that you'll have to apply migration migrate
yarn prisma migrate dev --name add_chat_toolkit_models
Example with Telegraf:
const bot = new Telegraf(process.env.TG_TOKEN)
const prisma = new PrismaClient()
const dbParams = {
findOrCreateUser: findOrCreateUserPrisma(prisma as any),
stateManager: defaultPrismaStateManagerImplementation(prisma as any),
// unfrotunatelly cast to any needed, since local prisma is not the same as chat-toolkits
// right now idk how to avoid this but it's works just fine
}
const allStates = {
mainState
}
const handler
= createTelegramHandler({
bot,
allStates,
defaultState: 'mainState'
}, dbParams)
bot.start(async ctx => {
await handler.handlePrivateMessage(ctx, true)
})
bot.on('message', async ctx => {
await handler.handlePrivateMessage(ctx as any, false)
})
say :: String -> Effect ()
^ Just sends text message to user
expect_ :: () -> Effect (String)
^ Expects text message from user
expectAny :: (Message -> a) -> Effect a
^ In case if if need to wait for specific user message or combined
for example
await expectAny(msg => {
if ('photo' in msg) {
return msg.photo[0]
}
if ('text' in msg) {
return msg.text
}
})
random :: () -> Effect (Number)
^ since states can do anything it's better not to use Math.random directly so it's just wrapper over it (needed for restoring state)
suggest
suggestIt
_disableRecording _onRestoreDoRun
switchState escape_
Suppose case when you need inline keyboard for example [like, dislike]
then each button should execute some kind of logic
in this case you can get use of createCallbackHandle
:
await bot.sendMessage('you liked that post?', {
reply_markup: {
inline_keyboard: [[
{
text: "Like",
callback_data: await recordingObject.like({post_id: post.id}),
},
{
text: "Dislike",
callback_data: await recordingObject.dislike({post_id: post.id}),
}
]]
}
})
where recordingObject
would be defined like this:
const recordingObject = createCallbackHandle({
namespace: 'post-likes',
handler: ctx => ({
async like({post_id}: { post_id: number }) {
const user = await User.find(ctx.from.id)
await Post.find(post_id).likeBy(user)
await ctx.editMessageText //...
},
dislike({post_id}: { post_id: number }) {
const user = await User.find(ctx.from.id)
await Post.find(post_id).likeBy(user)
await ctx.editMessageText //...
}
})
})
To use this you need to enable handler
const redis = new Redis() // from ioredis npm
const bot = new Telegraf(process.env.TG_TOKEN)
/// ...
export const {
setupCallbackHandler,
createCallbackHandle,
} = createRedisInlineKeyboardHandler({
redis,
projectName: 'mybot',
ivalidateIn: duration(1, 'day'),
})
// ...
setupCallbackHandler(bot)
Each call to recordingObject generates a UUID that is stored in callback_data
on the Telegram side.
When a button is pressed, the corresponding function is invoked.
On our end, the arguments for the handler function are stored
in Redis under the key ${projectName}:callbackquery:${uuid}
for 24 hours
(this is the default duration and covers 99% of use cases).
declare module 'chat-toolkit' {
interface GlobalSharedAppContext {
}
}
declare module 'chat-toolkit' {
interface EscapeData {
user: User
}
}
- Notifications
- Custom expects
- GC
- Detect long states
- Error handling strategy