What is it ?
it's library/toolkit for easier chat-bot state managment/persistion
currently only telegram API supported, further extension for discrod API possible
Content tree
- How to use
- Setup
- How it works
- How changes in state handled
- Actions
- Avaliable actions
- Modifing actions
- Other features
Adding new 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')
await stateSwitcher.mainState()
}
And then add that state to AllStates to safe typing
declare module 'chat-toolkit' {
interface AllStates {
mainState: typeof mainState
}
}
Default / Main state
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)
Setup
Ensure prisma config and file structure is right
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"]
}
Generate models
Then you need to run
npx chat-toolkit setup
which would create needed models in your project folder
Apply prisma migrations
After that you'll have to apply migration migrate
yarn prisma migrate dev --name add_chat_toolkit_models
Adjust code to supply events to handler
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),
}
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)
})
Actions
Avaliable actions
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)
TODO:
suggest
suggestIt
_disableRecording
_onRestoreDoRun
switchState
escape_
Modifing actions
TODO
Notification / Interrupt state
TODO
Inline keyboard handler
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()
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).
Adjusting global state
TODO
declare module 'chat-toolkit' {
interface GlobalSharedAppContext {
}
}
Adjusting escape ctx
declare module 'chat-toolkit' {
interface EscapeData {
user: User
}
}
TODO