make mail a service

This commit is contained in:
stratic-dev
2024-10-17 02:39:40 +01:00
parent b6f24ae22d
commit 6075e9c756
9 changed files with 297 additions and 264 deletions

18
internal/constant/cast.go Normal file
View File

@@ -0,0 +1,18 @@
package constant
const (
BinaryMessageTypeState = 0
BinaryMessageTypeChat = 1
BinaryMessageTypeQuest = 2
BinaryMessageTypeData = 3
BinaryMessageTypeMailNotify = 4
BinaryMessageTypeEmote = 6
)
// MSG_SYS_CAST[ED]_BINARY broadcast types enum
const (
BroadcastTypeTargeted = 0x01
BroadcastTypeStage = 0x03
BroadcastTypeServer = 0x06
BroadcastTypeWorld = 0x0a
)

236
internal/service/mail.go Normal file
View File

@@ -0,0 +1,236 @@
package service
import (
"database/sql"
"erupe-ce/internal/constant"
"erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket"
"erupe-ce/utils/byteframe"
"erupe-ce/utils/db"
"erupe-ce/utils/logger"
"fmt"
"time"
"go.uber.org/zap"
)
type Mail struct {
ID int `db:"id"`
SenderID uint32 `db:"sender_id"`
RecipientID uint32 `db:"recipient_id"`
Subject string `db:"subject"`
Body string `db:"body"`
Read bool `db:"read"`
Deleted bool `db:"deleted"`
Locked bool `db:"locked"`
AttachedItemReceived bool `db:"attached_item_received"`
AttachedItemID uint16 `db:"attached_item"`
AttachedItemAmount uint16 `db:"attached_item_amount"`
CreatedAt time.Time `db:"created_at"`
IsGuildInvite bool `db:"is_guild_invite"`
IsSystemMessage bool `db:"is_sys_message"`
SenderName string `db:"sender_name"`
}
func (m *Mail) Send(transaction *sql.Tx) error {
db, err := db.GetDB()
logger := logger.Get()
if err != nil {
logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
query := `
INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite, is_sys_message)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
`
if transaction == nil {
_, err = db.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite, m.IsSystemMessage)
} else {
_, err = transaction.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite, m.IsSystemMessage)
}
if err != nil {
logger.Error(
"failed to send mail",
zap.Error(err),
zap.Uint32("senderID", m.SenderID),
zap.Uint32("recipientID", m.RecipientID),
zap.String("subject", m.Subject),
zap.String("body", m.Body),
zap.Uint16("itemID", m.AttachedItemID),
zap.Uint16("itemAmount", m.AttachedItemAmount),
zap.Bool("isGuildInvite", m.IsGuildInvite),
zap.Bool("isSystemMessage", m.IsSystemMessage),
)
return err
}
return nil
}
func (m *Mail) MarkRead() error {
db, err := db.GetDB()
logger := logger.Get()
if err != nil {
logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
_, err = db.Exec(`
UPDATE mail SET read = true WHERE id = $1
`, m.ID)
if err != nil {
logger.Error(
"failed to mark mail as read",
zap.Error(err),
zap.Int("mailID", m.ID),
)
return err
}
return nil
}
func GetMailListForCharacter(charID uint32) ([]Mail, error) {
db, err := db.GetDB()
logger := logger.Get()
if err != nil {
logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
rows, err := db.Queryx(`
SELECT
m.id,
m.sender_id,
m.recipient_id,
m.subject,
m.read,
m.attached_item_received,
m.attached_item,
m.attached_item_amount,
m.created_at,
m.is_guild_invite,
m.is_sys_message,
m.deleted,
m.locked,
c.name as sender_name
FROM mail m
JOIN characters c ON c.id = m.sender_id
WHERE recipient_id = $1 AND m.deleted = false
ORDER BY m.created_at DESC, id DESC
LIMIT 32
`, charID)
if err != nil {
logger.Error("failed to get mail for character", zap.Error(err), zap.Uint32("charID", charID))
return nil, err
}
defer rows.Close()
allMail := make([]Mail, 0)
for rows.Next() {
mail := Mail{}
err := rows.StructScan(&mail)
if err != nil {
return nil, err
}
allMail = append(allMail, mail)
}
return allMail, nil
}
func GetMailByID(ID int) (*Mail, error) {
db, err := db.GetDB()
logger := logger.Get()
if err != nil {
logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
row := db.QueryRowx(`
SELECT
m.id,
m.sender_id,
m.recipient_id,
m.subject,
m.read,
m.body,
m.attached_item_received,
m.attached_item,
m.attached_item_amount,
m.created_at,
m.is_guild_invite,
m.is_sys_message,
m.deleted,
m.locked,
c.name as sender_name
FROM mail m
JOIN characters c ON c.id = m.sender_id
WHERE m.id = $1
LIMIT 1
`, ID)
mail := &Mail{}
err = row.StructScan(mail)
if err != nil {
logger.Error(
"failed to retrieve mail",
zap.Error(err),
zap.Int("mailID", ID),
)
return nil, err
}
return mail, nil
}
type SessionMail interface {
QueueSendMHF(packet mhfpacket.MHFPacket)
}
func SendMailNotification(s SessionMail, m *Mail, recipient SessionMail) {
bf := byteframe.NewByteFrame()
notification := &binpacket.MsgBinMailNotify{
SenderName: getCharacterName(m.SenderID),
}
notification.Build(bf)
castedBinary := &mhfpacket.MsgSysCastedBinary{
CharID: m.SenderID,
BroadcastType: 0x00,
MessageType: constant.BinaryMessageTypeMailNotify,
RawDataPayload: bf.Data(),
}
castedBinary.Build(bf)
recipient.QueueSendMHF(castedBinary)
}
func getCharacterName(charID uint32) string {
db, err := db.GetDB()
logger := logger.Get()
if err != nil {
logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
row := db.QueryRow("SELECT name FROM characters WHERE id = $1", charID)
charName := ""
err = row.Scan(&charName)
if err != nil {
return ""
}
return charName
}

View File

@@ -4,6 +4,7 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"erupe-ce/config" "erupe-ce/config"
"erupe-ce/internal/constant"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"erupe-ce/utils/byteframe" "erupe-ce/utils/byteframe"
"erupe-ce/utils/db" "erupe-ce/utils/db"
@@ -357,7 +358,7 @@ func teleport(s *Session, args []string) error {
payload.WriteInt16(int16(y)) payload.WriteInt16(int16(y))
s.QueueSendMHF(&mhfpacket.MsgSysCastedBinary{ s.QueueSendMHF(&mhfpacket.MsgSysCastedBinary{
CharID: s.CharID, CharID: s.CharID,
MessageType: BinaryMessageTypeState, MessageType: constant.BinaryMessageTypeState,
RawDataPayload: payload.Data(), RawDataPayload: payload.Data(),
}) })
s.sendMessage(t("commands.teleport.success", v{"x": fmt.Sprintf("%d", x), "y": fmt.Sprintf("%d", y)})) s.sendMessage(t("commands.teleport.success", v{"x": fmt.Sprintf("%d", x), "y": fmt.Sprintf("%d", y)}))

View File

@@ -2,6 +2,7 @@ package channelserver
import ( import (
"erupe-ce/config" "erupe-ce/config"
"erupe-ce/internal/constant"
"erupe-ce/network/binpacket" "erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"erupe-ce/utils/byteframe" "erupe-ce/utils/byteframe"
@@ -18,22 +19,6 @@ import (
) )
// MSG_SYS_CAST[ED]_BINARY types enum // MSG_SYS_CAST[ED]_BINARY types enum
const (
BinaryMessageTypeState = 0
BinaryMessageTypeChat = 1
BinaryMessageTypeQuest = 2
BinaryMessageTypeData = 3
BinaryMessageTypeMailNotify = 4
BinaryMessageTypeEmote = 6
)
// MSG_SYS_CAST[ED]_BINARY broadcast types enum
const (
BroadcastTypeTargeted = 0x01
BroadcastTypeStage = 0x03
BroadcastTypeServer = 0x06
BroadcastTypeWorld = 0x0a
)
var ( var (
commands map[string]config.Command commands map[string]config.Command
@@ -72,7 +57,7 @@ func sendServerChatMessage(s *Session, message string) {
castedBin := &mhfpacket.MsgSysCastedBinary{ castedBin := &mhfpacket.MsgSysCastedBinary{
CharID: 0, CharID: 0,
MessageType: BinaryMessageTypeChat, MessageType: constant.BinaryMessageTypeChat,
RawDataPayload: bf.Data(), RawDataPayload: bf.Data(),
} }
@@ -118,7 +103,7 @@ func handleMsgSysCastBinary(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) {
var msgBinTargeted *binpacket.MsgBinTargeted var msgBinTargeted *binpacket.MsgBinTargeted
var message, author string var message, author string
var returnToSender bool var returnToSender bool
if pkt.MessageType == BinaryMessageTypeChat { if pkt.MessageType == constant.BinaryMessageTypeChat {
tmp.SetLE() tmp.SetLE()
tmp.Seek(8, 0) tmp.Seek(8, 0)
message = string(tmp.ReadNullTerminatedBytes()) message = string(tmp.ReadNullTerminatedBytes())
@@ -127,7 +112,7 @@ func handleMsgSysCastBinary(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) {
// Customise payload // Customise payload
realPayload := pkt.RawDataPayload realPayload := pkt.RawDataPayload
if pkt.BroadcastType == BroadcastTypeTargeted { if pkt.BroadcastType == constant.BroadcastTypeTargeted {
tmp.SetBE() tmp.SetBE()
tmp.Seek(0, 0) tmp.Seek(0, 0)
msgBinTargeted = &binpacket.MsgBinTargeted{} msgBinTargeted = &binpacket.MsgBinTargeted{}
@@ -137,11 +122,11 @@ func handleMsgSysCastBinary(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) {
return return
} }
realPayload = msgBinTargeted.RawDataPayload realPayload = msgBinTargeted.RawDataPayload
} else if pkt.MessageType == BinaryMessageTypeChat { } else if pkt.MessageType == constant.BinaryMessageTypeChat {
if message == "@dice" { if message == "@dice" {
returnToSender = true returnToSender = true
m := binpacket.MsgBinChat{ m := binpacket.MsgBinChat{
Type: BinaryMessageTypeChat, Type: constant.BinaryMessageTypeChat,
Flags: 4, Flags: 4,
Message: fmt.Sprintf(`%d`, token.RNG.Intn(100)+1), Message: fmt.Sprintf(`%d`, token.RNG.Intn(100)+1),
SenderName: author, SenderName: author,
@@ -162,7 +147,7 @@ func handleMsgSysCastBinary(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) {
} }
return return
} }
if (pkt.BroadcastType == BroadcastTypeStage && s.stage.id == "sl1Ns200p0a0u0") || pkt.BroadcastType == BroadcastTypeWorld { if (pkt.BroadcastType == constant.BroadcastTypeStage && s.stage.id == "sl1Ns200p0a0u0") || pkt.BroadcastType == constant.BroadcastTypeWorld {
s.Server.DiscordChannelSend(chatMessage.SenderName, chatMessage.Message) s.Server.DiscordChannelSend(chatMessage.SenderName, chatMessage.Message)
} }
} }
@@ -178,15 +163,15 @@ func handleMsgSysCastBinary(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) {
// Send to the proper recipients. // Send to the proper recipients.
switch pkt.BroadcastType { switch pkt.BroadcastType {
case BroadcastTypeWorld: case constant.BroadcastTypeWorld:
s.Server.WorldcastMHF(resp, s, nil) s.Server.WorldcastMHF(resp, s, nil)
case BroadcastTypeStage: case constant.BroadcastTypeStage:
if returnToSender { if returnToSender {
s.stage.BroadcastMHF(resp, nil) s.stage.BroadcastMHF(resp, nil)
} else { } else {
s.stage.BroadcastMHF(resp, s) s.stage.BroadcastMHF(resp, s)
} }
case BroadcastTypeServer: case constant.BroadcastTypeServer:
if pkt.MessageType == 1 { if pkt.MessageType == 1 {
raviSema := s.Server.getRaviSemaphore() raviSema := s.Server.getRaviSemaphore()
if raviSema != nil { if raviSema != nil {
@@ -195,7 +180,7 @@ func handleMsgSysCastBinary(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) {
} else { } else {
s.Server.BroadcastMHF(resp, s) s.Server.BroadcastMHF(resp, s)
} }
case BroadcastTypeTargeted: case constant.BroadcastTypeTargeted:
for _, targetID := range (*msgBinTargeted).TargetCharIDs { for _, targetID := range (*msgBinTargeted).TargetCharIDs {
char := s.Server.FindSessionByCharID(targetID) char := s.Server.FindSessionByCharID(targetID)

View File

@@ -7,6 +7,8 @@ import (
"errors" "errors"
"erupe-ce/config" "erupe-ce/config"
"erupe-ce/internal/model" "erupe-ce/internal/model"
"erupe-ce/internal/service"
"erupe-ce/utils/db" "erupe-ce/utils/db"
"erupe-ce/utils/gametime" "erupe-ce/utils/gametime"
"erupe-ce/utils/mhfitem" "erupe-ce/utils/mhfitem"
@@ -723,13 +725,13 @@ func HandleMsgMhfOperateGuild(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) {
if err != nil { if err != nil {
response = 0 response = 0
} else { } else {
mail := Mail{ mail := service.Mail{
RecipientID: s.CharID, RecipientID: s.CharID,
Subject: "Withdrawal", Subject: "Withdrawal",
Body: fmt.Sprintf("You have withdrawn from 「%s」.", guild.Name), Body: fmt.Sprintf("You have withdrawn from 「%s」.", guild.Name),
IsSystemMessage: true, IsSystemMessage: true,
} }
mail.Send(s, nil) mail.Send(nil)
} }
bf.WriteUint32(uint32(response)) bf.WriteUint32(uint32(response))
case mhfpacket.OperateGuildDonateRank: case mhfpacket.OperateGuildDonateRank:
@@ -900,11 +902,11 @@ func HandleMsgMhfOperateGuildMember(s *Session, db *sqlx.DB, p mhfpacket.MHFPack
return return
} }
var mail Mail var mail service.Mail
switch pkt.Action { switch pkt.Action {
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT: case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT:
err = guild.AcceptApplication(s, pkt.CharID) err = guild.AcceptApplication(s, pkt.CharID)
mail = Mail{ mail = service.Mail{
RecipientID: pkt.CharID, RecipientID: pkt.CharID,
Subject: "Accepted!", Subject: "Accepted!",
Body: fmt.Sprintf("Your application to join 「%s」 was accepted.", guild.Name), Body: fmt.Sprintf("Your application to join 「%s」 was accepted.", guild.Name),
@@ -912,7 +914,7 @@ func HandleMsgMhfOperateGuildMember(s *Session, db *sqlx.DB, p mhfpacket.MHFPack
} }
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT: case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT:
err = guild.RejectApplication(s, pkt.CharID) err = guild.RejectApplication(s, pkt.CharID)
mail = Mail{ mail = service.Mail{
RecipientID: pkt.CharID, RecipientID: pkt.CharID,
Subject: "Rejected", Subject: "Rejected",
Body: fmt.Sprintf("Your application to join 「%s」 was rejected.", guild.Name), Body: fmt.Sprintf("Your application to join 「%s」 was rejected.", guild.Name),
@@ -920,7 +922,7 @@ func HandleMsgMhfOperateGuildMember(s *Session, db *sqlx.DB, p mhfpacket.MHFPack
} }
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_KICK: case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_KICK:
err = guild.RemoveCharacter(s, pkt.CharID) err = guild.RemoveCharacter(s, pkt.CharID)
mail = Mail{ mail = service.Mail{
RecipientID: pkt.CharID, RecipientID: pkt.CharID,
Subject: "Kicked", Subject: "Kicked",
Body: fmt.Sprintf("You were kicked from 「%s」.", guild.Name), Body: fmt.Sprintf("You were kicked from 「%s」.", guild.Name),
@@ -934,11 +936,11 @@ func HandleMsgMhfOperateGuildMember(s *Session, db *sqlx.DB, p mhfpacket.MHFPack
if err != nil { if err != nil {
s.DoAckSimpleFail(pkt.AckHandle, make([]byte, 4)) s.DoAckSimpleFail(pkt.AckHandle, make([]byte, 4))
} else { } else {
mail.Send(s, nil) mail.Send(nil)
for _, channel := range s.Server.Channels { for _, channel := range s.Server.Channels {
for _, session := range channel.sessions { for _, session := range channel.sessions {
if session.CharID == pkt.CharID { if session.CharID == pkt.CharID {
SendMailNotification(s, &mail, session) service.SendMailNotification(s, &mail, session)
} }
} }
} }

View File

@@ -1,6 +1,7 @@
package channelserver package channelserver
import ( import (
"erupe-ce/internal/service"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"erupe-ce/utils/byteframe" "erupe-ce/utils/byteframe"
"erupe-ce/utils/gametime" "erupe-ce/utils/gametime"
@@ -60,7 +61,7 @@ func HandleMsgMhfPostGuildScout(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket)
panic(err) panic(err)
} }
mail := &Mail{ mail := &service.Mail{
SenderID: s.CharID, SenderID: s.CharID,
RecipientID: pkt.CharID, RecipientID: pkt.CharID,
Subject: s.Server.i18n.guild.invite.title, Subject: s.Server.i18n.guild.invite.title,
@@ -71,7 +72,7 @@ func HandleMsgMhfPostGuildScout(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket)
IsGuildInvite: true, IsGuildInvite: true,
} }
err = mail.Send(s, transaction) err = mail.Send(transaction)
if err != nil { if err != nil {
rollbackTransaction(s, transaction) rollbackTransaction(s, transaction)
@@ -144,16 +145,16 @@ func HandleMsgMhfAnswerGuildScout(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket
return return
} }
var mail []Mail var mail []service.Mail
if pkt.Answer { if pkt.Answer {
err = guild.AcceptApplication(s, s.CharID) err = guild.AcceptApplication(s, s.CharID)
mail = append(mail, Mail{ mail = append(mail, service.Mail{
RecipientID: s.CharID, RecipientID: s.CharID,
Subject: s.Server.i18n.guild.invite.success.title, Subject: s.Server.i18n.guild.invite.success.title,
Body: fmt.Sprintf(s.Server.i18n.guild.invite.success.body, guild.Name), Body: fmt.Sprintf(s.Server.i18n.guild.invite.success.body, guild.Name),
IsSystemMessage: true, IsSystemMessage: true,
}) })
mail = append(mail, Mail{ mail = append(mail, service.Mail{
SenderID: s.CharID, SenderID: s.CharID,
RecipientID: pkt.LeaderID, RecipientID: pkt.LeaderID,
Subject: s.Server.i18n.guild.invite.accepted.title, Subject: s.Server.i18n.guild.invite.accepted.title,
@@ -162,13 +163,13 @@ func HandleMsgMhfAnswerGuildScout(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket
}) })
} else { } else {
err = guild.RejectApplication(s, s.CharID) err = guild.RejectApplication(s, s.CharID)
mail = append(mail, Mail{ mail = append(mail, service.Mail{
RecipientID: s.CharID, RecipientID: s.CharID,
Subject: s.Server.i18n.guild.invite.rejected.title, Subject: s.Server.i18n.guild.invite.rejected.title,
Body: fmt.Sprintf(s.Server.i18n.guild.invite.rejected.body, guild.Name), Body: fmt.Sprintf(s.Server.i18n.guild.invite.rejected.body, guild.Name),
IsSystemMessage: true, IsSystemMessage: true,
}) })
mail = append(mail, Mail{ mail = append(mail, service.Mail{
SenderID: s.CharID, SenderID: s.CharID,
RecipientID: pkt.LeaderID, RecipientID: pkt.LeaderID,
Subject: s.Server.i18n.guild.invite.declined.title, Subject: s.Server.i18n.guild.invite.declined.title,
@@ -185,7 +186,7 @@ func HandleMsgMhfAnswerGuildScout(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket
bf.WriteUint32(guild.ID) bf.WriteUint32(guild.ID)
s.DoAckBufSucceed(pkt.AckHandle, bf.Data()) s.DoAckBufSucceed(pkt.AckHandle, bf.Data())
for _, m := range mail { for _, m := range mail {
m.Send(s, nil) m.Send(nil)
} }
} }
} }

View File

@@ -1,228 +1,15 @@
package channelserver package channelserver
import ( import (
"database/sql" "erupe-ce/internal/service"
"erupe-ce/utils/db"
"erupe-ce/utils/stringsupport" "erupe-ce/utils/stringsupport"
"fmt"
"time"
"erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"erupe-ce/utils/byteframe" "erupe-ce/utils/byteframe"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"go.uber.org/zap"
) )
type Mail struct {
ID int `db:"id"`
SenderID uint32 `db:"sender_id"`
RecipientID uint32 `db:"recipient_id"`
Subject string `db:"subject"`
Body string `db:"body"`
Read bool `db:"read"`
Deleted bool `db:"deleted"`
Locked bool `db:"locked"`
AttachedItemReceived bool `db:"attached_item_received"`
AttachedItemID uint16 `db:"attached_item"`
AttachedItemAmount uint16 `db:"attached_item_amount"`
CreatedAt time.Time `db:"created_at"`
IsGuildInvite bool `db:"is_guild_invite"`
IsSystemMessage bool `db:"is_sys_message"`
SenderName string `db:"sender_name"`
}
func (m *Mail) Send(s *Session, transaction *sql.Tx) error {
db, err := db.GetDB()
if err != nil {
s.Logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
query := `
INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite, is_sys_message)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
`
if transaction == nil {
_, err = db.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite, m.IsSystemMessage)
} else {
_, err = transaction.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite, m.IsSystemMessage)
}
if err != nil {
s.Logger.Error(
"failed to send mail",
zap.Error(err),
zap.Uint32("senderID", m.SenderID),
zap.Uint32("recipientID", m.RecipientID),
zap.String("subject", m.Subject),
zap.String("body", m.Body),
zap.Uint16("itemID", m.AttachedItemID),
zap.Uint16("itemAmount", m.AttachedItemAmount),
zap.Bool("isGuildInvite", m.IsGuildInvite),
zap.Bool("isSystemMessage", m.IsSystemMessage),
)
return err
}
return nil
}
func (m *Mail) MarkRead(s *Session) error {
db, err := db.GetDB()
if err != nil {
s.Logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
_, err = db.Exec(`
UPDATE mail SET read = true WHERE id = $1
`, m.ID)
if err != nil {
s.Logger.Error(
"failed to mark mail as read",
zap.Error(err),
zap.Int("mailID", m.ID),
)
return err
}
return nil
}
func GetMailListForCharacter(s *Session, charID uint32) ([]Mail, error) {
db, err := db.GetDB()
if err != nil {
s.Logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
rows, err := db.Queryx(`
SELECT
m.id,
m.sender_id,
m.recipient_id,
m.subject,
m.read,
m.attached_item_received,
m.attached_item,
m.attached_item_amount,
m.created_at,
m.is_guild_invite,
m.is_sys_message,
m.deleted,
m.locked,
c.name as sender_name
FROM mail m
JOIN characters c ON c.id = m.sender_id
WHERE recipient_id = $1 AND m.deleted = false
ORDER BY m.created_at DESC, id DESC
LIMIT 32
`, charID)
if err != nil {
s.Logger.Error("failed to get mail for character", zap.Error(err), zap.Uint32("charID", charID))
return nil, err
}
defer rows.Close()
allMail := make([]Mail, 0)
for rows.Next() {
mail := Mail{}
err := rows.StructScan(&mail)
if err != nil {
return nil, err
}
allMail = append(allMail, mail)
}
return allMail, nil
}
func GetMailByID(s *Session, ID int) (*Mail, error) {
db, err := db.GetDB()
if err != nil {
s.Logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
row := db.QueryRowx(`
SELECT
m.id,
m.sender_id,
m.recipient_id,
m.subject,
m.read,
m.body,
m.attached_item_received,
m.attached_item,
m.attached_item_amount,
m.created_at,
m.is_guild_invite,
m.is_sys_message,
m.deleted,
m.locked,
c.name as sender_name
FROM mail m
JOIN characters c ON c.id = m.sender_id
WHERE m.id = $1
LIMIT 1
`, ID)
mail := &Mail{}
err = row.StructScan(mail)
if err != nil {
s.Logger.Error(
"failed to retrieve mail",
zap.Error(err),
zap.Int("mailID", ID),
)
return nil, err
}
return mail, nil
}
func SendMailNotification(s *Session, m *Mail, recipient *Session) {
bf := byteframe.NewByteFrame()
notification := &binpacket.MsgBinMailNotify{
SenderName: getCharacterName(s, m.SenderID),
}
notification.Build(bf)
castedBinary := &mhfpacket.MsgSysCastedBinary{
CharID: m.SenderID,
BroadcastType: 0x00,
MessageType: BinaryMessageTypeMailNotify,
RawDataPayload: bf.Data(),
}
castedBinary.Build(bf)
recipient.QueueSendMHF(castedBinary)
}
func getCharacterName(s *Session, charID uint32) string {
db, err := db.GetDB()
if err != nil {
s.Logger.Fatal(fmt.Sprintf("Failed to get database instance: %s", err))
}
row := db.QueryRow("SELECT name FROM characters WHERE id = $1", charID)
charName := ""
err = row.Scan(&charName)
if err != nil {
return ""
}
return charName
}
func handleMsgMhfReadMail(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) { func handleMsgMhfReadMail(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMail) pkt := p.(*mhfpacket.MsgMhfReadMail)
@@ -232,7 +19,7 @@ func handleMsgMhfReadMail(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) {
return return
} }
mail, err := GetMailByID(s, mailId) mail, err := service.GetMailByID(mailId)
if err != nil { if err != nil {
s.DoAckBufSucceed(pkt.AckHandle, []byte{0}) s.DoAckBufSucceed(pkt.AckHandle, []byte{0})
return return
@@ -248,7 +35,7 @@ func handleMsgMhfReadMail(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) {
func handleMsgMhfListMail(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) { func handleMsgMhfListMail(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfListMail) pkt := p.(*mhfpacket.MsgMhfListMail)
mail, err := GetMailListForCharacter(s, s.CharID) mail, err := service.GetMailListForCharacter(s.CharID)
if err != nil { if err != nil {
s.DoAckBufSucceed(pkt.AckHandle, []byte{0}) s.DoAckBufSucceed(pkt.AckHandle, []byte{0})
return return
@@ -317,7 +104,7 @@ func handleMsgMhfListMail(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) {
func handleMsgMhfOprtMail(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) { func handleMsgMhfOprtMail(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfOprtMail) pkt := p.(*mhfpacket.MsgMhfOprtMail)
mail, err := GetMailByID(s, s.mailList[pkt.AccIndex]) mail, err := service.GetMailByID(s.mailList[pkt.AccIndex])
if err != nil { if err != nil {
s.DoAckSimpleSucceed(pkt.AckHandle, make([]byte, 4)) s.DoAckSimpleSucceed(pkt.AckHandle, make([]byte, 4))
return return

View File

@@ -1,6 +1,7 @@
package channelserver package channelserver
import ( import (
"erupe-ce/internal/constant"
"erupe-ce/network/binpacket" "erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"erupe-ce/utils/byteframe" "erupe-ce/utils/byteframe"
@@ -45,7 +46,7 @@ func (server *ChannelServer) BroadcastChatMessage(message string) {
msgBinChat.Build(bf) msgBinChat.Build(bf)
server.BroadcastMHF(&mhfpacket.MsgSysCastedBinary{ server.BroadcastMHF(&mhfpacket.MsgSysCastedBinary{
MessageType: BinaryMessageTypeChat, MessageType: constant.BinaryMessageTypeChat,
RawDataPayload: bf.Data(), RawDataPayload: bf.Data(),
}, nil) }, nil)
} }
@@ -76,8 +77,8 @@ func (server *ChannelServer) BroadcastRaviente(ip uint32, port uint16, stage []b
bf.WriteUint16(0) // Unk bf.WriteUint16(0) // Unk
bf.WriteBytes(stage) bf.WriteBytes(stage)
server.WorldcastMHF(&mhfpacket.MsgSysCastedBinary{ server.WorldcastMHF(&mhfpacket.MsgSysCastedBinary{
BroadcastType: BroadcastTypeServer, BroadcastType: constant.BroadcastTypeServer,
MessageType: BinaryMessageTypeChat, MessageType: constant.BinaryMessageTypeChat,
RawDataPayload: bf.Data(), RawDataPayload: bf.Data(),
}, nil, server) }, nil, server)
} }

View File

@@ -4,6 +4,7 @@ import (
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"erupe-ce/config" "erupe-ce/config"
"erupe-ce/internal/constant"
"erupe-ce/network" "erupe-ce/network"
"erupe-ce/network/binpacket" "erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
@@ -15,6 +16,7 @@ import (
"erupe-ce/utils/stringstack" "erupe-ce/utils/stringstack"
"fmt" "fmt"
"io" "io"
"net" "net"
"sync" "sync"
"time" "time"
@@ -269,7 +271,7 @@ func (s *Session) sendMessage(message string) {
msgBinChat.Build(bf) msgBinChat.Build(bf)
castedBin := &mhfpacket.MsgSysCastedBinary{ castedBin := &mhfpacket.MsgSysCastedBinary{
CharID: 0, CharID: 0,
MessageType: BinaryMessageTypeChat, MessageType: constant.BinaryMessageTypeChat,
RawDataPayload: bf.Data(), RawDataPayload: bf.Data(),
} }
s.QueueSendMHF(castedBin) s.QueueSendMHF(castedBin)