Skip to main content

πŸ’Ύ Sessions

What are sessions for?​

Because, bot only has access to the information of the currently incoming update (e.g. message), i.e. the information that is available on the context object ctx. Consequently, if you do want to access old data, you have to store it as soon as it arrives. This means that you must have a data storage, such as a file, a database, or an in-memory storage.

Sessions are a simple key β†’ value storage for data that is needed temporarily and their loss, etc., will not affect the bot's work in any way.

Sessions provide a simple interface for storing and retrieving data in handlers when it needs to be received and stored in handlers over a time interval greater than 1 update lifecycle.

In other words, sessions are used to store information about the current dialogue between the user and the bot. This allows the bot to remember the state of the conversation between requests, and respond to user requests contextually.

For example:

  • Intermediate handler's data on a time interval greater than the update lifecycle
  • User input data (using scenes / etc.)
  • Other things like storing user locale (for i18n)

Use case​

It is a very common thing for bots to store some piece of data per chat. For example, let's say we want to build a bot that counts the number of received times that a message in current chat for every user. When our bot receives a message, it has to remember how many times it saw the message in that chat before. Sessions are a way to store data per chat.

How session works​

By default, sessions tied to chat identifier and user identifier from context (ctx) and store data as object where key is ctx.from.id + ctx.chat.id with : delimiter and value - your session data.

Example of stored object:

{
"user_id:chat_id": { "someProp": "my data" }
"user_id1:chat_id1": { "someProp": "other data" }
...
}

Flowchart of sessions:​

Click to expand

Configuration​

Accepted fields:

PropertyDescriptionRequiredDefault value
getSessionKeySession key generator function, accepts context, returns key string or nullNo(ctx) => ctx.from && ctx.chat && ctx.from.id + ':' + ctx.chat.id
ttlTime-to-live in secondsNo-
propertySession property name for contextNosession
storeMap compatible storage for storing session data. Can be handwritten storage / keyv / native MapNonew Map()

Availbale getters / setters​

PropertyDescriptionType
storeReturns current storeGetter
ttlReturns current TTLGetter
ttlSets a new TTL for sessionsSetter

Custom session key generator​

You can write own key generator and pass into session middleware factory function.

For example, default session key generator doesn't support inline query, because inline queries doesn't have ctx.chat property, this means not accessible in inline queries by default, let's fix that for inline queries in bot direct messages (when chat type = sender)

bot.use(
session({
getSessionKey: (ctx) => {
// This same as in default session key generator
if (ctx.from?.id && ctx.chat?.id) {
return `${ctx.from.id}:${ctx.chat.id}`
}

// If inline mode called from bot direct messages, use same session key as used in "private"
if (ctx.inlineQuery?.chat_type === 'sender') {
return `${ctx.from.id}:${ctx.from.id}`
}

return null // Return null for other cases (for other - session not available)
}
})
)

Other examples of session storage for chat id and user id

bot.use(
session({
// Stores data per user.
getSessionKey: (ctx) => {
if (ctx.from?.id) {
return `${ctx.from.id}`
}

return null // Return null for other cases (for other - session not available)
}
})
)
bot.use(
session({
// Stores data per chat
getSessionKey: (ctx) => {
if (ctx.chat?.id) {
return `${ctx.chat.id}`
}

return null // Return null for other cases (for other - session not available)
}
})
)

Changing session property name​

caution

If you want to use a session with Stage, you need to specify a new name of the session property in its settings, as the default is ctx.session

bot.use(
session({
property: 'mySessionProp'
})
)

bot.on('message', ctx => {
ctx.mySessionProp // Session available in mySessionProp
})

Session Time-to-live (TTL) / Timeouts​

You can define TTL for session. With the TTL set, the content of the session will be destroyed after it expires

caution

When the session expires, data about the current scene and so on will also be deleted (the user will no longer be in the scene)

bot.use(
session({
ttl: 60 // 60 seconds
})
)

Multi sessions​

You can create separated sessions with different session keys, just create two session middlewares with different key generator and/or property names:

// Default session for chat id + user id
bot.use(session())

// Session stored per chat, same for all chat users
bot.use(
session({
property: 'chatSession',
// Stores data per chat
getSessionKey: (ctx) => {
if (ctx.chat?.id) {
return `${ctx.chat.id}`
}

return null // Return null for other cases (for other - session not available)
}
})
)

bot.on('message', ctx => {
ctx.session // Default session for chat id + user id
ctx.chatSession // Session per chat
})

Write your own store​

You can write your own store, just make it compatible with the Map API. At the moment, at least the implementation of these methods is required - get, set. Methods can be either asynchronous (returning a Promise ) or synchronous

Use with external storage (with keyv)​

note

Before use, install the following dependencies:

npm install keyv @keyv/mongo
const { Opengram, session } = require('opengram')
const Keyv = require('keyv');

const bot = new Opengram(process.env.BOT_TOKEN)
const sessionsStore = new Keyv('mongodb://user:pass@localhost:27017/dbname', { namespace: 'sessions' });

// Handle connection errors
sessionsStore.on('error', err => console.log('Connection Error', err));

bot.use(
session({
store: sessionsStore
})
)

// ...

Additional Information​