Files
honoka-chan/internal/handler/friend/search.go
T
YumeMichi 45eef9566a Fix client friend status mapping
- Add explicit client friend status constants and resolver
- Return dynamic friend status from profileInfo and friend/search
- Accept invite_code when loading profileInfo targets
- Align live support friend status values with client expectations

Signed-off-by: Sean Du <do4suki@gmail.com>
2026-06-05 22:55:00 +08:00

193 lines
5.8 KiB
Go

package friend
import (
"errors"
"honoka-chan/internal/middleware"
unitmodel "honoka-chan/internal/model/unit"
usermodel "honoka-chan/internal/model/user"
"honoka-chan/internal/router"
friendschema "honoka-chan/internal/schema/friend"
"honoka-chan/internal/session"
honokautils "honoka-chan/internal/utils"
"time"
"github.com/gin-gonic/gin"
)
type friendSearchRow struct {
UserID int `xorm:"user_id"`
InviteCode string `xorm:"invite_code"`
UserName string `xorm:"user_name"`
UserLevel int `xorm:"user_level"`
UserDesc string `xorm:"user_desc"`
EnergyMax int `xorm:"energy_max"`
AwardID int `xorm:"award_id"`
LastLoginTime int64 `xorm:"last_login_time"`
CenterUnitOwningUserID int `xorm:"center_unit_owning_user_id"`
NaviUnitOwningUserID int `xorm:"navi_unit_owning_user_id"`
}
func search(ctx *gin.Context) {
ss := session.Get(ctx)
defer ss.Finalize()
req := friendschema.SearchReq{}
err := honokautils.ParseRequestData(ctx, &req)
if ss.CheckErr(err) {
return
}
targetUserID, err := resolveUserIDByInviteCode(ss, req.InviteCode)
if ss.CheckErr(err) {
return
}
row := friendSearchRow{}
has, err := ss.UserEng.Table(new(usermodel.UserPref)).Alias("up").
Join("LEFT", "users u", "u.user_id = up.user_id").
Join("LEFT", "user_deck ud", "ud.user_id = up.user_id AND ud.main_flag = 1").
Join("LEFT", "user_deck_unit udu", "udu.user_deck_id = ud.id AND udu.position = 5").
Select(`
up.user_id,
up.invite_code,
up.user_name,
up.user_level,
up.user_desc,
up.energy_max,
up.award_id,
u.last_login_time,
COALESCE(udu.unit_owning_user_id, up.unit_owning_user_id) AS center_unit_owning_user_id,
up.unit_owning_user_id AS navi_unit_owning_user_id
`).
Where("up.user_id = ?", targetUserID).
Get(&row)
if ss.CheckErr(err) {
return
}
if !has {
ss.CheckErr(errors.New("user not found"))
return
}
unitCount, err := ss.UserEng.Table(new(usermodel.UserUnitData)).
Where("user_id = ?", targetUserID).
Count()
if ss.CheckErr(err) {
return
}
centerUnitInfo, err := buildCenterUnitInfo(ss, targetUserID, row.CenterUnitOwningUserID, row.AwardID)
if ss.CheckErr(err) {
return
}
costume, err := getSearchCostume(ss, targetUserID, row.NaviUnitOwningUserID, centerUnitInfo)
if ss.CheckErr(err) {
return
}
friendStatus, err := usermodel.ResolveClientFriendStatus(ss.UserEng, ss.UserID, targetUserID)
if ss.CheckErr(err) {
return
}
ss.Respond(friendschema.SearchResp{
ResponseData: friendschema.SearchData{
UserInfo: friendschema.SearchUserInfo{
UserID: row.UserID,
Name: row.UserName,
Level: row.UserLevel,
CostMax: 100,
UnitMax: 5000,
EnergyMax: positiveIntOrDefault(row.EnergyMax, usermodel.DefaultUserEnergyMax),
FriendMax: 99,
UnitCnt: unitCount,
ElapsedTimeFromLogin: formatElapsedTime(row.LastLoginTime),
Comment: row.UserDesc,
},
CenterUnitInfo: toSearchCenterUnitInfo(centerUnitInfo, costume),
SettingAwardID: row.AwardID,
IsAlliance: false,
FriendStatus: friendStatus,
ServerTimestamp: time.Now().Unix(),
},
ReleaseInfo: []any{},
StatusCode: 200,
})
}
func init() {
router.AddHandler("main.php", "POST", "/friend/search", middleware.Common, search)
}
func positiveIntOrDefault(value, fallback int) int {
if value > 0 {
return value
}
return fallback
}
func toSearchCenterUnitInfo(info friendschema.CenterUnitInfo, costume friendschema.SearchCostume) friendschema.SearchCenterUnitInfo {
return friendschema.SearchCenterUnitInfo{
UnitOwningUserID: info.UnitOwningUserID,
UnitID: info.UnitID,
Exp: info.Exp,
NextExp: info.NextExp,
Level: info.Level,
LevelLimitID: info.LevelLimitID,
MaxLevel: info.MaxLevel,
Rank: info.Rank,
MaxRank: info.MaxRank,
Love: info.Love,
MaxLove: info.MaxLove,
UnitSkillLevel: info.UnitSkillLevel,
MaxHp: info.MaxHp,
FavoriteFlag: info.FavoriteFlag,
DisplayRank: info.DisplayRank,
UnitSkillExp: info.UnitSkillExp,
UnitRemovableSkillCapacity: info.UnitRemovableSkillCapacity,
Attribute: info.Attribute,
Smile: info.Smile,
Cute: info.Cute,
Cool: info.Cool,
IsLoveMax: info.IsLoveMax,
IsLevelMax: info.IsLevelMax,
IsRankMax: info.IsRankMax,
IsSigned: info.IsSigned,
IsSkillLevelMax: info.IsSkillLevelMax,
SettingAwardID: info.SettingAwardID,
RemovableSkillIds: info.RemovableSkillIds,
AccessoryInfo: info.AccessoryInfo,
Costume: costume,
}
}
func getSearchCostume(ss *session.Session, targetUserID, unitOwningUserID int, fallback friendschema.CenterUnitInfo) (friendschema.SearchCostume, error) {
costume := friendschema.SearchCostume{
UnitID: fallback.UnitID,
IsRankMax: fallback.IsRankMax,
IsSigned: fallback.IsSigned,
}
if unitOwningUserID <= 0 {
return costume, nil
}
unitData := unitmodel.UnitDataMap{}
has, err := ss.GetBasicUnitInfo().
Where("a.user_id = ?", targetUserID).
Where("a.unit_owning_user_id = ?", unitOwningUserID).
Get(&unitData)
if err != nil {
return costume, err
}
if !has {
return costume, nil
}
return friendschema.SearchCostume{
UnitID: unitData.UnitID,
IsRankMax: unitData.IsRankMax,
IsSigned: unitData.IsSigned,
}, nil
}