diff --git a/build.sbt b/build.sbt index 2350a09..f2f50d0 100644 --- a/build.sbt +++ b/build.sbt @@ -4,4 +4,13 @@ version := "0.1" scalaVersion := "2.12.9" -libraryDependencies += "com.bot4s" %% "telegram-core" % "4.4.0-RC2" \ No newline at end of file +libraryDependencies ++= Seq( + "com.bot4s" %% "telegram-core" % "4.4.0-RC2", + "com.softwaremill.sttp" %% "json4s" % "1.7.2", + "org.json4s" %% "json4s-native" % "3.6.0", + "org.scalamock" %% "scalamock" % "4.4.0" % Test +) + +libraryDependencies += "org.scalactic" %% "scalactic" % "3.1.1" +libraryDependencies += "org.scalatest" %% "scalatest" % "3.1.1" % "test" + diff --git a/src/main/scala/bot/BotStarter.scala b/src/main/scala/bot/BotStarter.scala index 565a9a9..ac10140 100644 --- a/src/main/scala/bot/BotStarter.scala +++ b/src/main/scala/bot/BotStarter.scala @@ -7,7 +7,8 @@ import com.bot4s.telegram.api.declarative.Commands import com.bot4s.telegram.clients.{FutureSttpClient, ScalajHttpClient} import com.bot4s.telegram.future.{Polling, TelegramBot} import com.bot4s.telegram.models.{Message, User} -import com.softwaremill.sttp.SttpBackendOptions +import com.softwaremill.sttp.{SttpBackendOptions, sttp} +import com.softwaremill.sttp.json4s.asJson import com.softwaremill.sttp.okhttp.{OkHttpBackend, OkHttpFutureBackend} import slogging.{LogLevel, LoggerConfig, PrintLoggerFactory} @@ -17,43 +18,78 @@ import scala.collection.mutable.Queue import scala.concurrent.duration.Duration import scala.concurrent.{Await, ExecutionContext, Future} import scala.io.Source +import scala.util.Random +import scala.io.Source +import com.softwaremill.sttp._ +import com.softwaremill.sttp.json4s._ +import org.json4s.native.Serialization -class BotStarter(override val client: RequestHandler[Future]) extends TelegramBot +class BotStarter(override val client: RequestHandler[Future], val service: Service, + val userHandler: UserHandler, val messageHandler: MessageHandler) extends TelegramBot with Polling with Commands[Future] { - val registeredUsers: mutable.Set[User] = mutable.Set[User]() - onCommand("/start") { implicit msg => msg.from match { case None => reply("Register error").void - case Some(user) => { - registeredUsers += user - reply(s"You're registered.\n Your id is ${user.id}").void - } + case Some(user) => + userHandler.register(user) + reply(s"You're registered.\nYour id is ${user.id}").void } } onCommand("/users") { implicit msg => - var usersString = "" - registeredUsers.foreach { - it => usersString += s"${it.firstName} ${it.lastName}\n" + reply(userHandler.show()).void + } + + onCommand("/check") { implicit msg => + msg.from match { + case None => reply("Error.").void + case Some(user) => + val messages = messageHandler.show(user.id.toString) + messageHandler.clear(user.id.toString) + if (messages.nonEmpty) reply(messages).void + else reply("You haven't received any new messages yet.").void + } + } + + onCommand("/send") { implicit msg => + var from_id : Int = 0 + msg.from match { + case None => from_id = 0 + case Some(user) => from_id = user.id + } + + msg.text match { + case None => reply("Enter non-empty message").void + case Some(s) => + val id: String = s.slice(6, 15) + val text: String = s.slice(16, s.length) + messageHandler.send(from_id.toString, id, text) + reply(s"Message was sent to $id").void } - reply(usersString).void + } + + onCommand("/cat") { implicit msg => + service.getCat.flatMap(reply(_)).void } } object BotStarter { def main(args: Array[String]): Unit = { implicit val ec: ExecutionContext = ExecutionContext.global - implicit val backend = OkHttpFutureBackend( + implicit val backend: SttpBackend[Future, Nothing] = OkHttpFutureBackend( SttpBackendOptions.Default.socksProxy("ps8yglk.ddns.net", 11999) ) + val userHandler = new UserHandler + val messageHandler = new MessageHandler val fileSource = Source.fromFile("token.txt") val token = fileSource.mkString fileSource.close() - val bot = new BotStarter(new FutureSttpClient(token)) + + val service: Service = new Service() + val bot = new BotStarter(new FutureSttpClient(token), service, userHandler, messageHandler) Await.result(bot.run(), Duration.Inf) } } \ No newline at end of file diff --git a/src/main/scala/bot/MessageHandler.scala b/src/main/scala/bot/MessageHandler.scala new file mode 100644 index 0000000..3fe158a --- /dev/null +++ b/src/main/scala/bot/MessageHandler.scala @@ -0,0 +1,21 @@ +package bot + +import scala.collection.mutable + +class MessageHandler { + var messagesUsers: mutable.Map[String, mutable.ListBuffer[(String, String)]] = + mutable.Map[String, mutable.ListBuffer[(String, String)]]().withDefaultValue(mutable.ListBuffer()) + + def send(senderId: String, receiverId: String, message: String): Unit = { + if (!messagesUsers.contains(receiverId)) { + messagesUsers(receiverId) = mutable.ListBuffer() + } + messagesUsers(receiverId) += Tuple2(senderId, message) + } + + def show(id: String): String = messagesUsers(id).foldLeft(""){ + (last, p) => last + s"From: ${p._1}\n${p._2}\n\n" + } + + def clear(id: String): Unit = messagesUsers(id).clear() +} \ No newline at end of file diff --git a/src/main/scala/bot/Service.scala b/src/main/scala/bot/Service.scala new file mode 100644 index 0000000..dd71658 --- /dev/null +++ b/src/main/scala/bot/Service.scala @@ -0,0 +1,24 @@ +package bot + +import com.softwaremill.sttp.{SttpBackendOptions, sttp} +import com.softwaremill.sttp.json4s.asJson + +import scala.concurrent.{Await, ExecutionContext, Future} +import com.softwaremill.sttp._ +import org.json4s.native.Serialization + +case class Response(data: List[Data]) +case class Data(link: String) + +class Service(implicit val backend: SttpBackend[Future, Nothing], + implicit val serialization : Serialization.type = org.json4s.native.Serialization, + implicit val ec: ExecutionContext = ExecutionContext.global) { + val request: RequestT[Id, Response, Nothing] = sttp + .header("Authorization", "Client-ID 2a47c24862afdf7") + .get(uri"https://api.imgur.com/3/gallery/search?q=cats") + .response(asJson[Response]) + + def getCat: Future[String] = backend.send(request).map { response => + scala.util.Random.shuffle(response.unsafeBody.data).head.link + } +} \ No newline at end of file diff --git a/src/main/scala/bot/UserHandler.scala b/src/main/scala/bot/UserHandler.scala new file mode 100644 index 0000000..d20ad75 --- /dev/null +++ b/src/main/scala/bot/UserHandler.scala @@ -0,0 +1,18 @@ +package bot + +import com.bot4s.telegram.models.User + +import scala.collection.mutable + + +class UserHandler { + def getUserDescription(user : User) = s"${user.firstName} ${user.lastName.getOrElse("")}, id: ${user.id}\n" + + val registeredUsers: mutable.Set[User] = mutable.Set[User]() + + def register(user: User): Unit = registeredUsers += user + + def show(): String = registeredUsers.foldLeft("") { + (last, user) => last + getUserDescription(user) + } +} \ No newline at end of file diff --git a/src/test/scala/bot/MessageHandlerTest.scala b/src/test/scala/bot/MessageHandlerTest.scala new file mode 100644 index 0000000..2594376 --- /dev/null +++ b/src/test/scala/bot/MessageHandlerTest.scala @@ -0,0 +1,33 @@ +package bot + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.must.Matchers + +class MessageHandlerTest extends AnyFlatSpec with Matchers { + val messageHandler = new MessageHandler() + + "Person without messages" should "have empty list of messages" in { + assert(messageHandler.show("1") == "") + } + + "Clear" should "clear" in { + messageHandler.send("2", "1", "mem") + messageHandler.clear("1") + assert(messageHandler.show("1") == "") + } + + "Person with messages" should "have non-empty list of messages" in { + messageHandler.send("2", "1", "mem") + assert(messageHandler.show("1") == "From: 2\nmem\n\n") + messageHandler.clear("1") + } + + "Messages from other people" should "be different" in { + messageHandler.send("2", "1", "kek") + messageHandler.send("3", "1", "kek") + print(messageHandler.show("1")) + assert(messageHandler.show("1") == "From: 2\nkek\n\nFrom: 3\nkek\n\n") + messageHandler.clear("1") + } + +} \ No newline at end of file diff --git a/src/test/scala/bot/ServiceTest.scala b/src/test/scala/bot/ServiceTest.scala new file mode 100644 index 0000000..a02a61d --- /dev/null +++ b/src/test/scala/bot/ServiceTest.scala @@ -0,0 +1,27 @@ +package bot + +import com.softwaremill.sttp +import com.softwaremill.sttp.SttpBackend +import org.scalamock.matchers.Matchers +import org.scalamock.scalatest.MockFactory +import org.scalatest.flatspec.AnyFlatSpec + +import scala.concurrent.duration.Duration +import scala.concurrent.{Await, ExecutionContext, ExecutionContextExecutor, Future} + +class ServiceTest extends AnyFlatSpec with Matchers with MockFactory { + trait mocks { + implicit val ec: ExecutionContextExecutor = ExecutionContext.global + implicit val backend: SttpBackend[Future, Nothing] = mock[SttpBackend[Future, Nothing]] + + val service = new Service() + } + + "Service" should "return link with cat" in new mocks { + (backend.send[Response] _).expects(*).returning(Future.successful( + sttp.Response.ok(Response(List(Data(link = "cat")))) + )) + val result: String = Await.result(service.getCat, Duration.Inf) + assert(result == "cat") + } +} diff --git a/src/test/scala/bot/UserHandlerTest.scala b/src/test/scala/bot/UserHandlerTest.scala new file mode 100644 index 0000000..866775a --- /dev/null +++ b/src/test/scala/bot/UserHandlerTest.scala @@ -0,0 +1,47 @@ +package bot + +import com.bot4s.telegram.models.User +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.must.Matchers + + +class UserHandlerTest extends AnyFlatSpec with Matchers { + val userHandler = new UserHandler + + val users : List[User] = List( + User(0, isBot = false, "Shrek", Some("1")), + User(1, isBot = false, "Shrek", Some("2")), + User(2, isBot = false, "Shrek", Some("3")), + User(3, isBot = false, "Donkey", Some("Kong")), + User(4, isBot = false, "Mario", None) + ) + + val usersDescriptions : List[String] = List( + "Shrek 1, id: 0\n", + "Shrek 2, id: 1\n", + "Shrek 3, id: 2\n", + "Donkey Kong, id: 3\n", + "Mario , id: 4\n" + ) + + "Descriptions" should "match" in { + users.zipWithIndex.foreach { x => assert(userHandler.getUserDescription(x._1) == usersDescriptions(x._2)) } + } + + "Register" should "add Users" in { + users.foreach {userHandler.register } + val tmp: Array[String] = userHandler.show().split('\n') + assert(tmp.length == 5) + } + + "Users" should "have description" in { + userHandler.register(users(0)) + userHandler.register(users(2)) + userHandler.register(users(4)) + val tmp: Array[String] = userHandler.show().split('\n') + assert(tmp.contains(usersDescriptions(0).dropRight(1))) + assert(tmp.contains(usersDescriptions(2).dropRight(1))) + assert(tmp.contains(usersDescriptions(4).dropRight(1))) + } + +}