dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

handlers.go (18193B)


      1 package v1
      2 
      3 import (
      4 	"dkforest/pkg/LeChatPHP/captcha"
      5 	mycaptcha "dkforest/pkg/captcha"
      6 	"dkforest/pkg/config"
      7 	"dkforest/pkg/database"
      8 	dutils "dkforest/pkg/database/utils"
      9 	"dkforest/pkg/global"
     10 	"dkforest/pkg/managers"
     11 	"dkforest/pkg/utils"
     12 	"dkforest/pkg/web/handlers/interceptors"
     13 	"dkforest/pkg/web/handlers/interceptors/command"
     14 	hutils "dkforest/pkg/web/handlers/utils"
     15 	"errors"
     16 	"fmt"
     17 	"github.com/labstack/echo"
     18 	"github.com/sirupsen/logrus"
     19 	qt "github.com/valyala/quicktemplate"
     20 	"io"
     21 	"net/http"
     22 	"strings"
     23 	"time"
     24 )
     25 
     26 // GetChatMenuData gets the data needed to render the "right-menu" in a chat room.
     27 // We have it separate because we have one endpoint that only render the right menu (for "stream" chat).
     28 // and one endpoint to render both messages and menu ("non-stream" chat).
     29 func GetChatMenuData(c echo.Context, room database.ChatRoom) ChatMenuData {
     30 	db := c.Get("database").(*database.DkfDB)
     31 	authUser := c.Get("authUser").(*database.User)
     32 
     33 	data := ChatMenuData{}
     34 	data.PreventRefresh = utils.DoParseBool(c.QueryParam("r"))
     35 	sessionToken := ""
     36 	authCookie, _ := c.Cookie(hutils.AuthCookieName)
     37 	if authCookie != nil {
     38 		sessionToken = authCookie.Value
     39 	}
     40 	data.InboxCount = global.GetUserNotificationCount(db, authUser.ID, sessionToken)
     41 	data.OfficialRooms, _ = db.GetOfficialChatRooms1(authUser.ID)
     42 	data.SubscribedRooms, _ = db.GetUserRoomSubscriptions(authUser.ID)
     43 
     44 	membersInRoom, membersInChat := managers.ActiveUsers.GetRoomUsers(room, managers.GetUserIgnoreSet(db, authUser))
     45 	data.Members = membersInRoom
     46 	data.MembersInChat = membersInChat
     47 	for _, user := range membersInChat {
     48 		if !user.IsHellbanned {
     49 			data.VisibleMemberInChat = true
     50 			break
     51 		}
     52 	}
     53 	data.RoomName = room.Name
     54 
     55 	if _, found := c.QueryParams()["ml"]; found {
     56 		data.TopBarQueryParams = "&ml=1"
     57 	}
     58 
     59 	return data
     60 }
     61 
     62 func chatMessages(c echo.Context) (status int, data ChatMessagesData) {
     63 	authUser := c.Get("authUser").(*database.User)
     64 	db := c.Get("database").(*database.DkfDB)
     65 	roomName := c.Param("roomName")
     66 
     67 	pmOnlyQuery := dutils.DoParsePmDisplayMode(c.QueryParam("pmonly"))
     68 	mentionsOnlyQuery := utils.DoParseBool(c.QueryParam("mentionsOnly"))
     69 	pmUserID := dutils.GetUserIDFromUsername(db, c.QueryParam(command.RedirectPmUsernameQP))
     70 
     71 	room, roomKey, err := dutils.GetRoomAndKey(db, c, roomName)
     72 	if err != nil {
     73 		return http.StatusForbidden, data
     74 	}
     75 
     76 	managers.ActiveUsers.UpdateUserInRoom(room, managers.NewUserInfo(authUser))
     77 
     78 	displayHellbanned := authUser.DisplayHellbanned || authUser.IsHellbanned
     79 	displayIgnoredMessages := utils.False()
     80 	msgs, err := db.GetChatMessages(room.ID, roomKey, authUser.Username, authUser.ID, pmUserID, pmOnlyQuery, mentionsOnlyQuery,
     81 		displayHellbanned, authUser.DisplayIgnored, authUser.DisplayModerators, displayIgnoredMessages, 150, 0)
     82 	if err != nil {
     83 		return http.StatusInternalServerError, data
     84 	}
     85 
     86 	// Update read record
     87 	db.UpdateChatReadRecord(authUser.ID, room.ID)
     88 
     89 	data.Error = c.QueryParam("error")
     90 	if data.Error != "" {
     91 		errorDisplayTime := int64(4) // Time in seconds
     92 		nowUnix := time.Now().Unix()
     93 		data.ErrorTs = utils.DoParseInt64(c.QueryParam("errorTs"))
     94 		if nowUnix > data.ErrorTs+errorDisplayTime {
     95 			data.Error = ""
     96 		}
     97 	}
     98 
     99 	// If your tutorial was reset (you are not a new user), force display manual refresh popup
    100 	if ((room.IsOfficialRoom() || (room.IsListed && !room.IsProtected())) && !authUser.TutorialCompleted()) &&
    101 		authUser.GeneralMessagesCount > 0 {
    102 		data.ForceManualRefresh = true
    103 	}
    104 
    105 	data.ManualRefreshTimeout = authUser.RefreshRate + 25
    106 	data.Messages = msgs
    107 	data.ReadMarker, _ = db.GetUserReadMarker(authUser.ID, room.ID)
    108 	data.NbButtons = authUser.CountUIButtons()
    109 	if authUser.NotifyNewMessage || authUser.NotifyPmmed || authUser.NotifyTagged {
    110 		lastKnownDate := time.Now()
    111 		if out, err := db.GetUserLastKnownMessage(authUser.ID, room.ID); err == nil {
    112 			lastKnownDate = out.LastKnownTimestamp
    113 		}
    114 		newMessageSound, pmSound, taggedSound, lastMessageCreatedAt := shouldPlaySound(authUser, lastKnownDate, msgs)
    115 		utils.LogErr(db.CreateUserLastKnownMessage(authUser.ID, room.ID, lastMessageCreatedAt))
    116 		data.NewMessageSound = utils.TernaryOrZero(authUser.NotifyNewMessage, newMessageSound)
    117 		data.PmSound = utils.TernaryOrZero(authUser.NotifyPmmed, pmSound)
    118 		data.TaggedSound = utils.TernaryOrZero(authUser.NotifyTagged, taggedSound)
    119 	}
    120 
    121 	data.ChatMenuData = GetChatMenuData(c, room)
    122 
    123 	return http.StatusOK, data
    124 }
    125 
    126 func writeunesc(iow io.Writer, s string) {
    127 	qw := qt.AcquireWriter(iow)
    128 	streamunesc(qw, s)
    129 	qt.ReleaseWriter(qw)
    130 }
    131 
    132 func unesc(s string) string {
    133 	qb := qt.AcquireByteBuffer()
    134 	writeunesc(qb, s)
    135 	qs := string(qb.B)
    136 	qt.ReleaseByteBuffer(qb)
    137 	return qs
    138 }
    139 
    140 func streamunesc(qw *qt.Writer, s string) {
    141 	qw.N().S(s)
    142 }
    143 
    144 // ChatMessagesHandler room messages iframe handler
    145 // The chat messages iframe use this endpoint to get the messages for a room.
    146 func ChatMessagesHandler(c echo.Context) error {
    147 	authUser := c.Get("authUser").(*database.User)
    148 	status, data := chatMessages(c)
    149 	if status != http.StatusOK {
    150 		return c.NoContent(status)
    151 	}
    152 	if c.QueryParams().Has("json") {
    153 		authUser := c.Get("authUser").(*database.User)
    154 		if authUser.IsHellbanned || (!authUser.IsModerator() && !authUser.CanSeeHellbanned) {
    155 			for i := range data.Messages {
    156 				data.Messages[i].IsHellbanned = false
    157 			}
    158 		}
    159 		return c.JSON(http.StatusOK, data)
    160 	}
    161 	version := config.Global.AppVersion.Get().Original()
    162 	csrf, _ := c.Get("csrf").(string)
    163 	msgs := Messages(version, csrf, config.NullUsername, authUser, data)
    164 	return c.HTML(http.StatusOK, msgs)
    165 	//return c.Render(http.StatusOK, "chat-messages", data)
    166 }
    167 
    168 func RoomNotifierHandler(c echo.Context) error {
    169 	authUser := c.Get("authUser").(*database.User)
    170 	db := c.Get("database").(*database.DkfDB)
    171 	roomName := c.Param("roomName")
    172 	lastKnownDate := c.Request().PostFormValue("last_known_date")
    173 
    174 	room, roomKey, err := dutils.GetRoomAndKey(db, c, roomName)
    175 	if err != nil {
    176 		return c.NoContent(http.StatusForbidden)
    177 	}
    178 
    179 	managers.ActiveUsers.UpdateUserInRoom(room, managers.NewUserInfo(authUser))
    180 
    181 	displayHellbanned := authUser.DisplayHellbanned || authUser.IsHellbanned
    182 	mentionsOnly := utils.False()
    183 	displayIgnoredMessages := utils.False()
    184 	var pmUserID *database.UserID
    185 	msgs, err := db.GetChatMessages(room.ID, roomKey, authUser.Username, authUser.ID, pmUserID, database.PmNoFilter, mentionsOnly,
    186 		displayHellbanned, authUser.DisplayIgnored, authUser.DisplayModerators, displayIgnoredMessages, 150, 0)
    187 	if err != nil {
    188 		return c.NoContent(http.StatusInternalServerError)
    189 	}
    190 
    191 	var data testData
    192 	lastKnownMsgDate, _ := time.Parse(time.RFC3339Nano, lastKnownDate)
    193 	var lastMessageCreatedAt time.Time
    194 	data.NewMessageSound, data.PmSound, data.TaggedSound, lastMessageCreatedAt = shouldPlaySound(authUser, lastKnownMsgDate, msgs)
    195 	data.LastMessageCreatedAt = lastMessageCreatedAt.Format(time.RFC3339Nano)
    196 	data.InboxCount = db.GetUserInboxMessagesCount(authUser.ID)
    197 
    198 	return c.JSON(http.StatusOK, data)
    199 }
    200 
    201 // Given a "lastKnownDate" and a list of messages, returns what sound notification should be played.
    202 func shouldPlaySound(authUser *database.User, lastKnownMsgDate time.Time, msgs []database.ChatMessage) (newMessageSound, pmSound, taggedSound bool, lastMsgCreatedAt time.Time) {
    203 	if len(msgs) > 0 {
    204 		for _, msg := range msgs {
    205 			lastKnownDateTrunc := lastKnownMsgDate.Truncate(time.Second)
    206 			createdAtTrunc := msg.CreatedAt.Truncate(time.Second)
    207 			if createdAtTrunc.After(lastKnownDateTrunc) {
    208 				if msg.User.ID != authUser.ID {
    209 					newMessageSound = true
    210 					if strings.Contains(msg.Message, authUser.Username.AtStr()) {
    211 						taggedSound = true
    212 					}
    213 					if msg.IsPmRecipient(authUser.ID) {
    214 						pmSound = true
    215 					}
    216 					break
    217 				}
    218 			} else if createdAtTrunc.Before(lastKnownMsgDate) {
    219 				break
    220 			}
    221 		}
    222 		lastMsg := msgs[0]
    223 		lastMsgCreatedAt = lastMsg.CreatedAt
    224 	}
    225 	return newMessageSound, pmSound, taggedSound, lastMsgCreatedAt
    226 }
    227 
    228 func UserHellbanHandler(c echo.Context) error {
    229 	authUser := c.Get("authUser").(*database.User)
    230 	db := c.Get("database").(*database.DkfDB)
    231 	userID := dutils.DoParseUserID(c.Param("userID"))
    232 	user, err := db.GetUserByID(userID)
    233 	if err != nil {
    234 		return hutils.RedirectReferer(c)
    235 	}
    236 	if !user.IsHellbanned {
    237 		if authUser.IsAdmin || !user.IsModerator() {
    238 			db.NewAudit(*authUser, fmt.Sprintf("hellban %s #%d", user.Username, user.ID))
    239 			user.HellBan(db)
    240 			managers.ActiveUsers.UpdateUserHBInRooms(managers.NewUserInfo(&user))
    241 		}
    242 	}
    243 	return hutils.RedirectReferer(c)
    244 }
    245 
    246 func UserUnHellbanHandler(c echo.Context) error {
    247 	authUser := c.Get("authUser").(*database.User)
    248 	db := c.Get("database").(*database.DkfDB)
    249 	userID := dutils.DoParseUserID(c.Param("userID"))
    250 	user, err := db.GetUserByID(userID)
    251 	if err != nil {
    252 		return hutils.RedirectReferer(c)
    253 	}
    254 	if user.IsHellbanned {
    255 		db.NewAudit(*authUser, fmt.Sprintf("unhellban %s #%d", user.Username, user.ID))
    256 		user.UnHellBan(db)
    257 		managers.ActiveUsers.UpdateUserHBInRooms(managers.NewUserInfo(&user))
    258 	}
    259 	return hutils.RedirectReferer(c)
    260 }
    261 
    262 func KickHandler(c echo.Context) error {
    263 	authUser := c.Get("authUser").(*database.User)
    264 	db := c.Get("database").(*database.DkfDB)
    265 	userID := dutils.DoParseUserID(c.Param("userID"))
    266 	user, err := db.GetUserByID(userID)
    267 	if err != nil {
    268 		return hutils.RedirectReferer(c)
    269 	}
    270 	if user.IsModerator() {
    271 		return hutils.RedirectReferer(c)
    272 	}
    273 	_ = dutils.SilentKick(db, user, *authUser)
    274 	return hutils.RedirectReferer(c)
    275 }
    276 
    277 func SubscribeHandler(c echo.Context) error {
    278 	authUser := c.Get("authUser").(*database.User)
    279 	db := c.Get("database").(*database.DkfDB)
    280 	roomName := c.Param("roomName")
    281 	room, err := db.GetChatRoomByName(roomName)
    282 	if err != nil {
    283 		return hutils.RedirectReferer(c)
    284 	}
    285 	_ = db.SubscribeToRoom(authUser.ID, room.ID)
    286 	return hutils.RedirectReferer(c)
    287 }
    288 
    289 func UnsubscribeHandler(c echo.Context) error {
    290 	authUser := c.Get("authUser").(*database.User)
    291 	db := c.Get("database").(*database.DkfDB)
    292 	roomName := c.Param("roomName")
    293 	room, err := db.GetChatRoomByName(roomName)
    294 	if err != nil {
    295 		return hutils.RedirectReferer(c)
    296 	}
    297 	_ = db.UnsubscribeFromRoom(authUser.ID, room.ID)
    298 	return hutils.RedirectReferer(c)
    299 }
    300 
    301 func ThreadSubscribeHandler(c echo.Context) error {
    302 	authUser := c.Get("authUser").(*database.User)
    303 	db := c.Get("database").(*database.DkfDB)
    304 	threadUUID := database.ForumThreadUUID(c.Param("threadUUID"))
    305 	thread, err := db.GetForumThreadByUUID(threadUUID)
    306 	if err != nil {
    307 		return hutils.RedirectReferer(c)
    308 	}
    309 	_ = db.SubscribeToForumThread(authUser.ID, thread.ID)
    310 	return hutils.RedirectReferer(c)
    311 }
    312 
    313 func ThreadUnsubscribeHandler(c echo.Context) error {
    314 	authUser := c.Get("authUser").(*database.User)
    315 	db := c.Get("database").(*database.DkfDB)
    316 	threadUUID := database.ForumThreadUUID(c.Param("threadUUID"))
    317 	thread, err := db.GetForumThreadByUUID(threadUUID)
    318 	if err != nil {
    319 		return hutils.RedirectReferer(c)
    320 	}
    321 	_ = db.UnsubscribeFromForumThread(authUser.ID, thread.ID)
    322 	return hutils.RedirectReferer(c)
    323 }
    324 
    325 func ChatMessageReactionHandler(c echo.Context) error {
    326 	authUser := c.Get("authUser").(*database.User)
    327 	db := c.Get("database").(*database.DkfDB)
    328 	messageUUID := c.Request().PostFormValue("message_uuid")
    329 	var msg database.ChatMessage
    330 	if err := db.DB().Where("uuid = ?", messageUUID).Preload("User").Preload("Room").First(&msg).Error; err != nil {
    331 		return err
    332 	}
    333 	reaction := utils.DoParseInt64(c.Request().PostFormValue("reaction_id"))
    334 	if reaction < 0 || reaction > 2 {
    335 		return errors.New("invalid reaction")
    336 	}
    337 
    338 	if err := db.CreateChatReaction(authUser.ID, msg.ID, reaction); err != nil {
    339 		_ = db.DeleteReaction(authUser.ID, msg.ID, reaction)
    340 	}
    341 
    342 	return hutils.RedirectReferer(c)
    343 }
    344 
    345 func ChatDeleteMessageHandler(c echo.Context) error {
    346 	authUser := c.Get("authUser").(*database.User)
    347 	db := c.Get("database").(*database.DkfDB)
    348 
    349 	var returnResp error
    350 	if c.Request().Method == http.MethodGet {
    351 		returnResp = c.NoContent(http.StatusOK)
    352 	} else {
    353 		returnResp = hutils.RedirectReferer(c)
    354 	}
    355 
    356 	messageUUID := c.Param("messageUUID")
    357 	var msg database.ChatMessage
    358 	if err := db.DB().Where("uuid = ?", messageUUID).
    359 		Preload("User").
    360 		Preload("Room").
    361 		First(&msg).Error; err != nil {
    362 		return returnResp
    363 	}
    364 
    365 	if err := msg.UserCanDeleteErr(authUser); err != nil {
    366 		logrus.Error(err)
    367 		return returnResp
    368 	}
    369 
    370 	// Audit when moderator/admin deletes a message he doesn't own
    371 	if authUser.IsModerator() && !msg.OwnMessage(authUser.ID) && msg.User.Username != config.NullUsername {
    372 		auditMsg := fmt.Sprintf(`deleted msg #%d from user "%s" #%d -> %s`,
    373 			msg.ID,
    374 			msg.User.Username,
    375 			msg.User.ID,
    376 			utils.TruncStr(msg.RawMessage, 75, "…"))
    377 		db.NewAudit(*authUser, auditMsg)
    378 	}
    379 
    380 	if msg.OwnMessage(authUser.ID) && msg.RoomID == config.GeneralRoomID && !msg.IsPm() {
    381 		authUser.DecrGeneralMessagesCount(db)
    382 	}
    383 
    384 	if err := msg.Delete(db); err != nil {
    385 		logrus.Error(err)
    386 	}
    387 	return returnResp
    388 }
    389 
    390 func ClubDeleteMessageHandler(c echo.Context) error {
    391 	authUser := c.Get("authUser").(*database.User)
    392 	db := c.Get("database").(*database.DkfDB)
    393 	messageID := database.ForumMessageID(utils.DoParseInt64(c.Param("messageID")))
    394 	msg, err := db.GetForumMessage(messageID)
    395 	if err != nil {
    396 		return hutils.RedirectReferer(c)
    397 	}
    398 
    399 	if authUser.ID != msg.UserID && !authUser.IsAdmin {
    400 		return hutils.RedirectReferer(c)
    401 	}
    402 
    403 	if !msg.CanEdit() && !authUser.IsAdmin {
    404 		return hutils.RedirectReferer(c)
    405 	}
    406 
    407 	if err := db.DeleteForumMessageByID(messageID); err != nil {
    408 		logrus.Error(err)
    409 	}
    410 	return hutils.RedirectReferer(c)
    411 }
    412 
    413 func DeleteNotificationHandler(c echo.Context) error {
    414 	authUser := c.Get("authUser").(*database.User)
    415 	db := c.Get("database").(*database.DkfDB)
    416 	notificationID := utils.DoParseInt64(c.Param("notificationID"))
    417 	var msg database.Notification
    418 	if err := db.DB().Where("ID = ? AND user_id = ?", notificationID, authUser.ID).First(&msg).Error; err != nil {
    419 		logrus.Error(err)
    420 		return hutils.RedirectReferer(c)
    421 	}
    422 	if err := db.DeleteNotificationByID(notificationID); err != nil {
    423 		logrus.Error(err)
    424 	}
    425 	return c.Redirect(http.StatusFound, "/settings/inbox")
    426 }
    427 
    428 func DeleteSessionNotificationHandler(c echo.Context) error {
    429 	db := c.Get("database").(*database.DkfDB)
    430 	authCookie, _ := c.Cookie(hutils.AuthCookieName)
    431 	sessionNotificationID := utils.DoParseInt64(c.Param("sessionNotificationID"))
    432 	var msg database.SessionNotification
    433 	if err := db.DB().Where("ID = ? AND session_token = ?", sessionNotificationID, authCookie.Value).First(&msg).Error; err != nil {
    434 		logrus.Error(err)
    435 		return hutils.RedirectReferer(c)
    436 	}
    437 	if err := db.DeleteSessionNotificationByID(sessionNotificationID); err != nil {
    438 		logrus.Error(err)
    439 	}
    440 	return c.Redirect(http.StatusFound, "/settings/inbox")
    441 }
    442 
    443 func ChatInboxDeleteMessageHandler(c echo.Context) error {
    444 	authUser := c.Get("authUser").(*database.User)
    445 	db := c.Get("database").(*database.DkfDB)
    446 	messageID := utils.DoParseInt64(c.Param("messageID"))
    447 	var msg database.ChatInboxMessage
    448 	if err := db.DB().Where("ID = ? AND to_user_id = ?", messageID, authUser.ID).First(&msg).Error; err != nil {
    449 		logrus.Error(err)
    450 		return c.Redirect(http.StatusFound, "/settings/inbox")
    451 	}
    452 	if err := db.DeleteChatInboxMessageByID(messageID); err != nil {
    453 		logrus.Error(err)
    454 	}
    455 	return c.Redirect(http.StatusFound, "/settings/inbox")
    456 }
    457 
    458 func ChatInboxDeleteAllMessageHandler(c echo.Context) error {
    459 	authCookie, _ := c.Cookie(hutils.AuthCookieName)
    460 	authUser := c.Get("authUser").(*database.User)
    461 	db := c.Get("database").(*database.DkfDB)
    462 	if err := db.DeleteAllChatInbox(authUser.ID); err != nil {
    463 		logrus.Error(err)
    464 	}
    465 	if err := db.DeleteAllNotifications(authUser.ID); err != nil {
    466 		logrus.Error(err)
    467 	}
    468 	if err := db.DeleteAllSessionNotifications(authCookie.Value); err != nil {
    469 		logrus.Error(err)
    470 	}
    471 	return c.Redirect(http.StatusFound, "/settings/inbox")
    472 }
    473 
    474 func GetCaptchaHandler(c echo.Context) error {
    475 	//authUser := c.Get("authUser").(*database.User)
    476 	captchaID, captchaImg := mycaptcha.New()
    477 	return c.JSON(http.StatusOK, map[string]any{"ID": captchaID, "img": captchaImg})
    478 }
    479 
    480 func CaptchaSolverHandler(c echo.Context) error {
    481 	authUser := c.Get("authUser").(*database.User)
    482 	db := c.Get("database").(*database.DkfDB)
    483 	captchaB64 := c.Request().PostFormValue("captcha")
    484 	answer, err := captcha.SolveBase64(captchaB64)
    485 	if err != nil {
    486 		logrus.Error(err.Error())
    487 		return c.NoContent(http.StatusInternalServerError)
    488 	}
    489 	captchaReq := database.CaptchaRequest{
    490 		UserID:     authUser.ID,
    491 		CaptchaImg: captchaB64,
    492 		Answer:     answer,
    493 	}
    494 	if err := db.DB().Create(&captchaReq).Error; err != nil {
    495 		logrus.Error(err.Error())
    496 	}
    497 	return c.JSON(http.StatusOK, map[string]any{"answer": answer})
    498 }
    499 
    500 func WerewolfHandler(c echo.Context) error {
    501 	db := c.Get("database").(*database.DkfDB)
    502 	roomName := "werewolf"
    503 	origMessage := c.Request().PostFormValue("message")
    504 	redirectURL := "/api/v1/chat/messages/" + roomName
    505 	room, roomKey, err := dutils.GetRoomAndKey(db, c, roomName)
    506 	if err != nil {
    507 		return c.Redirect(http.StatusFound, redirectURL+"?error="+err.Error()+"&errorTs="+utils.FormatInt64(time.Now().Unix()))
    508 	}
    509 	cmd := command.NewCommand(c, origMessage, room, roomKey)
    510 	interceptors.WWInstance.InterceptMsg(cmd)
    511 	if cmd.Err != nil {
    512 		return c.Redirect(http.StatusFound, redirectURL+"?error="+cmd.Err.Error()+"&errorTs="+utils.FormatInt64(time.Now().Unix()))
    513 	}
    514 	return c.Redirect(http.StatusFound, redirectURL)
    515 }
    516 
    517 func BattleshipHandler(c echo.Context) error {
    518 	authUser := c.Get("authUser").(*database.User)
    519 	db := c.Get("database").(*database.DkfDB)
    520 	roomName := c.Request().PostFormValue("room")
    521 	enemyUsername := database.Username(c.Request().PostFormValue("enemyUsername"))
    522 	pos := c.Request().PostFormValue("move")
    523 	redirectURL := "/api/v1/chat/messages/" + roomName
    524 	room, roomKey, err := dutils.GetRoomAndKey(db, c, roomName)
    525 	if err != nil {
    526 		return c.Redirect(http.StatusFound, redirectURL+"?error="+err.Error()+"&errorTs="+utils.FormatInt64(time.Now().Unix()))
    527 	}
    528 	if err = interceptors.BattleshipInstance.PlayMove(roomName, room.ID, roomKey, *authUser, enemyUsername, pos); err != nil {
    529 		return c.Redirect(http.StatusFound, redirectURL+"?error="+err.Error()+"&errorTs="+utils.FormatInt64(time.Now().Unix()))
    530 	}
    531 	return c.Redirect(http.StatusFound, redirectURL)
    532 }