All Downloads are FREE. Search and download functionalities are using the official Maven repository.

telegramium.bots.high.LongPollBot.scala Maven / Gradle / Ivy

package telegramium.bots.high

import scala.concurrent.duration.*
import scala.util.control.NonFatal

import cats.Monad
import cats.Parallel
import cats.effect.Async
import cats.effect.Ref
import cats.syntax.all.*

import telegramium.bots.*
import telegramium.bots.client.*

abstract class LongPollBot[F[_]: Parallel: Async](bot: Api[F]) extends Methods {

  import LongPollBot.OffsetKeeper

  private def noop[A](a: A) = Monad[F].pure(a).void

  def onMessage(msg: Message): F[Unit]                                      = noop(msg)
  def onEditedMessage(msg: Message): F[Unit]                                = noop(msg)
  def onChannelPost(msg: Message): F[Unit]                                  = noop(msg)
  def onEditedChannelPost(msg: Message): F[Unit]                            = noop(msg)
  def onBusinessConnection(connection: BusinessConnection): F[Unit]         = noop(connection)
  def onBusinessMessage(msg: Message): F[Unit]                              = noop(msg)
  def onEditedBusinessMessage(msg: Message): F[Unit]                        = noop(msg)
  def onDeletedBusinessMessages(messages: BusinessMessagesDeleted): F[Unit] = noop(messages)
  def onMessageReaction(reaction: MessageReactionUpdated): F[Unit]          = noop(reaction)
  def onMessageReactionCount(count: MessageReactionCountUpdated): F[Unit]   = noop(count)
  def onInlineQuery(query: InlineQuery): F[Unit]                            = noop(query)
  def onCallbackQuery(query: CallbackQuery): F[Unit]                        = noop(query)
  def onChosenInlineResult(inlineResult: ChosenInlineResult): F[Unit]       = noop(inlineResult)
  def onShippingQuery(query: ShippingQuery): F[Unit]                        = noop(query)
  def onPreCheckoutQuery(query: PreCheckoutQuery): F[Unit]                  = noop(query)
  def onPoll(poll: Poll): F[Unit]                                           = noop(poll)
  def onPollAnswer(pollAnswer: PollAnswer): F[Unit]                         = noop(pollAnswer)
  def onMyChatMember(myChatMember: ChatMemberUpdated): F[Unit]              = noop(myChatMember)
  def onChatMember(chatMember: ChatMemberUpdated): F[Unit]                  = noop(chatMember)
  def onChatJoinRequest(request: ChatJoinRequest): F[Unit]                  = noop(request)
  def onChatBoost(boost: ChatBoostUpdated): F[Unit]                         = noop(boost)
  def onRemovedChatBoost(boostRemoved: ChatBoostRemoved): F[Unit]           = noop(boostRemoved)

  def onUpdate(update: Update): F[Unit] =
    for {
      _ <- update.message.fold(Monad[F].unit)(onMessage)
      _ <- update.editedMessage.fold(Monad[F].unit)(onEditedMessage)
      _ <- update.channelPost.fold(Monad[F].unit)(onChannelPost)
      _ <- update.editedChannelPost.fold(Monad[F].unit)(onEditedChannelPost)
      _ <- update.businessConnection.fold(Monad[F].unit)(onBusinessConnection)
      _ <- update.businessMessage.fold(Monad[F].unit)(onBusinessMessage)
      _ <- update.editedBusinessMessage.fold(Monad[F].unit)(onEditedBusinessMessage)
      _ <- update.deletedBusinessMessages.fold(Monad[F].unit)(onDeletedBusinessMessages)
      _ <- update.messageReaction.fold(Monad[F].unit)(onMessageReaction)
      _ <- update.messageReactionCount.fold(Monad[F].unit)(onMessageReactionCount)
      _ <- update.inlineQuery.fold(Monad[F].unit)(onInlineQuery)
      _ <- update.callbackQuery.fold(Monad[F].unit)(onCallbackQuery)
      _ <- update.chosenInlineResult.fold(Monad[F].unit)(onChosenInlineResult)
      _ <- update.shippingQuery.fold(Monad[F].unit)(onShippingQuery)
      _ <- update.preCheckoutQuery.fold(Monad[F].unit)(onPreCheckoutQuery)
      _ <- update.poll.fold(Monad[F].unit)(onPoll)
      _ <- update.pollAnswer.fold(Monad[F].unit)(onPollAnswer)
      _ <- update.myChatMember.fold(Monad[F].unit)(onMyChatMember)
      _ <- update.chatMember.fold(Monad[F].unit)(onChatMember)
      _ <- update.chatJoinRequest.fold(Monad[F].unit)(onChatJoinRequest)
      _ <- update.chatBoost.fold(Monad[F].unit)(onChatBoost)
      _ <- update.removedChatBoost.fold(Monad[F].unit)(onRemovedChatBoost)
    } yield ()

  def onError(e: Throwable): F[Unit] = {
    Async[F].delay(e.printStackTrace())
  }

  def poll(offsetKeeper: OffsetKeeper[F]): F[Unit] = {
    for {
      offset <- offsetKeeper.getOffset
      seconds = pollInterval.toSeconds.toInt
      updates <- bot
        .execute(getUpdates(offset = Some(offset), timeout = Some(seconds)))
        .onError {
          case _: java.util.concurrent.TimeoutException => poll(offsetKeeper)
          case NonFatal(e) =>
            for {
              _     <- onError(e)
              delay <- onErrorDelay
              _     <- Async[F].sleep(delay)
              _     <- poll(offsetKeeper)
            } yield ()
        }
      _ <- updates.parTraverse {
        onUpdate(_).recoverWith { case NonFatal(e) =>
          onError(e)
        }
      }
      _    <- updates.map(_.updateId).maximumOption.traverse(max => offsetKeeper.setOffset(max + 1))
      next <- poll(offsetKeeper)
    } yield {
      next
    }
  }

  def start(): F[Unit] = {
    for {
      _          <- bot.execute(deleteWebhook())
      refCounter <- Ref.of[F, Int](0)
      offsetKeeper = new OffsetKeeper[F] {
        def getOffset: F[Int]               = refCounter.get
        def setOffset(offset: Int): F[Unit] = refCounter.set(offset)
      }
      _ <- poll(offsetKeeper)
    } yield {
      ()
    }
  }

  def pollInterval: Duration = 10.seconds

  /* Use effectful override to implement different backoff strategies */
  def onErrorDelay: F[FiniteDuration] = {
    Async[F].delay(5.seconds)
  }

}

object LongPollBot {

  trait OffsetKeeper[F[_]] {
    def getOffset: F[Int]
    def setOffset(offset: Int): F[Unit]
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy