<commit_before>package lila.round

import actorApi._, round._
import chess.{ Black, Centis, Color, White }
import org.joda.time.DateTime
import ornicar.scalalib.Zero
import play.api.libs.json._
import scala.concurrent.duration._
import scala.concurrent.Promise
import scala.util.chaining._

import lila.game.Game.{ FullId, PlayerId }
import lila.game.{ Game, GameRepo, Pov, Event, Progress, Player => GamePlayer }
import lila.hub.actorApi.round.{
  Abort,
  BotPlay,
  FishnetPlay,
  FishnetStart,
  IsOnGame,
  RematchNo,
  RematchYes,
  Resign
}
import lila.hub.Duct
import lila.room.RoomSocket.{ Protocol => RP, _ }
import lila.socket.Socket.{ makeMessage, GetVersion, SocketVersion }
import lila.socket.UserLagCache
import lila.user.User

final private[round] class RoundDuct(
    dependencies: RoundDuct.Dependencies,
    gameId: Game.ID,
    socketSend: String => Unit
)(implicit
    ec: scala.concurrent.ExecutionContext,
    proxy: GameProxy
) extends Duct {

  import RoundSocket.Protocol
  import RoundDuct._
  import dependencies._

  private var takebackSituation: Option[TakebackSituation] = None

  private var version = SocketVersion(0)

  private var mightBeSimul = true // until proven otherwise

  final private class Player(color: Color) {

    private var offlineSince: Option[Long] = nowMillis.some
    // wether the player closed the window intentionally
    private var bye: Boolean = false
    // connected as a bot
    private var botConnected: Boolean = false

    var userId     = none[User.ID]
    var goneWeight = 1f

    def isOnline = offlineSince.isEmpty || botConnected

    def setOnline(on: Boolean): Unit = {
      isLongGone foreach { _ ?? notifyGone(color, gone = false) }
      offlineSince = if (on) None else offlineSince orElse nowMillis.some
      bye = bye && !on
    }
    def setBye(): Unit = {
      bye = true
    }

    private def isHostingSimul: Fu[Boolean] = mightBeSimul ?? userId ?? isSimulHost

    private def timeoutMillis: Long = {
      val base = {
        if (bye) RoundSocket.ragequitTimeout
        else
          proxy.withGameOptionSync { g =>
            RoundSocket.povDisconnectTimeout(g pov color)
          } | RoundSocket.disconnectTimeout
      }.toMillis * goneWeight
      base atLeast RoundSocket.ragequitTimeout.toMillis.toFloat
    }.toLong

    def isLongGone: Fu[Boolean] = {
      !botConnected && offlineSince.exists(_ < (nowMillis - timeoutMillis))
    } ?? !isHostingSimul

    def showMillisToGone: Fu[Option[Long]] =
      if (botConnected) fuccess(none)
      else {
        val now = nowMillis
        offlineSince.filter { since =>
          bye || (now - since) > 5000
        } ?? { since =>
          isHostingSimul map {
            !_ option (timeoutMillis + since - now)
          }
        }
      }

    def setBotConnected(v: Boolean) =
      botConnected = v

    def isBotConnected = botConnected
  }

  private val whitePlayer = new Player(White)
  private val blackPlayer = new Player(Black)

  def getGame: Fu[Option[Game]]          = proxy.game
  def updateGame(f: Game => Game): Funit = proxy update f

  val process: Duct.ReceiveAsync = {

    case SetGameInfo(game, (whiteGoneWeight, blackGoneWeight)) =>
      fuccess {
        whitePlayer.userId = game.player(White).userId
        blackPlayer.userId = game.player(Black).userId
        mightBeSimul = game.isSimul
        whitePlayer.goneWeight = whiteGoneWeight
        blackPlayer.goneWeight = blackGoneWeight
        if (game.playableByAi) player.requestFishnet(game, this)
      }

    // socket stuff

    case ByePlayer(playerId) =>
      proxy.withPov(playerId) {
        _ ?? { pov =>
          fuccess(getPlayer(pov.color).setBye())
        }
      }

    case GetVersion(promise) =>
      fuccess {
        promise success version
      }
    case SetVersion(v) =>
      fuccess {
        version = v
      }

    case RoomCrowd(white, black) =>
      fuccess {
        whitePlayer setOnline white
        blackPlayer setOnline black
      }

    case IsOnGame(color, promise) =>
      fuccess {
        promise success getPlayer(color).isOnline
      }

    case GetSocketStatus(promise) =>
      whitePlayer.isLongGone zip blackPlayer.isLongGone map { case (whiteIsGone, blackIsGone) =>
        promise success SocketStatus(
          version = version,
          whiteOnGame = whitePlayer.isOnline,
          whiteIsGone = whiteIsGone,
          blackOnGame = blackPlayer.isOnline,
          blackIsGone = blackIsGone
        )
      }

    case HasUserId(userId, promise) =>
      fuccess {
        promise success {
          (whitePlayer.userId.has(userId) && whitePlayer.isOnline) ||
          (blackPlayer.userId.has(userId) && blackPlayer.isOnline)
        }
      }

    case lila.chat.actorApi.RoundLine(line, watcher) =>
      val message = line match {
        case l: lila.chat.UserLine => Event.UserMessage(l, watcher).some
        case l: lila.chat.PlayerLine =>
          proxy.withGameOptionSync {
            _.fromFriend option Event.PlayerMessage(l)
          }.flatten
      }
      message ?? { msg =>
        fuccess(publish(List(msg)))
      }

    case Protocol.In.HoldAlert(fullId, ip, mean, sd) =><commit_msg>Revert "make sure anon chat only works in friend games - closes #7666"<commit_after>package lila.round

import actorApi._, round._
import chess.{ Black, Centis, Color, White }
import org.joda.time.DateTime
import ornicar.scalalib.Zero
import play.api.libs.json._
import scala.concurrent.duration._
import scala.concurrent.Promise
import scala.util.chaining._

import lila.game.Game.{ FullId, PlayerId }
import lila.game.{ Game, GameRepo, Pov, Event, Progress, Player => GamePlayer }
import lila.hub.actorApi.round.{
  Abort,
  BotPlay,
  FishnetPlay,
  FishnetStart,
  IsOnGame,
  RematchNo,
  RematchYes,
  Resign
}
import lila.hub.Duct
import lila.room.RoomSocket.{ Protocol => RP, _ }
import lila.socket.Socket.{ makeMessage, GetVersion, SocketVersion }
import lila.socket.UserLagCache
import lila.user.User

final private[round] class RoundDuct(
    dependencies: RoundDuct.Dependencies,
    gameId: Game.ID,
    socketSend: String => Unit
)(implicit
    ec: scala.concurrent.ExecutionContext,
    proxy: GameProxy
) extends Duct {

  import RoundSocket.Protocol
  import RoundDuct._
  import dependencies._

  private var takebackSituation: Option[TakebackSituation] = None

  private var version = SocketVersion(0)

  private var mightBeSimul = true // until proven otherwise

  final private class Player(color: Color) {

    private var offlineSince: Option[Long] = nowMillis.some
    // wether the player closed the window intentionally
    private var bye: Boolean = false
    // connected as a bot
    private var botConnected: Boolean = false

    var userId     = none[User.ID]
    var goneWeight = 1f

    def isOnline = offlineSince.isEmpty || botConnected

    def setOnline(on: Boolean): Unit = {
      isLongGone foreach { _ ?? notifyGone(color, gone = false) }
      offlineSince = if (on) None else offlineSince orElse nowMillis.some
      bye = bye && !on
    }
    def setBye(): Unit = {
      bye = true
    }

    private def isHostingSimul: Fu[Boolean] = mightBeSimul ?? userId ?? isSimulHost

    private def timeoutMillis: Long = {
      val base = {
        if (bye) RoundSocket.ragequitTimeout
        else
          proxy.withGameOptionSync { g =>
            RoundSocket.povDisconnectTimeout(g pov color)
          } | RoundSocket.disconnectTimeout
      }.toMillis * goneWeight
      base atLeast RoundSocket.ragequitTimeout.toMillis.toFloat
    }.toLong

    def isLongGone: Fu[Boolean] = {
      !botConnected && offlineSince.exists(_ < (nowMillis - timeoutMillis))
    } ?? !isHostingSimul

    def showMillisToGone: Fu[Option[Long]] =
      if (botConnected) fuccess(none)
      else {
        val now = nowMillis
        offlineSince.filter { since =>
          bye || (now - since) > 5000
        } ?? { since =>
          isHostingSimul map {
            !_ option (timeoutMillis + since - now)
          }
        }
      }

    def setBotConnected(v: Boolean) =
      botConnected = v

    def isBotConnected = botConnected
  }

  private val whitePlayer = new Player(White)
  private val blackPlayer = new Player(Black)

  def getGame: Fu[Option[Game]]          = proxy.game
  def updateGame(f: Game => Game): Funit = proxy update f

  val process: Duct.ReceiveAsync = {

    case SetGameInfo(game, (whiteGoneWeight, blackGoneWeight)) =>
      fuccess {
        whitePlayer.userId = game.player(White).userId
        blackPlayer.userId = game.player(Black).userId
        mightBeSimul = game.isSimul
        whitePlayer.goneWeight = whiteGoneWeight
        blackPlayer.goneWeight = blackGoneWeight
        if (game.playableByAi) player.requestFishnet(game, this)
      }

    // socket stuff

    case ByePlayer(playerId) =>
      proxy.withPov(playerId) {
        _ ?? { pov =>
          fuccess(getPlayer(pov.color).setBye())
        }
      }

    case GetVersion(promise) =>
      fuccess {
        promise success version
      }
    case SetVersion(v) =>
      fuccess {
        version = v
      }

    case RoomCrowd(white, black) =>
      fuccess {
        whitePlayer setOnline white
        blackPlayer setOnline black
      }

    case IsOnGame(color, promise) =>
      fuccess {
        promise success getPlayer(color).isOnline
      }

    case GetSocketStatus(promise) =>
      whitePlayer.isLongGone zip blackPlayer.isLongGone map { case (whiteIsGone, blackIsGone) =>
        promise success SocketStatus(
          version = version,
          whiteOnGame = whitePlayer.isOnline,
          whiteIsGone = whiteIsGone,
          blackOnGame = blackPlayer.isOnline,
          blackIsGone = blackIsGone
        )
      }

    case HasUserId(userId, promise) =>
      fuccess {
        promise success {
          (whitePlayer.userId.has(userId) && whitePlayer.isOnline) ||
          (blackPlayer.userId.has(userId) && blackPlayer.isOnline)
        }
      }

    case lila.chat.actorApi.RoundLine(line, watcher) =>
      fuccess {
        publish(List(line match {
          case l: lila.chat.UserLine   => Event.UserMessage(l, watcher)
          case l: lila.chat.PlayerLine => Event.PlayerMessage(l)
        }))
      }

    case Protocol.In.HoldAlert(fullId, ip, mean, sd) =>
{'fraction_non_alphanumeric': 0.09429400386847195, 'fraction_numerical': 0.0015473887814313346, 'mean_word_length': 2.590625, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 48, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7471983', 'n_tokens_mistral': 3209, 'n_tokens_neox': 3119, 'n_words': 751}