0

I am creating game bot, where user should answer some questions, and admin should approve or reject them. I need to find a way, when admin is accepted the answer, to force change scene of the user.

So for example the user is in scene "answering". When user send message, it sends to admin

const gameButtons = [
  [
    { text: `✅ approve`, callback_data: `gTo:appG/uId=${ctx.state.userId}` }, 
    { text: `❌ reject`, callback_data: `gTo:rejG/uId=${ctx.state.userId}` },
  ], 
];
await ctx.telegram.sendMessage(ctx.state.chatTo, `User answer is: ${ctx.message.text}`, {
  reply_markup: JSON.stringify({ inline_keyboard: gameButtons }),
});

So the admin receive message and or will accept or reject

const approveQuestion = async ({ ctx, text }) => {
  const [, user] = text.split("/");
  const [, userId] = user.split("=");

  //somehow change users scene to answered
  await changeUserScene("winner", userId); 
  await ctx.telegram.sendMessage(userId, "It is right answer");
};

Is there way to do that?

Ashot Khanamiryan
  • 1,102
  • 12
  • 19
  • What do you mean by 'users scene'? – 0stone0 Apr 28 '23 at 09:01
  • "user" is person, from who message is sends to admin, with callback_data. "users scene" mean telegraf scene, where user now. Admin receives some text and buttons. For example admin receive message from user "Good Answer" with buttons "approve" and "reject". And when admin clicked to "approve", it should change scene of that user. – Ashot Khanamiryan Apr 28 '23 at 09:20
  • As `ctx` contains a reference to the scene of the current user, you have to find "scenes storage" somehow, and retrieve one by user id. – xamgore May 01 '23 at 05:54
  • Yeah, that's what I technically want to do, but there is no documentation, and I thought may be someone more familliar with telegraf.js will help – Ashot Khanamiryan May 01 '23 at 09:15

2 Answers2

1

Edit: After reading/trying a lot, based on a few github issues. You can only change a user's scene when you have the context (ctx) of that user.

The easiest way is to get the ctx is to save it for later once you get an interaction with a user.


Please see my test code, explanation below:

const { Telegraf, Scenes, session } = require('telegraf');
const { BaseScene, Stage } = Scenes;

const botToken = '859163076:gfkjsdgkaslgflalsgflgsadhjg';
const user1id  = 123456;
const user2id  = 789456;

const ctxObj = { };

// Scene 1
const scene_1 = new BaseScene('scene_1');
scene_1.enter((ctx) => {

    // Save ctx
    ctxObj[ctx.message.from.id] = ctx;
    console.log('State: ', ctxObj)
    ctx.reply('You just enetered scene_1')
});
scene_1.on('text', async (ctx) => {
    console.log('Got message in scene_1', ctx.message.text, ctx.message.from.id)

    // When receiving form user-2, force user-1 scene
    if (ctx.message.from.id === user2id) {
        if (ctxObj[user1id]) {
            ctxObj[user1id].scene.enter('scene_2');
        }
    }
});

// Scene 2
const scene_2 = new BaseScene('scene_2');
scene_2.enter((ctx) => ctx.reply('You just enetered scene_2'));
scene_2.on('text', (ctx) => {
    console.log('Got message in scene_2', ctx.message.text);
    ctx.scene.leave();
});

// Stage
const stage = new Stage([ scene_1, scene_2 ]);

// Bot
const bot = new Telegraf(botToken);
bot.use(session());
bot.use(stage.middleware());
bot.command('start', (ctx) => ctx.scene.enter('scene_1'));
bot.launch();

The 2 important parts are:

scene_1.enter((ctx) => {

    // Save ctx
    ctxObj[ctx.message.from.id] = ctx;
    console.log('State: ', ctxObj)
    ctx.reply('You just enetered scene_1')
});

Here we save the ctx of every user when they enter scene_1.


scene_1.on('text', async (ctx) => {
    console.log('Got message in scene_1', ctx.message.text, ctx.message.from.id)

    // When receiving form user-2, force user-1 scene
    if (ctx.message.from.id === user2id) {
        if (ctxObj[user1id]) {
            ctxObj[user1id].scene.enter('scene_2');
        }
    }
});

Then, when we receive a message from a user that's in scene_2, we check (this is pure for testing) if the message if from user_2, if so, we use the ctx of user_1 and call scene.enter to force him into the new scene.


This works as expected, if needed I can place some screenshots of 2 Telegram accounts talking to that bot.

It seems like you can create the ctx on the fly, but I'd recommend just saving it if you have the ability to.

0stone0
  • 34,288
  • 4
  • 39
  • 64
  • I am trying to make it work, but no success yet. Found also Scenes.SceneContextScene, which also look like Scene creator, but don't understand how to pass speicific user session and make it work – Ashot Khanamiryan Apr 28 '23 at 11:36
  • The user id should be enought. `session` can be imported. – 0stone0 Apr 28 '23 at 11:39
  • it says TypeError: Scenes.SceneContext is not a constructor – Ashot Khanamiryan Apr 28 '23 at 12:08
  • @AshotKhanamiryan I'm sorry, my first answer didn't work. Should have tested it. Please see my updated answer. – 0stone0 May 01 '23 at 09:57
  • 1
    Yeah. it's working, Many thanks. But it will be great to create context on the fly and enter the scene, because it's not good to have all users context global variable in the app. It will clean, if i will restart app, and alsoit can have memory problem, if there will be many users. But anyway. it's working, thanks! – Ashot Khanamiryan May 01 '23 at 13:35
  • Also there is problem with caching scene on user1 side. Even though it works, and all entering functions are working, but on user1 side, it is remain the old scene. – Ashot Khanamiryan May 01 '23 at 13:57
0

Yes run the following. Have inserted comments to aid

const { session } = require('telegraf')
const RedisSession = require('telegraf-session-redis')

// initialize Redis session store
const redisSession = new RedisSession({
  store: {
    host: 'localhost',
    port: 6379,
    db: 0,
  },
})

// register Redis session middleware
bot.use(session(redisSession))

// define a function to change user's scene
const changeUserScene = async (scene, userId) => {
  // update the user's session data with the new scene
  await bot.telegram.setMyCommands(userId, [{ command: '/start', description: 'Start the game' }]);
  await bot.telegram.sendMessage(userId, `You have answered the question correctly! Moving to the next stage...`);
  await bot.telegram.sendMessage(userId, `You are now in the "${scene}" scene.`);
  await bot.telegram.sendMessage(userId, `Type /start to start the next question.`);
  await bot.session.set(userId, { scene })
  await bot.session.save()
}

const approveQuestion = async ({ ctx, text }) => {
  const [, user] = text.split("/")
  const [, userId] = user.split("=")

  // change user's scene to "answered"
  await changeUserScene("answered", userId)

  // send response to admin
  await ctx.answerCbQuery("Answer approved!")
}