Need to prevent GAS web app instances from colliding

Viewed 283

I'm using Google Apps Script to create a web app for my Telegram bot. The web app is invoked by the bot via web hook.

My bot has an inline keyboard that you can see in action in GIF 1 below. Tapping the keyboard buttons you enter a code that appears on the screen.

My problem is when I tap the buttons a little faster, callback queries to the web app collide, which results in messed up entry. See GIF 2.

I've been racking my brain trying to figure out a way to prevent instances of the web app from collision, but I've had no luck so far.

I'm posting my code below. Please, help.

The way I see it is that every instance of the script needs more time to complete before the next one kicks in. I've tried using async/await and lockService to the best of my understanding. I've been advised to try and queue the queries, but sadly haven't been able to make it work.

GIF 1

GIF 2

var lock = LockService.getScriptLock();
    
function doPost(e){

      var contents = JSON.parse(e.postData.contents);
   
      var query_id = contents.callback_query.id;
        
      var mes_id = contents.callback_query.message.message_id;
        
      var userinput = contents.callback_query.data;
        
      var message_now = contents.callback_query.message.text;
      
      var inline_keyboard = contents.callback_query.message.reply_markup;  
      
  
      var message_upd = message_now + " " + userinput;
           
        var keydata = {
            method: "post",
            payload: {
              method: "editMessageText",
              chat_id: String(chat_id),
              message_id: mes_id,
              text: message_upd ,
              parse_mode: "HTML",
              reply_markup: JSON.stringify(inline_keyboard)
            }
          }
   
    lock.waitLock(10000);

    UrlFetchApp.fetch('https://api.telegram.org/bot' + token + '/', keydata); 
        
    UrlFetchApp.fetch(url + "/answerCallbackQuery?callback_query_id=" + query_id);
      
    lock.releaseLock();

    } 
1 Answers

Issue:

I believe the lock actually works. Problem would probably be the callback query sent by telegram bot. In your second gif,

At the time of pressing 2, 3 and 4, the message it is attached to is empty. So, All 4 callbacks' message.text will be empty

var message_now = contents.callback_query.message.text; 

message_now is empty for all 4 messages and all 4 mesage_upd will be different:

var message_upd = message_now + " " + userinput;

Even if you queue everything server side using LockService, If the message_now supplied by telegram is empty for all 4 messages, queuing is useless to create a concatenated string like that.

Possible Solution(s):

  • Queing the callbacks should be done client side. Only after receiving the first button press' response, should the second callback be activated. I'm not sure whether telegram offers such fine control. But if it does, This is the preferred solution.

  • Use Cache service server side to cache the last message_now for a particular message.id scoped to the particular user. Save it to Cache service for 30 seconds or so. If another callback with the same message_id comes along in 30s and the message.text is empty, use the cached message instead.

    • key: Some type of message_id and user_id combination
    • value: Current concatenated message_now

Snippet:

  let message_now = contents.callback_query.message.text;
  if (message_now === '') message_now = cache.get(String(mes_id)) || '';
  /*....*/
  cache.put(String(mes_id), String(message_upd), 30);

function doPost(e) {
  const lock = LockService.getScriptLock();
  lock.waitLock(10000);
  const cache = CacheService.getScriptCache();
  const contents = JSON.parse(e.postData.contents);
  const query_id = contents.callback_query.id;
  const mes_id = contents.callback_query.message.message_id;
  const userinput = contents.callback_query.data;
  let message_now = contents.callback_query.message.text;
  if (message_now === '') message_now = cache.get(String(mes_id)) || '';
  const inline_keyboard = contents.callback_query.message.reply_markup;
  const message_upd = message_now + ' ' + userinput;
  const keydata = {
    method: 'post',
    payload: {
      method: 'editMessageText',
      chat_id: String(chat_id),
      message_id: mes_id,
      text: message_upd,
      parse_mode: 'HTML',
      reply_markup: JSON.stringify(inline_keyboard),
    },
  };
  UrlFetchApp.fetch('https://api.telegram.org/bot' + token + '/', keydata);
  UrlFetchApp.fetch(url + '/answerCallbackQuery?callback_query_id=' + query_id);
  cache.put(String(mes_id), String(message_upd), 30);
  lock.releaseLock();
}

References:

Related