chirpnest/def.go

530 lines
16 KiB
Go
Raw Normal View History

2019-05-06 04:54:24 +02:00
package main
2019-05-09 11:19:55 +02:00
import (
"regexp"
"sync"
2019-05-09 12:36:45 +02:00
"time"
2019-05-17 10:09:09 +02:00
"github.com/streadway/amqp"
tb "gopkg.in/tucnak/telebot.v2"
2019-05-09 11:19:55 +02:00
)
2019-08-29 12:49:01 +02:00
const maxUnixTimestamp int64 = 2147483647
2019-08-23 14:09:08 +02:00
2019-06-08 17:28:07 +02:00
type DataBackup struct {
2019-06-08 17:59:34 +02:00
Messages []ChatWarsMessage `json:"messages"`
2019-06-08 17:28:07 +02:00
}
type MQClient struct {
2019-06-28 09:58:00 +02:00
User string
Password string
Host string
Path string
SSL bool
Connection *amqp.Connection
Channel *amqp.Channel
Queue amqp.Queue
2019-05-17 10:09:09 +02:00
}
2019-07-31 03:24:01 +02:00
type ChirpClient struct {
HeartBeat time.Time `json:"heart_beat"`
2019-07-31 09:22:02 +02:00
Login string `json:"nickname"`
2019-07-31 03:24:01 +02:00
Active bool
TGUserID64 int64 `json:"tg_user_id"`
MQ MQClient `json:"mq_client"`
CWUserID64 int64 `json:"user_id"`
CWGuildID64 int64 `json:"guild_id"`
CWRole string `json:"role"`
CWState string `json:"state"`
CWBusyUntil time.Time `json:"busy_until"`
CWLastUpdate time.Time `json:"last_update"`
Mux sync.Mutex
}
type MQKeepAlive struct {
TGUserID64 int64 `json:"tg_user_id"`
Nickname string `json:"nick"`
Queue string `json:"queue"`
Date time.Time `json:"date"`
2019-05-27 12:03:18 +02:00
}
2019-05-16 04:16:35 +02:00
type TGCommand struct {
2019-08-24 08:01:26 +02:00
Type int64 `json:"type"`
FromChatID64 int64 `json:"from_chat_id"`
FromUserID64 int64 `json:"from_user_id"`
FromMsgID64 int64 `json:"from_msg_id"`
ToChatID64 int64 `json:"to_chat_id"`
ToUserID64 int64 `json:"to_user_id"`
Text string `json:"text"`
Document tb.Document `json:"document"`
ParseMode int64 `json:"parse_mode"`
Delay time.Duration `json:"delay"`
2019-05-15 10:58:25 +02:00
}
2019-05-25 09:25:11 +02:00
type ChatWarsCastle struct {
ObjID64 int64 `json:"obj_id"`
Logo string `json:"logo"`
Name string `json:"name"`
}
type ChatWarsGuild struct {
ObjID64 int64 `json:"obj_id"`
Tag string `json:"tag"`
Name string `json:"name"`
}
2019-05-25 10:56:13 +02:00
type ChatWarsUser struct {
ObjID64 int64 `json:"obj_id"`
Name string `json:"name"`
}
2019-06-02 08:48:16 +02:00
type ChatWarsItem struct {
ObjID64 int64 `json:"obj_id"`
ItemTypeID int64 `json:"item_type_id"`
Code string `json:"code"`
Name string `json:"name"`
Weight int `json:"weight"`
2019-08-21 05:46:42 +02:00
Exchange bool `json:"exchange"`
Auction bool `json:"auction"`
2019-06-02 08:48:16 +02:00
}
2019-06-02 11:30:04 +02:00
type ChatWarsItems struct {
ItemID64 int64 `json:"item_id"`
Quantity int64 `json:"quantity"`
}
2019-05-06 04:54:24 +02:00
type ChatWarsMessage struct {
2019-07-31 10:53:09 +02:00
ObjID64 int64 `json:"obj_id"`
TGUserID64 int64 `json:"tg_user_id"`
TGSenderUserID64 int64 `json:"tg_sender_user_id"`
Date time.Time `json:"date"`
ID64 int64 `json:"id"`
ChatID64 int64 `json:"chat_id"`
Text string `json:"text"`
2019-05-06 04:54:24 +02:00
}
2019-05-06 05:59:19 +02:00
2019-08-22 11:31:16 +02:00
type ChatWarsMessageGStock struct {
2019-06-02 11:30:04 +02:00
Msg *ChatWarsMessage `json:"msg"`
OwnerID64 int64 `json:"owner_id"`
ItemList []ChatWarsItems `json:"item_list"`
}
2019-08-30 04:16:48 +02:00
type ChatWarsMessageGDepositReq struct {
Msg *ChatWarsMessage `json:"msg"`
ItemID64 int64 `json:"item_id"`
Quantity int64 `json:"quantity"`
}
2019-08-27 17:10:57 +02:00
type ChatWarsMessageGDepositAck struct {
Msg *ChatWarsMessage `json:"msg"`
ItemID64 int64 `json:"item_id"`
Quantity int64 `json:"quantity"`
}
2019-05-19 06:19:24 +02:00
type ChatWarsMessageReportAck struct {
2019-05-18 09:44:25 +02:00
ObjID64 int64 `json:"obj_id"`
2019-05-28 06:14:46 +02:00
}
2019-08-22 11:31:16 +02:00
type ChatWarsMessageStockAck struct {
Msg *ChatWarsMessage `json:"msg"`
Used int64 `json:"used"`
Available int64 `json:"available"`
Stock []ChatWarsItems `json:"stock"`
}
2019-08-30 09:11:49 +02:00
type ChatWarsMessageStockAnyAck struct {
Msg *ChatWarsMessage `json:"msg"`
Stock []ChatWarsItems `json:"stock"`
}
2019-08-21 08:37:53 +02:00
type ChatWarsMessageOrderbookAck struct {
Msg *ChatWarsMessage `json:"msg"`
Name string `json:"name"`
Code string `json:"code"`
Qty1 int64 `json:"qty1"`
Price1 int64 `json:"price1"`
Qty2 int64 `json:"qty2"`
Price2 int64 `json:"price2"`
Qty3 int64 `json:"qty3"`
Price3 int64 `json:"price3"`
Qty int64 `json:"qty"`
Gold int64 `json:"gold"`
}
2019-05-28 06:14:46 +02:00
type ChatWarsMessageMeAck struct {
2019-07-31 10:57:45 +02:00
Msg *ChatWarsMessage `json:"msg"`
CWUserID64 int64 `json:"user_id"`
CWGuildID64 int64 `json:"guild_id"`
State string `json:"state"`
Level int64 `json:"level"`
ExpNow int64 `json:"exp_now"`
ExpLvl int64 `json:"exp_lvl"`
2019-05-18 09:44:25 +02:00
}
2019-05-30 10:38:25 +02:00
type ChatWarsMessageGRolesAck struct {
2019-05-30 10:36:40 +02:00
Msg *ChatWarsMessage `json:"msg"`
BartenderID64 int64 `json:"bartender"`
CommanderID64 int64 `json:"commander"`
SquireID64 int64 `json:"squire"`
TreasurerID64 int64 `json:"treasurer"`
}
2019-05-28 09:00:37 +02:00
type ChatWarsMessageGoQuestAck struct {
2019-07-09 06:56:41 +02:00
Msg *ChatWarsMessage `json:"msg"`
QuestTypeID int `json:"quest"`
Duration time.Duration `json:"duration"`
2019-05-28 09:00:37 +02:00
}
2019-05-18 16:15:24 +02:00
type ChatWarsMessageDuelFight struct {
2019-05-19 14:01:32 +02:00
ObjID64 int64 `json:"obj_id"`
WinCastle string `json:"win_castle"`
WinGuild string `json:"win_guild"`
WinUser string `json:"win_user"`
WinLife int64 `json:"win_life"`
LossCastle string `json:"loss_castle"`
LossGuild string `json:"loss_guild"`
LossUser string `json:"loss_user"`
LossLife int64 `json:"loss_life"`
Exp int64 `json:"exp"`
Weapon string `json:"weapon"`
2019-05-18 16:15:24 +02:00
}
2019-05-09 11:19:02 +02:00
type ChatWarsMessageAuctionAnnounce struct {
2019-06-06 07:52:22 +02:00
ObjID64 int64 `json:"obj_id"`
LotID int32 `json:"lot_id"`
ItemID64 int64 `json:"item_id"`
Cond string `json:"cond"`
Quality string `json:"quality"`
2019-06-06 07:54:58 +02:00
SellerUserID64 int64 `json:"seller_id"`
2019-06-06 07:52:22 +02:00
SellerGuildID64 int64 `json:"seller_guild_id"`
SellerCastleID64 int64 `json:"seller_castle_id"`
2019-06-06 07:54:58 +02:00
BuyerUserID64 int64 `json:"buyer_id"`
2019-06-06 07:52:22 +02:00
BuyerGuildID64 int64 `json:"buyer_guild_id"`
BuyerCastleID64 int64 `json:"buyer_castle_id"`
Price int32 `json:"price"`
Status string `json:"status"`
End time.Time `json:"end"`
2019-05-09 11:19:02 +02:00
}
2019-05-15 12:02:17 +02:00
type ChatWarsMessagePillageInc struct {
ObjID64 int64 `json:"obj_id"`
Attacker string `json:"attacker"`
Guild string `json:"guild"`
Castle string `json:"castle"`
}
2019-05-11 12:45:05 +02:00
type ChatWarsMessageMiniWar struct {
2019-05-14 04:28:26 +02:00
ObjID64 int64 `json:"obj_id"`
Report map[string]*ChatWarsMessageMiniWarCastle `json:"castle"`
Time time.Time `json:"time"`
2019-05-11 12:45:05 +02:00
}
type ChatWarsMessageMiniWarCastle struct {
2019-05-11 13:22:50 +02:00
Gardian string `json:"gardian"`
Result string `json:"result"`
2019-05-13 08:12:31 +02:00
Gold int64 `json:"gold"`
Stock int64 `json:"stock"`
Points int64 `json:"points"`
2019-05-11 12:45:05 +02:00
}
2019-05-25 13:12:31 +02:00
type ChatWarsMessageUnionWar struct {
}
2019-05-07 13:15:25 +02:00
type MessageParsingRule struct {
2019-05-30 06:12:01 +02:00
ID int32
Priority int32
Description string
Rule string
2019-06-02 08:52:35 +02:00
MsgTypeID int64
2019-05-30 06:12:01 +02:00
ChatID64 int64
SenderUserID64 int64
re *regexp.Regexp
2019-05-07 13:15:25 +02:00
}
2019-05-11 05:15:42 +02:00
type BotMsg struct {
To tb.Recipient
Text string
}
2019-05-10 11:23:47 +02:00
type Job struct {
ID64 int64
JobTypeID int32
2019-08-21 05:54:01 +02:00
Trigger int64
2019-08-23 14:09:08 +02:00
Timeout time.Time
2019-05-15 12:31:03 +02:00
UserID64 int64
2019-05-10 11:23:47 +02:00
Payload []byte
}
2019-06-08 17:28:07 +02:00
type JobPayloadMsgRefresh struct {
ObjID64 int64 `json:"obj_id"`
}
2019-05-10 11:23:47 +02:00
type JobPayloadPillage struct {
2019-07-31 04:06:05 +02:00
ObjID64 int64 `json:"obj_id"`
Date time.Time `json:"date"`
2019-05-10 11:23:47 +02:00
}
type JobPayloadTribute struct {
}
type JobPayloadStatus struct {
}
type JobPayloadWithdrawal struct {
}
type JobPayloadGStock struct {
2019-08-08 14:39:23 +02:00
MsgID64 int64 `json:"msg_id"`
ChatID64 int64 `json:"chat_id"`
2019-05-10 11:23:47 +02:00
}
2019-08-16 12:29:07 +02:00
type JobPayloadGDeposit struct {
2019-08-17 08:18:25 +02:00
MsgID64 int64 `json:"msg_id"`
ChatID64 int64 `json:"chat_id"`
ResObjID64 []int64 `json:"res_obj_id"`
2019-08-21 05:46:42 +02:00
Status int `json:"status"`
2019-08-17 08:18:25 +02:00
}
2019-08-30 04:16:12 +02:00
type JobPayloadGDepositForward struct {
ItemID64 int64 `json:"item_id"`
Quantity int64 `json:"quantity"`
}
2019-08-17 08:18:25 +02:00
type JobPayloadSaveRes struct {
MsgID64 int64 `json:"msg_id"`
ChatID64 int64 `json:"chat_id"`
ResObjID64 int64 `json:"res_obj_id"`
BuyRes bool `json:"buy_res"`
2019-08-16 12:29:07 +02:00
}
2019-05-10 11:23:47 +02:00
type JobPayloadRescanMsg struct {
2019-05-15 12:31:03 +02:00
Query string `json:"query"`
MsgID64 int64 `json:"msg_id"`
ChatID64 int64 `json:"chat_id"`
2019-05-10 13:33:29 +02:00
}
2019-05-11 06:54:12 +02:00
type JobPayloadSetDone struct {
2019-05-16 04:49:34 +02:00
JobID64 int64 `json:"job_id"`
MsgID64 int64 `json:"msg_id"`
ChatID64 int64 `json:"chat_id"`
Text string `json:"test"`
2019-05-11 06:54:12 +02:00
}
2019-05-30 06:12:01 +02:00
type JobPayloadMsgClient struct {
2019-06-03 03:01:18 +02:00
Text string `json:"msg"`
MsgID64 int64 `json:"msg_id"`
2019-06-11 04:00:37 +02:00
ChatID64 int64 `json:"chat_id"`
}
type JobPayloadBackupExport struct {
MsgID64 int64 `json:"msg_id"`
ChatID64 int64 `json:"chat_id"`
}
type JobPayloadBackupImport struct {
URL string `json:"url"`
MsgID64 int64 `json:"msg_id"`
2019-06-03 03:01:18 +02:00
ChatID64 int64 `json:"chat_id"`
2019-05-30 06:12:01 +02:00
}
2019-05-06 05:59:19 +02:00
const (
2019-05-17 10:30:01 +02:00
userID64ChtWrsBot = 408101137
2019-06-09 09:34:08 +02:00
commandForwardMsg = 1
commandReplyMsg = 2
commandSendMsg = 3
commandDeleteMsg = 4
commandRefreshMsg = 5
commandSendDocument = 6
2019-05-15 10:58:25 +02:00
2019-06-10 11:05:59 +02:00
cmdParseModePlain = 1
cmdParseModeMarkDown = 2
cmdParseModeHTML = 3
2019-05-31 04:02:21 +02:00
objTypeUser = 1
objTypeGuild = 2
objTypeMessage = 3
objTypeWar = 4
objTypeWarReport = 5
objTypeJob = 6
objTypeItem = 7
objTypeCastle = 8
objTypeFair = 9
objTypeUnion = 10
objTypeTribute = 11
objTypeExperience = 12
2019-07-09 06:56:41 +02:00
objTypeQuest = 13
2019-05-06 05:59:19 +02:00
2019-05-14 04:28:26 +02:00
castleDeer = 1
castleDragon = 2
castleHighnest = 3
castleMoon = 4
castlePotato = 5
castleShark = 6
castleWolf = 7
2019-05-25 09:25:11 +02:00
objSubTypeUser = 101
objSubTypeGuild = 201
2019-05-08 16:34:47 +02:00
objSubTypeMessageUnknown = 301
2019-05-19 06:05:04 +02:00
objSubTypeMessageWar = 302 // from Chat Wars Reports (not done)
objSubTypeMessageMiniWar = 303 // from Chat Wars Mini Reports (done)
objSubTypeMessageGuildWar = 304 // from Chat Wars Reports (not done)
objSubTypeMessageReportReq = 305 // /report (done)
2019-05-19 06:19:24 +02:00
objSubTypeMessageReportAck = 306 // result from /report (done)
2019-05-19 06:05:04 +02:00
objSubTypeMessageGReportReq = 307 // /g_report (done)
objSubTypeMessageGReportAck = 308 // result from /g_report (not done)
objSubTypeMessageQuestResult = 309 // result from going to quest (not done)
objSubTypeMessageDuelFight = 310 // result from going to duel fight (done)
objSubTypeMessageHeroReq = 311 // /hero (done)
objSubTypeMessageHeroAck = 312 // result from /hero (not done)
objSubTypeMessageMeReq = 313 // 🏅Me (done)
2019-05-28 09:00:37 +02:00
objSubTypeMessageMeAck = 314 // result from 🏅Me (done)
2019-05-19 06:05:04 +02:00
objSubTypeMessageInventoryReq = 315 // /inv (done)
objSubTypeMessageInventoryAck = 316 // result from /inv (not done)
objSubTypeMessagePillageInc = 317 // random incoming pillage (done)
objSubTypeMessagePillageGo = 318 // ack from /go (done)
objSubTypeMessagePillageTimeout = 319 // ack from lack of /go (done)
objSubTypeMessagePillageWin = 320 // pillage successfully intercepted (done)
objSubTypeMessagePillageLoss = 321 // pillage not intercepted (done)
objSubTypeMessageTributeInc = 322 // request for a /pledge (not done)
objSubTypeMessageTributeAck = 323 // pledge accepted (not done)
objSubTypeMessageAuctionAnnounce = 324 // from Boris and Co, Ltd (done)
objSubTypeMessageAuctionUpdReq = 325 // /l_123456 msg (not done)
objSubTypeMessageAuctionUpdAck = 326 // result from /l_123456 (not done)
objSubTypeMessageTimeAck = 327 // result from /time (done)
objSubTypeMessageTimeReq = 328 // /time (done)
objSubTypeMessageGo = 329 // /go (done)
objSubTypeMessagePledge = 330 // /pledge (done)
objSubTypeMessageGoQuest = 331 // 🌲Forest or 🍄Swamp or ⛰Valley (not done)
objSubTypeMessageGoFastFight = 332 // ▶Fast fight (not done)
objSubTypeMessageGoArena = 333 // 📯Arena (not done)
2019-05-19 06:13:33 +02:00
objSubTypeMessageTop = 334 // any /topXX (not done)
objSubTypeMessageMenu = 335 // main menu (not done)
2019-05-19 08:35:26 +02:00
objSubTypeMessageBuyReq = 336 // /wtb_xx (done)
objSubTypeMessageSellReq = 337 // /wts_xx (done)
objSubTypeMessageOrderbookReq = 338 // /t_xx (done)
2019-05-19 14:00:18 +02:00
objSubTypeMessageOrderbookAck = 339 // orderbook summary (not done)
2019-05-20 16:16:49 +02:00
objSubTypeMessageWithdrawReq = 340 // /g_withdraw (done)
objSubTypeMessageWithdrawCode = 341 // code to receive (done)
2019-05-28 09:00:37 +02:00
objSubTypeMessageWithdrawRcv = 342 // Withdraw "received" msg (done)
2019-08-29 13:37:52 +02:00
objSubTypeMessageStockReq = 343 // /stock (done)
objSubTypeMessageStockAck = 344 // result from /stock (not done)
objSubTypeMessageMiscReq = 345 // /misc (done)
objSubTypeMessageMiscAck = 346 // result from /misc (not done)
objSubTypeMessageUnionWar = 347 // from Chat Wars Reports (not done)
objSubTypeMessageTUReportReq = 348 // /tu_report (not done)
objSubTypeMessageTUReportAck = 349 // result from /tu_report (not done)
objSubTypeMessageTimeout = 350 // generic timeout for action
objSubTypeMessageGoQuestAck = 351 // confirm quest destination/busyness (done)
objSubTypeMessageGRolesReq = 352 // /g_roles (done)
objSubTypeMessageGRolesAck = 353 // result from /g_roles (done)
objSubTypeMessageGStockResReq = 354 // /g_stock_res (done)
objSubTypeMessageGStockAlchReq = 355 // /g_stock_alch (done)
objSubTypeMessageGStockMiscReq = 356 // /g_stock_misc (done)
objSubTypeMessageGStockRecReq = 357 // /g_stock_rec (done)
objSubTypeMessageGStockPartReq = 358 // /g_stock_parts (done)
objSubTypeMessageGStockOthReq = 359 // /g_stock_other (done)
objSubTypeMessageGStockAnyAck = 360 // result from any /g_stock_xxx (done)
objSubTypeMessageGStockReq = 361 // /g_stock (done)
objSubTypeMessageGStockAck = 362 // result from /g_stock (done)
objSubTypeMessageBusy = 363 // too busy now
objSubTypeMessageResStockReq = 364 // 📦Resources or /stock (done)
objSubTypeMessageAlchStockReq = 365 // ⚗Alchemy (done)
objSubTypeMessageMiscStockReq = 366 // 🗃Misc or /misc (done)
objSubTypeMessageEquipStockReq = 367 // 🏷Equipment (done)
objSubTypeMessageCraftStockReq = 368 // ⚒Crafting (done)
objSubTypeMessageStockEmpty = 369 // [empty] (done)
objSubTypeMessageStockAnyAck = 370 // list of stock (done)
objSubTypeMessageGDepositReq = 371 // /g_deposit xx y (done)
objSubTypeMessageGDepositAck = 372 // Deposited successfully: xx (y) (done)
2019-05-08 16:34:47 +02:00
objSubTypeJobPillage = 601
objSubTypeJobTribute = 602
objSubTypeJobStatus = 603
objSubTypeJobWithdrawal = 604
objSubTypeJobGStock = 605
2019-05-10 11:23:47 +02:00
objSubTypeJobRescanMsg = 606
2019-05-19 05:20:01 +02:00
objSubTypeJobSetJobDone = 607
2019-05-26 15:06:12 +02:00
objSubTypeJobMsgClient = 608
2019-06-08 17:28:07 +02:00
objSubTypeJobMsgRefresh = 609
2019-06-11 03:59:20 +02:00
objSubTypeJobBackupExport = 610
objSubTypeJobBackupImport = 611
2019-08-16 12:29:07 +02:00
objSubTypeJobGDeposit = 612
2019-08-27 17:10:57 +02:00
objSubTypeJobGDepositForward = 613
objSubTypeJobSaveRes = 614
2019-05-09 09:17:59 +02:00
objSubTypeItemResource = 701
objSubTypeItemAlch = 702
objSubTypeItemMisc = 703
objSubTypeItemRecipe = 704
objSubTypeItemPart = 705
objSubTypeItemOther = 706
2019-06-02 08:48:16 +02:00
objSubTypeItemUnique = 707
2019-05-25 09:25:11 +02:00
objSubTypeCastle = 801
2019-05-25 09:37:45 +02:00
objSubTypeFair = 901
objSubTypeUnion = 1001
2019-05-31 04:02:21 +02:00
objSubTypeTribute = 1101
objSubTypeExperience = 1201
2019-07-09 06:56:41 +02:00
objSubTypeQuestForest = 1301
objSubTypeQuestSwamp = 1302
objSubTypeQuestValley = 1303
2019-05-06 09:25:57 +02:00
2019-08-17 08:18:25 +02:00
objJobStatusCallBack = -1
2019-05-18 12:33:53 +02:00
objJobStatusNew = 0
objJobStatusPillageGo = 1
objJonStatusPending = 10
objJobStatusDone = 20
2019-05-10 11:23:47 +02:00
2019-05-19 05:20:01 +02:00
objJobPriority = 1
objJobPriorityRescanMsg = 2
objJobPriorityRescanAllMsg = 3
2019-06-11 16:50:01 +02:00
objJobPriorityBackup = 4
2019-05-11 06:54:12 +02:00
2019-06-14 05:24:43 +02:00
MQGetMsgWorkers = 12
MQCWMsgQueueSize = 100
SQLCWMsgWorkers = 6
SQLIdentifyMsgWorkers = 6
SQLMsgIdentifyQueueSize = 100
SQLMsgRescanJobSize = 25
JobWorkers = 12
JobQueueSize = 100
TGCmdWorkers = 3
TGCmdQueueSize = 100
MQTGCmdWorkers = 3
MQTGCmdQueueSize = 100
SQLJobSliceSize = 25
KeepAliveHeartBeatSeconds = 5
2019-05-06 05:59:19 +02:00
)
2019-05-08 17:18:17 +02:00
2019-05-09 04:27:21 +02:00
var (
chatWarsMonth = map[string]int{
"Wintar": 1,
"Hornung": 2,
"Lenzin": 3,
"Ōstar": 4,
"Winni": 5,
"Brāh": 6,
"Hewi": 7,
"Aran": 8,
"Witu": 9,
"Wīndume": 10,
"Herbist": 11,
"Hailag": 12}
2019-05-09 04:28:06 +02:00
chatWarsDaysSpecial = map[int]map[int]int{
2: {1060: 29},
4: {1060: 29}}
2019-05-09 04:27:21 +02:00
chatWarsDays = map[int]int{
1: 31,
2: 28,
3: 31,
4: 30,
5: 31,
6: 30,
7: 31,
8: 31,
9: 30,
10: 31,
11: 30,
12: 31}
)