Files
huya-go/internal/huya/activity.go
T
yml2213 d21b06a311 新增商品类目(限量返场/精英专享)支持与版本号 0.2.1
10.15 抓包确认:积分中心商城 PlayMallUI.queryAllocation 的商品与 webActUI.getActPrizeList(sid=2277) pid 完全一致,属于同一批商品,因此列表无需接商城链路;真号实测 sid=2277 返回 30 项(限量返场 99 共 12 项 + 精英专享 101 共 18 项),sid=2203 已返回空。\n\n- huya:解析商品 tag23/25/26/27/28(发布时间、上架/下架时间、每日时段),新增 PrizeInfo.CategoryIDs/InCategory/StartsAt/NotYetOnSale 与响应级 CategoryStats/CategorySummary/FilterByCategory,列表日志带类目统计\n- sniper:Config.Category 限定自动选品范围,未命中提示带类目\n- CLI:新增 -category / -category-only\n- GUI:Config.Category、ListPrizes 回传类目字典与统计;界面商品列表按类目分页签、每行标注类目、未上架显示上架时间与倒计时,所选类目随配置提交给监控\n- 测试:新增 internal/huya/category_test.go 与 sniper 类目用例;README/WSS_NOTES 补记结论
2026-10-15 18:23:38 +08:00

845 lines
23 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package huya
import (
"fmt"
"math"
"strconv"
"strings"
"time"
"huya-go/internal/taf"
)
const (
ActivityWebUA = "web&1.0&huya"
ActivityRecordUA = "webh5&1.0.0&huya"
ActivityBindUA = "pc&1.0.0&huya"
ServantWebActUI = "webActUI"
FuncGetUserScore = "getUserScore"
FuncPrizeList = "getActPrizeList"
FuncPrizeRecords = "getUserPrizeRecords"
FuncExchangePrize = "scoreExchangePrize"
)
type ActivityUserID struct {
UID int64 `json:"uid"`
GUID string `json:"guid,omitempty"`
Token string `json:"token,omitempty"`
HuyaUA string `json:"huya_ua"`
Cookie string `json:"-"`
TokenType int32 `json:"token_type"`
DeviceInfo string `json:"device_info,omitempty"`
}
func NewActivityUserID(uid int64, cookie, ua string) ActivityUserID {
return ActivityUserID{
UID: uid,
HuyaUA: ua,
Cookie: NormalizeCookie(cookie),
TokenType: 0,
}
}
func (u *ActivityUserID) WriteTo(w *taf.Writer) error {
if err := w.WriteInt64(0, u.UID); err != nil {
return err
}
if err := w.WriteString(1, u.GUID); err != nil {
return err
}
if err := w.WriteString(2, u.Token); err != nil {
return err
}
if err := w.WriteString(3, u.HuyaUA); err != nil {
return err
}
if err := w.WriteString(4, u.Cookie); err != nil {
return err
}
if err := w.WriteInt32(5, int64(u.TokenType)); err != nil {
return err
}
return w.WriteString(6, u.DeviceInfo)
}
func (u *ActivityUserID) ReadFrom(r *taf.Reader) error {
var err error
if u.UID, err = r.ReadInt64(0, false, u.UID); err != nil {
return err
}
if u.GUID, err = r.ReadString(1, false, u.GUID); err != nil {
return err
}
if u.Token, err = r.ReadString(2, false, u.Token); err != nil {
return err
}
if u.HuyaUA, err = r.ReadString(3, false, u.HuyaUA); err != nil {
return err
}
if u.Cookie, err = r.ReadString(4, false, u.Cookie); err != nil {
return err
}
if u.TokenType, err = r.ReadInt32(5, false, u.TokenType); err != nil {
return err
}
u.DeviceInfo, err = r.ReadString(6, false, u.DeviceInfo)
return err
}
type GetUserScoreReq struct {
User ActivityUserID
SID int32
}
func (q *GetUserScoreReq) WriteTo(w *taf.Writer) error {
if err := w.WriteStruct(0, &q.User); err != nil {
return err
}
return w.WriteInt32(1, int64(q.SID))
}
func (q *GetUserScoreReq) ReadFrom(r *taf.Reader) error {
_, err := r.ReadStruct(0, false, &q.User)
if err != nil {
return err
}
q.SID, err = r.ReadInt32(1, false, q.SID)
return err
}
type GetUserScoreResp struct {
Status int32 `json:"status"`
Msg string `json:"msg"`
GainScore int32 `json:"gain_score"`
UsedScore int32 `json:"used_score"`
NewGainScore int64 `json:"new_gain_score"`
NewUsedScore int64 `json:"new_used_score"`
Score int64 `json:"score"`
IsOpenExchangeTip int32 `json:"is_open_exchange_tip"`
}
func (r *GetUserScoreResp) WriteTo(w *taf.Writer) error { return nil }
func (r *GetUserScoreResp) ReadFrom(rd *taf.Reader) error {
var err error
if r.Status, err = rd.ReadInt32(0, false, r.Status); err != nil {
return err
}
if r.Msg, err = rd.ReadString(1, false, r.Msg); err != nil {
return err
}
if r.GainScore, err = rd.ReadInt32(2, false, r.GainScore); err != nil {
return err
}
if r.UsedScore, err = rd.ReadInt32(3, false, r.UsedScore); err != nil {
return err
}
if r.NewGainScore, err = rd.ReadInt64(4, false, r.NewGainScore); err != nil {
return err
}
if r.NewUsedScore, err = rd.ReadInt64(5, false, r.NewUsedScore); err != nil {
return err
}
if r.Score, err = rd.ReadInt64(6, false, r.Score); err != nil {
return err
}
r.IsOpenExchangeTip, err = rd.ReadInt32(7, false, r.IsOpenExchangeTip)
return err
}
func (r GetUserScoreResp) AvailableScore() int64 {
if r.NewGainScore != 0 || r.NewUsedScore != 0 {
score := r.NewGainScore - r.NewUsedScore
if score < 0 {
return 0
}
return score
}
return r.Score
}
type GetActPrizeListReq struct {
User ActivityUserID
SID int32
}
func (q *GetActPrizeListReq) WriteTo(w *taf.Writer) error {
if err := w.WriteStruct(0, &q.User); err != nil {
return err
}
return w.WriteInt32(1, int64(q.SID))
}
func (q *GetActPrizeListReq) ReadFrom(r *taf.Reader) error {
_, err := r.ReadStruct(0, false, &q.User)
if err != nil {
return err
}
q.SID, err = r.ReadInt32(1, false, q.SID)
return err
}
type PrizeInfo struct {
ID int64 `json:"id"`
SID int64 `json:"sid"`
Name string `json:"name"`
Score int64 `json:"score"`
Type int32 `json:"type"`
Image string `json:"img,omitempty"`
Sort int32 `json:"sort"`
Frequency int32 `json:"frequency"`
FrequencyLimit int32 `json:"frequency_limit"`
CreateTime int64 `json:"create_time"`
Num int64 `json:"num"`
Status int32 `json:"status"`
Percent string `json:"percent"`
NewScore int64 `json:"new_score"`
// Tags 是奖品归属的类目 id(tag14,多个用逗号分隔)。
// 2026-10-15 实测:99=限量返场、101=精英专享,与响应 tag4 的字典一一对应。
Tags string `json:"tags,omitempty"`
LeftNum int64 `json:"left_num"`
IsShowNum int32 `json:"is_show_num"`
// 以下为 2026-10-15 抓包新确认的时间字段:限量返场商品是「预告上架」,
// 页面的「xx:xx:xx 后上架」倒计时就来自 StartTime。
PublishTime int64 `json:"publish_time,omitempty"` // tag23 商品发布时间
StartTime int64 `json:"start_time,omitempty"` // tag25 上架时间(秒)
EndTime int64 `json:"end_time,omitempty"` // tag26 下架时间(秒)
DailyStart string `json:"daily_start,omitempty"` // tag27 每日可兑时段开始
DailyEnd string `json:"daily_end,omitempty"` // tag28 每日可兑时段结束
}
func (p *PrizeInfo) WriteTo(w *taf.Writer) error { return nil }
func (p *PrizeInfo) ReadFrom(r *taf.Reader) error {
var err error
if p.ID, err = r.ReadInt64(0, false, p.ID); err != nil {
return err
}
if p.SID, err = r.ReadInt64(1, false, p.SID); err != nil {
return err
}
if p.Name, err = r.ReadString(2, false, p.Name); err != nil {
return err
}
if p.Score, err = r.ReadInt64(3, false, p.Score); err != nil {
return err
}
if p.Type, err = r.ReadInt32(4, false, p.Type); err != nil {
return err
}
if p.Image, err = r.ReadString(5, false, p.Image); err != nil {
return err
}
if p.Sort, err = r.ReadInt32(6, false, p.Sort); err != nil {
return err
}
if p.Frequency, err = r.ReadInt32(7, false, p.Frequency); err != nil {
return err
}
if p.FrequencyLimit, err = r.ReadInt32(8, false, p.FrequencyLimit); err != nil {
return err
}
if p.CreateTime, err = r.ReadInt64(9, false, p.CreateTime); err != nil {
return err
}
if p.Num, err = r.ReadInt64(10, false, p.Num); err != nil {
return err
}
if p.Status, err = r.ReadInt32(11, false, p.Status); err != nil {
return err
}
if p.Percent, err = r.ReadString(12, false, p.Percent); err != nil {
return err
}
if p.NewScore, err = r.ReadInt64(13, false, p.NewScore); err != nil {
return err
}
if p.Tags, err = r.ReadString(14, false, p.Tags); err != nil {
return err
}
if p.LeftNum, err = r.ReadInt64(15, false, p.LeftNum); err != nil {
return err
}
if p.IsShowNum, err = r.ReadInt32(16, false, p.IsShowNum); err != nil {
return err
}
if p.PublishTime, err = r.ReadInt64(23, false, p.PublishTime); err != nil {
return err
}
if p.StartTime, err = r.ReadInt64(25, false, p.StartTime); err != nil {
return err
}
if p.EndTime, err = r.ReadInt64(26, false, p.EndTime); err != nil {
return err
}
if p.DailyStart, err = r.ReadString(27, false, p.DailyStart); err != nil {
return err
}
p.DailyEnd, err = r.ReadString(28, false, p.DailyEnd)
return err
}
// CategoryIDs 返回奖品归属的类目 id(tag14,逗号分隔,可能是空串)。
func (p PrizeInfo) CategoryIDs() []int64 {
text := strings.TrimSpace(p.Tags)
if text == "" {
return nil
}
parts := strings.Split(text, ",")
ids := make([]int64, 0, len(parts))
for _, part := range parts {
id, err := strconv.ParseInt(strings.TrimSpace(part), 10, 64)
if err != nil {
continue
}
ids = append(ids, id)
}
return ids
}
// InCategory 判断奖品是否属于指定类目 id。
func (p PrizeInfo) InCategory(id int64) bool {
for _, item := range p.CategoryIDs() {
if item == id {
return true
}
}
return false
}
// StartsAt 返回上架时间;未下发时返回零值时间。
func (p PrizeInfo) StartsAt() time.Time {
if p.StartTime <= 0 {
return time.Time{}
}
return time.Unix(p.StartTime, 0)
}
// EndsAt 返回下架时间;未下发时返回零值时间。
func (p PrizeInfo) EndsAt() time.Time {
if p.EndTime <= 0 {
return time.Time{}
}
return time.Unix(p.EndTime, 0)
}
// NotYetOnSale 表示商品只是「预告上架」,还没到可兑换时间。
func (p PrizeInfo) NotYetOnSale(now time.Time) bool {
start := p.StartsAt()
return !start.IsZero() && now.Before(start)
}
func (p PrizeInfo) Price() int64 {
if p.NewScore != 0 {
return p.NewScore
}
return p.Score
}
func (p PrizeInfo) EstimatedLeft() int64 {
left, ok := p.InventoryLeft()
if ok {
return left
}
return 0
}
func (p PrizeInfo) InventoryLeft() (int64, bool) {
if p.LeftNum > 0 {
return p.LeftNum, true
}
text := strings.TrimSuffix(strings.TrimSpace(p.Percent), "%")
if text == "" || p.Num <= 0 {
return 0, false
}
percent, err := strconv.ParseFloat(text, 64)
if err != nil {
return 0, false
}
return int64(math.Floor(float64(p.Num)*percent/100.0 + 1e-9)), true
}
type PrizeTag struct {
ID int64 `json:"id"`
Name string `json:"name"`
Weight int32 `json:"weight"`
}
func (t *PrizeTag) WriteTo(w *taf.Writer) error { return nil }
func (t *PrizeTag) ReadFrom(r *taf.Reader) error {
var err error
if t.ID, err = r.ReadInt64(0, false, t.ID); err != nil {
return err
}
if t.Name, err = r.ReadString(1, false, t.Name); err != nil {
return err
}
t.Weight, err = r.ReadInt32(2, false, t.Weight)
return err
}
type GetActPrizeListResp struct {
Status int32 `json:"status"`
Msg string `json:"msg"`
Prizes []PrizeInfo `json:"prizes"`
ModuleID int32 `json:"module_id"`
Tags []PrizeTag `json:"tags"`
}
func (r *GetActPrizeListResp) WriteTo(w *taf.Writer) error { return nil }
func (r *GetActPrizeListResp) ReadFrom(rd *taf.Reader) error {
var err error
if r.Status, err = rd.ReadInt32(0, false, r.Status); err != nil {
return err
}
if r.Msg, err = rd.ReadString(1, false, r.Msg); err != nil {
return err
}
items, err := rd.ReadListStruct(2, false, func() taf.Unmarshaler { return &PrizeInfo{} })
if err != nil {
return err
}
r.Prizes = make([]PrizeInfo, 0, len(items))
for _, item := range items {
if prize, ok := item.(*PrizeInfo); ok {
r.Prizes = append(r.Prizes, *prize)
}
}
if r.ModuleID, err = rd.ReadInt32(3, false, r.ModuleID); err != nil {
return err
}
tags, err := rd.ReadListStruct(4, false, func() taf.Unmarshaler { return &PrizeTag{} })
if err != nil {
return err
}
r.Tags = make([]PrizeTag, 0, len(tags))
for _, item := range tags {
if tag, ok := item.(*PrizeTag); ok {
r.Tags = append(r.Tags, *tag)
}
}
return nil
}
// CategoryStat 汇总一个类目下的商品数量与库存。
type CategoryStat struct {
ID int64 `json:"id"`
Name string `json:"name"`
Count int `json:"count"`
Left int64 `json:"left"`
Total int64 `json:"total"`
}
// CategoryName 按类目 id 查名字;字典里没有时返回空串。
func (r GetActPrizeListResp) CategoryName(id int64) string {
for _, tag := range r.Tags {
if tag.ID == id {
return tag.Name
}
}
return ""
}
// CategoriesOf 返回某个奖品所属的类目(字典里找不到的 id 用 "类目<id>" 兜底)。
func (r GetActPrizeListResp) CategoriesOf(p PrizeInfo) []PrizeTag {
ids := p.CategoryIDs()
items := make([]PrizeTag, 0, len(ids))
for _, id := range ids {
if name := r.CategoryName(id); name != "" {
items = append(items, PrizeTag{ID: id, Name: name})
continue
}
items = append(items, PrizeTag{ID: id, Name: fmt.Sprintf("类目%d", id)})
}
return items
}
// CategoryStats 按响应里的类目字典统计商品数、剩余量与总量。
//
// 统计顺序与响应下发的类目顺序一致(页面 tab 顺序),便于日志与界面直接复用。
func (r GetActPrizeListResp) CategoryStats() []CategoryStat {
stats := make([]CategoryStat, 0, len(r.Tags))
for _, tag := range r.Tags {
prizes := make([]PrizeInfo, 0, len(r.Prizes))
for _, prize := range r.Prizes {
if prize.InCategory(tag.ID) {
prizes = append(prizes, prize)
}
}
left, total, _, _ := prizeListInventorySummary(prizes)
stats = append(stats, CategoryStat{ID: tag.ID, Name: tag.Name, Count: len(prizes), Left: left, Total: total})
}
return stats
}
// CategorySummary 生成「限量返场(99)=12项/精英专享(101)=18项」这样的单行摘要,供日志使用。
func (r GetActPrizeListResp) CategorySummary() string {
stats := r.CategoryStats()
if len(stats) == 0 {
return fmt.Sprintf("未下发类目(共%d项)", len(r.Prizes))
}
parts := make([]string, 0, len(stats))
for _, stat := range stats {
parts = append(parts, fmt.Sprintf("%s(%d)=%d项", stat.Name, stat.ID, stat.Count))
}
return strings.Join(parts, "/")
}
// FilterByCategory 按类目过滤商品。
//
// category 支持:空串(不过滤)、类目 id(如 "99")、类目名(如 "限量返场",
// 先精确匹配再模糊匹配)。类目字典里没有匹配项时,退回用原始 tag14 文本匹配,
// 这样服务端新增类目但没下发字典时也不会把商品全部过滤掉。
func (r GetActPrizeListResp) FilterByCategory(category string) []PrizeInfo {
key := strings.TrimSpace(category)
if key == "" {
return r.Prizes
}
ids := map[int64]bool{}
if id, err := strconv.ParseInt(key, 10, 64); err == nil {
ids[id] = true
} else {
for _, tag := range r.Tags {
if strings.EqualFold(strings.TrimSpace(tag.Name), key) {
ids[tag.ID] = true
}
}
if len(ids) == 0 {
for _, tag := range r.Tags {
if strings.Contains(tag.Name, key) {
ids[tag.ID] = true
}
}
}
}
out := make([]PrizeInfo, 0, len(r.Prizes))
for _, prize := range r.Prizes {
if len(ids) > 0 {
matched := false
for id := range ids {
if prize.InCategory(id) {
matched = true
break
}
}
if matched {
out = append(out, prize)
}
continue
}
if strings.Contains(prize.Tags, key) {
out = append(out, prize)
}
}
return out
}
type GetUserPrizeRecordsReq struct {
User ActivityUserID
SID int32
}
func (q *GetUserPrizeRecordsReq) WriteTo(w *taf.Writer) error {
if err := w.WriteStruct(0, &q.User); err != nil {
return err
}
return w.WriteInt32(1, int64(q.SID))
}
func (q *GetUserPrizeRecordsReq) ReadFrom(r *taf.Reader) error {
_, err := r.ReadStruct(0, false, &q.User)
if err != nil {
return err
}
q.SID, err = r.ReadInt32(1, false, q.SID)
return err
}
// UserPrizeInfo 对应 getUserPrizeRecords 的一条兑换记录。
//
// 除 tag0~tag10 外,2026-10-13 HAR 实测还有 tag11~tag23:
// 12=orderId、14=奖品图、16=奖品 pid、17=商城 itemId(数量级与 shopMiddleUI
// 的 itemId 一致,语义未在样本中最终确认)、19=prizeItemId、20=扩展 map
// (game/cjm/plat/isRole/liveLinkActId)、21=兑换日期(如 20261013)。
type UserPrizeInfo struct {
ID int64 `json:"id"`
Name string `json:"name"`
Score int64 `json:"score"`
CreateTime int64 `json:"create_time"`
Status int32 `json:"status"`
Type int32 `json:"type"`
IsReal int32 `json:"is_real"`
NewScore int64 `json:"new_score"`
Account string `json:"account,omitempty"`
Code string `json:"code,omitempty"`
CommonPrizeType int32 `json:"common_prize_type"`
OrderID string `json:"order_id,omitempty"`
Image string `json:"img,omitempty"`
PrizeID int64 `json:"prize_id,omitempty"`
ShopItemID int64 `json:"shop_item_id,omitempty"`
ItemID int64 `json:"item_id,omitempty"`
Ext map[string]string `json:"ext,omitempty"`
ExchangeDate int32 `json:"exchange_date,omitempty"`
}
func (p *UserPrizeInfo) WriteTo(w *taf.Writer) error { return nil }
func (p *UserPrizeInfo) ReadFrom(r *taf.Reader) error {
var err error
if p.ID, err = r.ReadInt64(0, false, p.ID); err != nil {
return err
}
if p.Name, err = r.ReadString(1, false, p.Name); err != nil {
return err
}
if p.Score, err = r.ReadInt64(2, false, p.Score); err != nil {
return err
}
if p.CreateTime, err = r.ReadInt64(3, false, p.CreateTime); err != nil {
return err
}
if p.Status, err = r.ReadInt32(4, false, p.Status); err != nil {
return err
}
if p.Type, err = r.ReadInt32(5, false, p.Type); err != nil {
return err
}
if p.IsReal, err = r.ReadInt32(6, false, p.IsReal); err != nil {
return err
}
if p.NewScore, err = r.ReadInt64(7, false, p.NewScore); err != nil {
return err
}
if p.Account, err = r.ReadString(8, false, p.Account); err != nil {
return err
}
if p.Code, err = r.ReadString(9, false, p.Code); err != nil {
return err
}
if p.CommonPrizeType, err = r.ReadInt32(10, false, p.CommonPrizeType); err != nil {
return err
}
if p.OrderID, err = r.ReadString(12, false, p.OrderID); err != nil {
return err
}
if p.Image, err = r.ReadString(14, false, p.Image); err != nil {
return err
}
if p.PrizeID, err = r.ReadInt64(16, false, p.PrizeID); err != nil {
return err
}
if p.ShopItemID, err = r.ReadInt64(17, false, p.ShopItemID); err != nil {
return err
}
if p.ItemID, err = r.ReadInt64(19, false, p.ItemID); err != nil {
return err
}
if p.Ext, err = r.ReadMapStringString(20, false); err != nil {
return err
}
p.ExchangeDate, err = r.ReadInt32(21, false, p.ExchangeDate)
return err
}
type GetUserPrizeRecordsResp struct {
Status int32 `json:"status"`
Msg string `json:"msg"`
Records []UserPrizeInfo `json:"records"`
}
func (r *GetUserPrizeRecordsResp) WriteTo(w *taf.Writer) error { return nil }
func (r *GetUserPrizeRecordsResp) ReadFrom(rd *taf.Reader) error {
var err error
if r.Status, err = rd.ReadInt32(0, false, r.Status); err != nil {
return err
}
if r.Msg, err = rd.ReadString(1, false, r.Msg); err != nil {
return err
}
items, err := rd.ReadListStruct(2, false, func() taf.Unmarshaler { return &UserPrizeInfo{} })
if err != nil {
return err
}
r.Records = make([]UserPrizeInfo, 0, len(items))
for _, item := range items {
if record, ok := item.(*UserPrizeInfo); ok {
r.Records = append(r.Records, *record)
}
}
return nil
}
type ScoreExchangePrizeReq struct {
User ActivityUserID
SID int32
PID int32
IP string
ClientEnv map[string]string
Source string
IsRole int32
}
func (q *ScoreExchangePrizeReq) WriteTo(w *taf.Writer) error {
if err := w.WriteStruct(0, &q.User); err != nil {
return err
}
if err := w.WriteInt32(1, int64(q.SID)); err != nil {
return err
}
if err := w.WriteInt32(2, int64(q.PID)); err != nil {
return err
}
if err := w.WriteString(3, q.IP); err != nil {
return err
}
if err := w.WriteMapStringString(4, q.ClientEnv); err != nil {
return err
}
if err := w.WriteString(5, q.Source); err != nil {
return err
}
if q.IsRole == 0 {
q.IsRole = 1
}
return w.WriteInt32(6, int64(q.IsRole))
}
func (q *ScoreExchangePrizeReq) ReadFrom(r *taf.Reader) error {
_, err := r.ReadStruct(0, false, &q.User)
if err != nil {
return err
}
if q.SID, err = r.ReadInt32(1, false, q.SID); err != nil {
return err
}
if q.PID, err = r.ReadInt32(2, false, q.PID); err != nil {
return err
}
if q.IP, err = r.ReadString(3, false, q.IP); err != nil {
return err
}
if q.ClientEnv, err = r.ReadMapStringString(4, false); err != nil {
return err
}
if q.Source, err = r.ReadString(5, false, q.Source); err != nil {
return err
}
q.IsRole, err = r.ReadInt32(6, false, q.IsRole)
return err
}
type ExchangeInfo struct {
PID int32 `json:"pid"`
ExchangeScore int64 `json:"exchange_score"`
Desc string `json:"desc"`
}
func (e *ExchangeInfo) WriteTo(w *taf.Writer) error { return nil }
func (e *ExchangeInfo) ReadFrom(r *taf.Reader) error {
var err error
if e.PID, err = r.ReadInt32(1, false, e.PID); err != nil {
return err
}
if e.ExchangeScore, err = r.ReadInt64(2, false, e.ExchangeScore); err != nil {
return err
}
e.Desc, err = r.ReadString(3, false, e.Desc)
return err
}
type ActPreCondition struct {
ConditionType int32 `json:"condition_type"`
ConditionParams map[string]string `json:"condition_params,omitempty"`
Tip string `json:"tip,omitempty"`
JumpURL string `json:"jump_url,omitempty"`
ButtonDesc string `json:"button_desc,omitempty"`
}
func (c *ActPreCondition) WriteTo(w *taf.Writer) error { return nil }
func (c *ActPreCondition) ReadFrom(r *taf.Reader) error {
var err error
if c.ConditionType, err = r.ReadInt32(0, false, c.ConditionType); err != nil {
return err
}
if c.ConditionParams, err = r.ReadMapStringString(1, false); err != nil {
return err
}
if c.Tip, err = r.ReadString(2, false, c.Tip); err != nil {
return err
}
if c.JumpURL, err = r.ReadString(3, false, c.JumpURL); err != nil {
return err
}
c.ButtonDesc, err = r.ReadString(4, false, c.ButtonDesc)
return err
}
type ScoreExchangePrizeResp struct {
Status int32 `json:"status"`
Msg string `json:"msg"`
OrderID string `json:"order_id,omitempty"`
ExchangeInfo ExchangeInfo `json:"exchange_info"`
ActPreCondition ActPreCondition `json:"act_pre_condition"`
}
func (r *ScoreExchangePrizeResp) WriteTo(w *taf.Writer) error { return nil }
func (r *ScoreExchangePrizeResp) ReadFrom(rd *taf.Reader) error {
var err error
if r.Status, err = rd.ReadInt32(0, false, r.Status); err != nil {
return err
}
if r.Msg, err = rd.ReadString(1, false, r.Msg); err != nil {
return err
}
if r.OrderID, err = rd.ReadString(3, false, r.OrderID); err != nil {
return err
}
if _, err = rd.ReadStruct(4, false, &r.ExchangeInfo); err != nil {
return err
}
_, err = rd.ReadStruct(5, false, &r.ActPreCondition)
return err
}
func (r ScoreExchangePrizeResp) Success() bool {
return r.Status == 200 && (r.Msg == "" || strings.Contains(r.Msg, "成功"))
}
func prizeListInventorySummary(prizes []PrizeInfo) (left, total int64, leftKnown, totalKnown int) {
for _, prize := range prizes {
if prize.Num > 0 {
total += prize.Num
totalKnown++
}
if itemLeft, ok := prize.InventoryLeft(); ok {
left += itemLeft
leftKnown++
}
}
return left, total, leftKnown, totalKnown
}
func exchangeResponseMessage(resp *ScoreExchangePrizeResp) string {
if resp == nil {
return ""
}
for _, text := range []string{
resp.Msg,
resp.ActPreCondition.Tip,
resp.ActPreCondition.ButtonDesc,
resp.ExchangeInfo.Desc,
resp.OrderID,
} {
if strings.TrimSpace(text) != "" {
return strings.TrimSpace(text)
}
}
return ""
}