555 lines
17 KiB
Go
555 lines
17 KiB
Go
package main
|
||
|
||
import (
|
||
"regexp"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/streadway/amqp"
|
||
tb "gopkg.in/tucnak/telebot.v2"
|
||
)
|
||
|
||
const maxUnixTimestamp int64 = 2147483647
|
||
|
||
type DataBackup struct {
|
||
Messages []ChatWarsMessage `json:"messages"`
|
||
}
|
||
|
||
type MQClient struct {
|
||
User string
|
||
Password string
|
||
Host string
|
||
Path string
|
||
SSL bool
|
||
Connection *amqp.Connection
|
||
Channel *amqp.Channel
|
||
Queue amqp.Queue
|
||
}
|
||
|
||
type ChirpClient struct {
|
||
HeartBeat time.Time `json:"heart_beat"`
|
||
Login string `json:"nickname"`
|
||
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"`
|
||
}
|
||
|
||
type TGCommand struct {
|
||
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"`
|
||
}
|
||
|
||
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"`
|
||
}
|
||
|
||
type ChatWarsUser struct {
|
||
ObjID64 int64 `json:"obj_id"`
|
||
Name string `json:"name"`
|
||
}
|
||
|
||
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"`
|
||
Exchange bool `json:"exchange"`
|
||
Auction bool `json:"auction"`
|
||
}
|
||
|
||
type ChatWarsItems struct {
|
||
ItemID64 int64 `json:"item_id"`
|
||
Quantity int64 `json:"quantity"`
|
||
}
|
||
|
||
type ChatWarsMessage struct {
|
||
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"`
|
||
}
|
||
|
||
type ChatWarsExchangeDeal struct {
|
||
ItemID64 int64 `json:"item_id"`
|
||
Quantity int64 `json:"quantity"`
|
||
Price int64 `json:"price"`
|
||
Link string `json:"link"`
|
||
}
|
||
|
||
type ChatWarsMessageExchangeAck struct {
|
||
Msg *ChatWarsMessage `json:"msg"`
|
||
ActiveDeals int64 `json:"active_deals"`
|
||
MaxDeals int64 `json:"max_deals"`
|
||
DealList []ChatWarsExchangeDeal `json:"deals"`
|
||
}
|
||
|
||
type ChatWarsMessageGStock struct {
|
||
Msg *ChatWarsMessage `json:"msg"`
|
||
OwnerID64 int64 `json:"owner_id"`
|
||
ItemList []ChatWarsItems `json:"item_list"`
|
||
}
|
||
|
||
type ChatWarsMessageGDepositReq struct {
|
||
Msg *ChatWarsMessage `json:"msg"`
|
||
ItemID64 int64 `json:"item_id"`
|
||
Quantity int64 `json:"quantity"`
|
||
}
|
||
|
||
type ChatWarsMessageGDepositAck struct {
|
||
Msg *ChatWarsMessage `json:"msg"`
|
||
ItemID64 int64 `json:"item_id"`
|
||
Quantity int64 `json:"quantity"`
|
||
}
|
||
|
||
type ChatWarsMessageReportAck struct {
|
||
ObjID64 int64 `json:"obj_id"`
|
||
}
|
||
|
||
type ChatWarsMessageStockAck struct {
|
||
Msg *ChatWarsMessage `json:"msg"`
|
||
Used int64 `json:"used"`
|
||
Available int64 `json:"available"`
|
||
Stock []ChatWarsItems `json:"stock"`
|
||
}
|
||
|
||
type ChatWarsMessageStockAnyAck struct {
|
||
Msg *ChatWarsMessage `json:"msg"`
|
||
Stock []ChatWarsItems `json:"stock"`
|
||
}
|
||
|
||
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"`
|
||
}
|
||
|
||
type ChatWarsMessageMeAck struct {
|
||
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"`
|
||
}
|
||
|
||
type ChatWarsMessageGRolesAck struct {
|
||
Msg *ChatWarsMessage `json:"msg"`
|
||
BartenderID64 int64 `json:"bartender"`
|
||
CommanderID64 int64 `json:"commander"`
|
||
SquireID64 int64 `json:"squire"`
|
||
TreasurerID64 int64 `json:"treasurer"`
|
||
}
|
||
|
||
type ChatWarsMessageGoQuestAck struct {
|
||
Msg *ChatWarsMessage `json:"msg"`
|
||
QuestTypeID int `json:"quest"`
|
||
Duration time.Duration `json:"duration"`
|
||
}
|
||
|
||
type ChatWarsMessageDuelFight struct {
|
||
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"`
|
||
}
|
||
|
||
type ChatWarsMessageAuctionAnnounce struct {
|
||
ObjID64 int64 `json:"obj_id"`
|
||
LotID int32 `json:"lot_id"`
|
||
ItemID64 int64 `json:"item_id"`
|
||
Cond string `json:"cond"`
|
||
Quality string `json:"quality"`
|
||
SellerUserID64 int64 `json:"seller_id"`
|
||
SellerGuildID64 int64 `json:"seller_guild_id"`
|
||
SellerCastleID64 int64 `json:"seller_castle_id"`
|
||
BuyerUserID64 int64 `json:"buyer_id"`
|
||
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"`
|
||
}
|
||
|
||
type ChatWarsMessagePillageInc struct {
|
||
ObjID64 int64 `json:"obj_id"`
|
||
Attacker string `json:"attacker"`
|
||
Guild string `json:"guild"`
|
||
Castle string `json:"castle"`
|
||
}
|
||
|
||
type ChatWarsMessageMiniWar struct {
|
||
ObjID64 int64 `json:"obj_id"`
|
||
Report map[string]*ChatWarsMessageMiniWarCastle `json:"castle"`
|
||
Time time.Time `json:"time"`
|
||
}
|
||
|
||
type ChatWarsMessageMiniWarCastle struct {
|
||
Gardian string `json:"gardian"`
|
||
Result string `json:"result"`
|
||
Gold int64 `json:"gold"`
|
||
Stock int64 `json:"stock"`
|
||
Points int64 `json:"points"`
|
||
}
|
||
|
||
type ChatWarsMessageUnionWar struct {
|
||
}
|
||
|
||
type MessageParsingRule struct {
|
||
ID int32
|
||
Priority int32
|
||
Description string
|
||
Rule string
|
||
MsgTypeID int64
|
||
ChatID64 int64
|
||
SenderUserID64 int64
|
||
re *regexp.Regexp
|
||
}
|
||
|
||
type BotMsg struct {
|
||
To tb.Recipient
|
||
Text string
|
||
}
|
||
|
||
type Job struct {
|
||
ID64 int64
|
||
JobTypeID int32
|
||
Trigger int64
|
||
Timeout time.Time
|
||
UserID64 int64
|
||
Payload []byte
|
||
}
|
||
|
||
type JobPayloadMsgRefresh struct {
|
||
ObjID64 int64 `json:"obj_id"`
|
||
}
|
||
|
||
type JobPayloadPillage struct {
|
||
ObjID64 int64 `json:"obj_id"`
|
||
Date time.Time `json:"date"`
|
||
}
|
||
|
||
type JobPayloadTribute struct {
|
||
}
|
||
|
||
type JobPayloadStatus struct {
|
||
}
|
||
|
||
type JobPayloadWithdrawal struct {
|
||
}
|
||
|
||
type JobPayloadGStock struct {
|
||
MsgID64 int64 `json:"msg_id"`
|
||
ChatID64 int64 `json:"chat_id"`
|
||
}
|
||
|
||
type JobPayloadGDeposit struct {
|
||
MsgID64 int64 `json:"msg_id"`
|
||
ChatID64 int64 `json:"chat_id"`
|
||
ResObjID64 []int64 `json:"res_obj_id"`
|
||
Status int `json:"status"`
|
||
}
|
||
|
||
type JobPayloadGDepositForward struct {
|
||
ItemID64 int64 `json:"item_id"`
|
||
Quantity int64 `json:"quantity"`
|
||
}
|
||
|
||
type JobPayloadSaveRes struct {
|
||
MsgID64 int64 `json:"msg_id"`
|
||
ChatID64 int64 `json:"chat_id"`
|
||
ResObjID64 int64 `json:"res_obj_id"`
|
||
BuyRes bool `json:"buy_res"`
|
||
}
|
||
|
||
type JobPayloadRescanMsg struct {
|
||
Query string `json:"query"`
|
||
MsgID64 int64 `json:"msg_id"`
|
||
ChatID64 int64 `json:"chat_id"`
|
||
}
|
||
|
||
type JobPayloadSetDone struct {
|
||
JobID64 int64 `json:"job_id"`
|
||
MsgID64 int64 `json:"msg_id"`
|
||
ChatID64 int64 `json:"chat_id"`
|
||
Text string `json:"test"`
|
||
}
|
||
|
||
type JobPayloadMsgClient struct {
|
||
Text string `json:"msg"`
|
||
MsgID64 int64 `json:"msg_id"`
|
||
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"`
|
||
ChatID64 int64 `json:"chat_id"`
|
||
}
|
||
|
||
const (
|
||
userID64ChtWrsBot = 408101137
|
||
|
||
commandForwardMsg = 1
|
||
commandReplyMsg = 2
|
||
commandSendMsg = 3
|
||
commandDeleteMsg = 4
|
||
commandRefreshMsg = 5
|
||
commandSendDocument = 6
|
||
|
||
cmdParseModePlain = 1
|
||
cmdParseModeMarkDown = 2
|
||
cmdParseModeHTML = 3
|
||
|
||
objTypeUser = 1
|
||
objTypeGuild = 2
|
||
objTypeMessage = 3
|
||
objTypeWar = 4
|
||
objTypeWarReport = 5
|
||
objTypeJob = 6
|
||
objTypeItem = 7
|
||
objTypeCastle = 8
|
||
objTypeFair = 9
|
||
objTypeUnion = 10
|
||
objTypeTribute = 11
|
||
objTypeExperience = 12
|
||
objTypeQuest = 13
|
||
|
||
castleDeer = 1
|
||
castleDragon = 2
|
||
castleHighnest = 3
|
||
castleMoon = 4
|
||
castlePotato = 5
|
||
castleShark = 6
|
||
castleWolf = 7
|
||
|
||
objSubTypeUser = 101
|
||
objSubTypeGuild = 201
|
||
objSubTypeMessageUnknown = 301
|
||
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)
|
||
objSubTypeMessageReportAck = 306 // result from /report (done)
|
||
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)
|
||
objSubTypeMessageMeAck = 314 // result from 🏅Me (done)
|
||
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)
|
||
objSubTypeMessageTop = 334 // any /topXX (not done)
|
||
objSubTypeMessageMenu = 335 // main menu (not done)
|
||
objSubTypeMessageBuyReq = 336 // /wtb_xx (done)
|
||
objSubTypeMessageSellReq = 337 // /wts_xx (done)
|
||
objSubTypeMessageOrderbookReq = 338 // /t_xx (done)
|
||
objSubTypeMessageOrderbookAck = 339 // orderbook summary (not done)
|
||
objSubTypeMessageWithdrawReq = 340 // /g_withdraw (done)
|
||
objSubTypeMessageWithdrawCode = 341 // code to receive (done)
|
||
objSubTypeMessageWithdrawRcv = 342 // Withdraw "received" msg (done)
|
||
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)
|
||
objSubTypeMessageAttackReq = 373 // Attack (done)
|
||
objSubTypeMessageAttackAck = 374 // Read for attack, select target (done)
|
||
objSubTypeMessageAttackTargetReq = 375 // Castle selection (done)
|
||
objSubTypeMessageAttackTargetAck = 376 // Castle confirmation (done)
|
||
objSubTypeMessageDefendReq = 377 // Defend (done)
|
||
objSubTypeMessageDefendAck = 378 // Defend confirmation (done)
|
||
objSubTypeMessageBack = 379 // Back (done)
|
||
objSubTypeMessageCastleReq = 380 // Castle (done)
|
||
objSubTypeMessageCastleAck = 381 // Castle msg (not done)
|
||
objSubTypeMessageExchangeReq = 382 // ⚖Exchange (done)
|
||
objSubTypeMessageExchangeAck = 383 // List of deals (done)
|
||
objSubTypeJobPillage = 601
|
||
objSubTypeJobTribute = 602
|
||
objSubTypeJobStatus = 603
|
||
objSubTypeJobWithdrawal = 604
|
||
objSubTypeJobGStock = 605
|
||
objSubTypeJobRescanMsg = 606
|
||
objSubTypeJobSetJobDone = 607
|
||
objSubTypeJobMsgClient = 608
|
||
objSubTypeJobMsgRefresh = 609
|
||
objSubTypeJobBackupExport = 610
|
||
objSubTypeJobBackupImport = 611
|
||
objSubTypeJobGDeposit = 612
|
||
objSubTypeJobGDepositForward = 613
|
||
objSubTypeJobSaveRes = 614
|
||
objSubTypeItemResource = 701
|
||
objSubTypeItemAlch = 702
|
||
objSubTypeItemMisc = 703
|
||
objSubTypeItemRecipe = 704
|
||
objSubTypeItemPart = 705
|
||
objSubTypeItemOther = 706
|
||
objSubTypeItemUnique = 707
|
||
objSubTypeCastle = 801
|
||
objSubTypeFair = 901
|
||
objSubTypeUnion = 1001
|
||
objSubTypeTribute = 1101
|
||
objSubTypeExperience = 1201
|
||
objSubTypeQuestForest = 1301
|
||
objSubTypeQuestSwamp = 1302
|
||
objSubTypeQuestValley = 1303
|
||
|
||
objJobStatusCallBack = -1
|
||
objJobStatusNew = 0
|
||
objJobStatusPillageGo = 1
|
||
objJonStatusPending = 10
|
||
objJobStatusDone = 20
|
||
|
||
objJobPriority = 1
|
||
objJobPriorityRescanMsg = 2
|
||
objJobPriorityRescanAllMsg = 3
|
||
objJobPriorityBackup = 4
|
||
|
||
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
|
||
)
|
||
|
||
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}
|
||
|
||
chatWarsDaysSpecial = map[int]map[int]int{
|
||
2: {1060: 29},
|
||
4: {1060: 29}}
|
||
|
||
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}
|
||
)
|