From 6503f7f0c7698a1b0018003739389bb3ef2bdc22 Mon Sep 17 00:00:00 2001
From: Tenko <IJNKAWAKAZE@protonmail.com>
Date: Wed, 3 Apr 2024 11:11:55 +0800
Subject: [PATCH] =?UTF-8?q?=E9=83=BD=E6=94=B9=E4=BA=86=E4=BB=80=E4=B9=88?=
 =?UTF-8?q?=E4=B8=9C=E8=A5=BF=E5=95=8A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/plugins/sign/auto_sign.go            |  11 +-
 src/plugins/sign/sign_handle.go          |  10 +-
 src/plugins/skland/auth.go               |  12 +-
 src/plugins/skland/config.go             |  22 ----
 src/plugins/skland/game_sign.go          |  67 ++++++++++
 src/plugins/skland/generate_sign.go      |  59 +++++++++
 src/plugins/skland/hypergryph_request.go | 120 +-----------------
 src/plugins/skland/player_info.go        |   5 -
 src/plugins/skland/player_redeem.go      |   2 +-
 src/plugins/skland/sign.go               | 149 -----------------------
 src/plugins/skland/skland_request.go     |  58 +++++++++
 11 files changed, 199 insertions(+), 316 deletions(-)
 delete mode 100644 src/plugins/skland/config.go
 create mode 100644 src/plugins/skland/game_sign.go
 create mode 100644 src/plugins/skland/generate_sign.go
 delete mode 100644 src/plugins/skland/sign.go
 create mode 100644 src/plugins/skland/skland_request.go

diff --git a/src/plugins/sign/auto_sign.go b/src/plugins/sign/auto_sign.go
index c639ffa..1a210a6 100644
--- a/src/plugins/sign/auto_sign.go
+++ b/src/plugins/sign/auto_sign.go
@@ -48,13 +48,8 @@ func sign(user UserSign) {
 				skAccount.Skland.Token = userAccount.SklandToken
 				skAccount.Skland.Cred = userAccount.SklandCred
 
-				var skPlayer skland.Player
-				skPlayer.NickName = player.PlayerName
-				skPlayer.ChannelName = player.ServerName
-				skPlayer.Uid = player.Uid
-
 				// 执行签到
-				record, err := skland.SignGamePlayer(&skPlayer, skAccount)
+				award, hasSigned, err := skland.SignGamePlayer(player.Uid, skAccount)
 				if err != nil {
 					// 签到失败
 					sendMessage := tgbotapi.NewMessage(user.UserNumber, fmt.Sprintf("角色 %s 签到失败!\nmsg:%s", player.PlayerName, err.Error()))
@@ -63,13 +58,13 @@ func sign(user UserSign) {
 					return
 				}
 				// 今日已完成签到
-				if record.HasSigned {
+				if hasSigned {
 					sendMessage := tgbotapi.NewMessage(user.UserNumber, fmt.Sprintf("角色 %s 今天已经签到过了", player.PlayerName))
 					bot.Arknights.Send(sendMessage)
 					return
 				}
 				// 签到成功
-				sendMessage := tgbotapi.NewMessage(user.UserNumber, fmt.Sprintf("角色 %s 签到成功!\n今日奖励:%s", player.PlayerName, record.Award))
+				sendMessage := tgbotapi.NewMessage(user.UserNumber, fmt.Sprintf("角色 %s 签到成功!\n今日奖励:%s", player.PlayerName, award))
 				bot.Arknights.Send(sendMessage)
 			}
 		}
diff --git a/src/plugins/sign/sign_handle.go b/src/plugins/sign/sign_handle.go
index 4d88644..760d35f 100644
--- a/src/plugins/sign/sign_handle.go
+++ b/src/plugins/sign/sign_handle.go
@@ -80,12 +80,8 @@ func SignHandle(update tgbotapi.Update) error {
 }
 
 func Sign(player account.UserPlayer, account account.UserAccount, chatId int64) error {
-	var skPlayer skland.Player
 	var skAccount skland.Account
 	playerName := player.PlayerName
-	skPlayer.NickName = playerName
-	skPlayer.ChannelName = player.ServerName
-	skPlayer.Uid = player.Uid
 	skAccount.Hypergryph.Token = account.HypergryphToken
 	skAccount.Skland.Token = account.SklandToken
 	skAccount.Skland.Cred = account.SklandCred
@@ -93,7 +89,7 @@ func Sign(player account.UserPlayer, account account.UserAccount, chatId int64)
 	sendAction := tgbotapi.NewChatAction(chatId, "typing")
 	bot.Arknights.Send(sendAction)
 
-	record, err := skland.SignGamePlayer(&skPlayer, skAccount)
+	award, hasSigned, err := skland.SignGamePlayer(player.Uid, skAccount)
 	if err != nil {
 		sendMessage := tgbotapi.NewMessage(chatId, fmt.Sprintf("角色 %s 签到失败!\nmsg:%s", playerName, err.Error()))
 		msg, _ := bot.Arknights.Send(sendMessage)
@@ -102,13 +98,13 @@ func Sign(player account.UserPlayer, account account.UserAccount, chatId int64)
 		return err
 	}
 	// 今日已完成签到
-	if record.HasSigned {
+	if hasSigned {
 		sendMessage := tgbotapi.NewMessage(chatId, fmt.Sprintf("角色 %s 今天已经签到过了", playerName))
 		bot.Arknights.Send(sendMessage)
 		return nil
 	}
 	// 签到成功
-	sendMessage := tgbotapi.NewMessage(chatId, fmt.Sprintf("角色 %s 签到成功!\n今日奖励:%s", playerName, record.Award))
+	sendMessage := tgbotapi.NewMessage(chatId, fmt.Sprintf("角色 %s 签到成功!\n今日奖励:%s", playerName, award))
 	bot.Arknights.Send(sendMessage)
 	return nil
 }
diff --git a/src/plugins/skland/auth.go b/src/plugins/skland/auth.go
index e17c4c5..e979c74 100644
--- a/src/plugins/skland/auth.go
+++ b/src/plugins/skland/auth.go
@@ -64,7 +64,7 @@ func Login(token string) (Account, error) {
 	}
 	account.Hypergryph.Token = token
 
-	res, err := grantApp(token, AppCodeSKLAND)
+	res, err := grantApp(token, "4ca99fa6b56cc2ba")
 	if err != nil {
 		return account, fmt.Errorf("grant app error: %w", err)
 	}
@@ -127,12 +127,12 @@ func RefreshToken(account Account) (Account, error) {
 	return account, nil
 }
 
-// 获取用户信息
+// GetUser 获取用户信息
 func GetUser(skland AccountSkland) (*User, error) {
 	return SklandRequest[*User](SKR(), "GET", "/api/v1/user", skland)
 }
 
-// 检查token有效性
+// CheckToken 检查token有效性
 func CheckToken(token string) error {
 	req := SKR().SetQueryParam("token", token)
 	_, err := HypergryphRequest[any](req, "GET", "/user/info/v1/basic")
@@ -177,9 +177,3 @@ func ArknightsPlayers(skland AccountSkland) ([]*Player, error) {
 	}
 	return players, nil
 }
-
-// GenTokenByUid 根据Oauth token和uid生成应用token
-func GenTokenByUid(uid string, token string) (*GenTokenByUidData, error) {
-	req := HR().SetBody(gh.M{"uid": uid, "token": token})
-	return HypergryphBindingAPIRequest[*GenTokenByUidData](req, "POST", "/account/binding/v1/token_by_uid")
-}
diff --git a/src/plugins/skland/config.go b/src/plugins/skland/config.go
deleted file mode 100644
index a6fdb99..0000000
--- a/src/plugins/skland/config.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package skland
-
-const (
-	HypergryphAddr           = "https://as.hypergryph.com"
-	HypergryphAKAddr         = "https://ak.hypergryph.com"
-	HypergryphBindingAPIAddr = "https://binding-api-account-prod.hypergryph.com"
-	HypergryphUserAgent      = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
-	AppCodeSKLAND            = "4ca99fa6b56cc2ba"
-
-	SklandAddr      = "https://zonai.skland.com"
-	SklandUserAgent = "Skland/1.5.1 (com.hypergryph.skland; build:100501001; Android 33; ) Okhttp/4.11.0"
-	Platform        = "1"
-	VName           = "1.5.1"
-	DId             = "743a446c83032899"
-
-	GameCodeArknights    = "1"   // 明日方舟
-	GameCodeExastris     = "2"   // 来自星尘
-	GameCodeEndfleld     = "3"   // 终末地
-	GameCodePopucom      = "4"   // 泡姆泡姆
-	GameCodeNest         = "100" // 纳斯特港
-	GameAppCodeArknights = "arknights"
-)
diff --git a/src/plugins/skland/game_sign.go b/src/plugins/skland/game_sign.go
new file mode 100644
index 0000000..af2a843
--- /dev/null
+++ b/src/plugins/skland/game_sign.go
@@ -0,0 +1,67 @@
+package skland
+
+import (
+	"fmt"
+	"github.com/starudream/go-lib/core/v2/gh"
+	"github.com/starudream/go-lib/resty/v2"
+	"strconv"
+	"strings"
+)
+
+type SignGameData struct {
+	Ts     string         `json:"ts"`
+	Awards SignGameAwards `json:"awards"`
+}
+
+type SignGameAward struct {
+	Type     string       `json:"type"`
+	Count    int          `json:"count"`
+	Resource *SignGameRes `json:"resource"`
+}
+
+type SignGameRes struct {
+	Id     string `json:"id"`
+	Type   string `json:"type"`
+	Name   string `json:"name"`
+	Rarity int    `json:"rarity"`
+}
+
+type SignGameAwards []*SignGameAward
+
+func SignGamePlayer(uid string, account Account) (award string, hasSigned bool, err error) {
+	account, err = RefreshToken(account)
+	if err != nil {
+		return
+	}
+	signGameData, err := signGame("1", uid, account.Skland)
+	if err != nil {
+		e, ok1 := resty.AsRespErr(err)
+		if ok1 {
+			t, ok2 := e.Response.Error().(*SKBaseResp[interface{}])
+			if ok2 && t.Message == "请勿重复签到!" {
+				err = nil
+				hasSigned = true
+			}
+		} else {
+			err = fmt.Errorf("sign game error: %w", err)
+			return
+		}
+	} else {
+		award = signGameData.Awards.shortString()
+	}
+	return
+}
+
+// 签到
+func signGame(gid, uid string, skland AccountSkland) (*SignGameData, error) {
+	req := SKR().SetBody(gh.M{"gameId": gid, "uid": uid})
+	return SklandRequest[*SignGameData](req, "POST", "/api/v1/game/attendance", skland)
+}
+
+func (t SignGameAwards) shortString() string {
+	v := make([]string, len(t))
+	for i, a := range t {
+		v[i] = a.Resource.Name + "*" + strconv.Itoa(a.Count)
+	}
+	return strings.Join(v, ", ")
+}
diff --git a/src/plugins/skland/generate_sign.go b/src/plugins/skland/generate_sign.go
new file mode 100644
index 0000000..6e9e119
--- /dev/null
+++ b/src/plugins/skland/generate_sign.go
@@ -0,0 +1,59 @@
+package skland
+
+import (
+	"crypto/hmac"
+	"crypto/md5"
+	"crypto/sha256"
+	"encoding/hex"
+	"github.com/starudream/go-lib/core/v2/codec/json"
+	"github.com/starudream/go-lib/core/v2/utils/structutil"
+	"github.com/starudream/go-lib/resty/v2"
+	"net/url"
+	"strconv"
+	"time"
+)
+
+func addSign(r *resty.Request, method, path string, skland AccountSkland) {
+	ts := strconv.FormatInt(time.Now().Unix(), 10)
+
+	headers := signHeaders{Platform: "1", Timestamp: ts, DId: "743a446c83032899", VName: "1.5.1"}
+
+	r.SetHeaders(tom(headers))
+
+	_, signature := sign(headers, method, path, skland.Token, r.QueryParam, r.Body)
+
+	r.SetHeader("cred", skland.Cred)
+	r.SetHeader("sign", signature)
+}
+
+func tom(s any) map[string]string {
+	t := structutil.New(s)
+	t.TagName = "json"
+	m := map[string]string{}
+	for k, v := range t.Map() {
+		m[k] = v.(string)
+	}
+	return m
+}
+
+func sign(headers signHeaders, method, path, token string, query url.Values, body any) (string, string) {
+	str := query.Encode()
+	if method != "GET" {
+		str = json.MustMarshalString(body)
+	}
+
+	content := path + str + headers.Timestamp + json.MustMarshalString(headers)
+
+	b1 := hmac256(token, content)
+	s1 := hex.EncodeToString(b1)
+	b2 := md5.Sum([]byte(s1))
+	s2 := hex.EncodeToString(b2[:])
+
+	return content, s2
+}
+
+func hmac256(key, content string) []byte {
+	h := hmac.New(sha256.New, []byte(key))
+	h.Write([]byte(content))
+	return h.Sum(nil)
+}
diff --git a/src/plugins/skland/hypergryph_request.go b/src/plugins/skland/hypergryph_request.go
index ef62b74..e6fb108 100644
--- a/src/plugins/skland/hypergryph_request.go
+++ b/src/plugins/skland/hypergryph_request.go
@@ -1,19 +1,14 @@
 package skland
 
 import (
-	"crypto/hmac"
-	"crypto/md5"
-	"crypto/sha256"
-	"encoding/hex"
 	"fmt"
-	"github.com/starudream/go-lib/core/v2/codec/json"
-	"github.com/starudream/go-lib/core/v2/utils/structutil"
+	"github.com/spf13/viper"
 	"github.com/starudream/go-lib/resty/v2"
-	"net/url"
-	"strconv"
-	"time"
 )
 
+var HypergryphAddr = "https://as.hypergryph.com"
+var HypergryphAKAddr = "https://ak.hypergryph.com"
+
 type HBaseResp[T any] struct {
 	StatusCode *int   `json:"statusCode"`
 	Error      string `json:"error"`
@@ -26,12 +21,6 @@ type HBaseResp[T any] struct {
 	Data T `json:"data,omitempty"`
 }
 
-type SKBaseResp[T any] struct {
-	Code    *int   `json:"code"`
-	Message string `json:"message"`
-	Data    T      `json:"data,omitempty"`
-}
-
 type signHeaders struct {
 	Platform  string `json:"platform"`
 	Timestamp string `json:"timestamp"`
@@ -43,10 +32,6 @@ func (t *HBaseResp[T]) IsSuccess() bool {
 	return t != nil && t.Status != nil && *t.Status == 0
 }
 
-func (t *SKBaseResp[T]) IsSuccess() bool {
-	return t != nil && t.Code != nil && *t.Code == 0
-}
-
 func (t *HBaseResp[T]) String() string {
 	if t != nil && t.StatusCode != nil {
 		return fmt.Sprintf("status: %d, error: %s, message: %s", *t.StatusCode, t.Error, t.Message)
@@ -56,16 +41,8 @@ func (t *HBaseResp[T]) String() string {
 	return "<nil>"
 }
 
-func (t *SKBaseResp[T]) String() string {
-	return fmt.Sprintf("code: %d, message: %s", *t.Code, t.Message)
-}
-
 func HR() *resty.Request {
-	return resty.R().SetHeader("User-Agent", HypergryphUserAgent).SetHeader("Accept-Encoding", "gzip")
-}
-
-func SKR() *resty.Request {
-	return resty.R().SetHeader("User-Agent", SklandUserAgent).SetHeader("Accept-Encoding", "gzip")
+	return resty.R().SetHeader("User-Agent", viper.GetString("api.user_agent")).SetHeader("Accept-Encoding", "gzip")
 }
 
 func IsUnauthorized(err error) bool {
@@ -86,16 +63,6 @@ func HypergryphRequest[T any](r *resty.Request, method, path string) (t T, _ err
 	return res.Data, nil
 }
 
-func HypergryphBindingAPIRequest[T any](r *resty.Request, method, path string) (t T, _ error) {
-	res, err := resty.ParseResp[*HBaseResp[any], *HBaseResp[T]](
-		r.SetError(&HBaseResp[any]{}).SetResult(&HBaseResp[T]{}).Execute(method, HypergryphBindingAPIAddr+path),
-	)
-	if err != nil {
-		return t, fmt.Errorf("[hypergryph] %w", err)
-	}
-	return res.Data, nil
-}
-
 func HypergryphAKRequest(r *resty.Request, method, path string) (d string, _ error) {
 	res, err := r.Execute(method, HypergryphAKAddr+path)
 	if err != nil {
@@ -103,80 +70,3 @@ func HypergryphAKRequest(r *resty.Request, method, path string) (d string, _ err
 	}
 	return string(res.Body()), nil
 }
-
-func SklandRequest[T any](r *resty.Request, method, path string, vs ...any) (t T, _ error) {
-	for i := 0; i < len(vs); i++ {
-		switch v := vs[i].(type) {
-		case AccountSkland:
-			addSign(r, method, path, v)
-		}
-	}
-
-	res, err := resty.ParseResp[*SKBaseResp[any], *SKBaseResp[T]](
-		r.SetError(&SKBaseResp[any]{}).SetResult(&SKBaseResp[T]{}).Execute(method, SklandAddr+path),
-	)
-	if err != nil {
-		return t, fmt.Errorf("[skland] %w", err)
-	}
-	return res.Data, nil
-}
-
-func SklandRequestPlayerData(r *resty.Request, method, path string, vs ...any) (d string, _ error) {
-	for i := 0; i < len(vs); i++ {
-		switch v := vs[i].(type) {
-		case AccountSkland:
-			addSign(r, method, path, v)
-		}
-	}
-
-	res, err := r.Execute(method, SklandAddr+path)
-	if err != nil {
-		return d, fmt.Errorf("[skland] %w", err)
-	}
-	return string(res.Body()), nil
-}
-
-func addSign(r *resty.Request, method, path string, skland AccountSkland) {
-	ts := strconv.FormatInt(time.Now().Unix(), 10)
-
-	headers := signHeaders{Platform: Platform, Timestamp: ts, DId: DId, VName: VName}
-
-	r.SetHeaders(tom(headers))
-
-	_, signature := sign(headers, method, path, skland.Token, r.QueryParam, r.Body)
-
-	r.SetHeader("cred", skland.Cred)
-	r.SetHeader("sign", signature)
-}
-
-func tom(s any) map[string]string {
-	t := structutil.New(s)
-	t.TagName = "json"
-	m := map[string]string{}
-	for k, v := range t.Map() {
-		m[k] = v.(string)
-	}
-	return m
-}
-
-func sign(headers signHeaders, method, path, token string, query url.Values, body any) (string, string) {
-	str := query.Encode()
-	if method != "GET" {
-		str = json.MustMarshalString(body)
-	}
-
-	content := path + str + headers.Timestamp + json.MustMarshalString(headers)
-
-	b1 := hmac256(token, content)
-	s1 := hex.EncodeToString(b1)
-	b2 := md5.Sum([]byte(s1))
-	s2 := hex.EncodeToString(b2[:])
-
-	return content, s2
-}
-
-func hmac256(key, content string) []byte {
-	h := hmac.New(sha256.New, []byte(key))
-	h.Write([]byte(content))
-	return h.Sum(nil)
-}
diff --git a/src/plugins/skland/player_info.go b/src/plugins/skland/player_info.go
index c3550e1..7ed77eb 100644
--- a/src/plugins/skland/player_info.go
+++ b/src/plugins/skland/player_info.go
@@ -24,11 +24,6 @@ func GetPlayerInfo(uid string, account Account) (*PlayerData, Account, error) {
 	return playerData, account, nil
 }
 
-func getPlayerInfo(uid string, skland AccountSkland) (*PlayerData, error) {
-	req := SKR().SetQueryParams(gh.MS{"uid": uid})
-	return SklandRequest[*PlayerData](req, "GET", "/api/v1/game/player/info", skland)
-}
-
 func getPlayerInfoStr(uid string, skland AccountSkland) (string, error) {
 	req := SKR().SetQueryParams(gh.MS{"uid": uid})
 	return SklandRequestPlayerData(req, "GET", "/api/v1/game/player/info", skland)
diff --git a/src/plugins/skland/player_redeem.go b/src/plugins/skland/player_redeem.go
index 8b06e48..e149982 100644
--- a/src/plugins/skland/player_redeem.go
+++ b/src/plugins/skland/player_redeem.go
@@ -51,7 +51,7 @@ func getPlayerRedeem(token, cdk, channelId string) (string, error) {
 		}
 	}
 	headers["X-Csrf-Token"] = csrfToken
-	req1 := SKR().SetHeaders(headers).SetBody(gh.M{"token": token, "giftCode": cdk, "channelId": channelId}).SetCookies(resp.Cookies())
+	req1 := HR().SetHeaders(headers).SetBody(gh.M{"token": token, "giftCode": cdk, "channelId": channelId}).SetCookies(resp.Cookies())
 	res, err := HypergryphAKRequest(req1, "POST", path)
 	if err != nil {
 		return "", fmt.Errorf("发送兑换请求失败")
diff --git a/src/plugins/skland/sign.go b/src/plugins/skland/sign.go
deleted file mode 100644
index cada178..0000000
--- a/src/plugins/skland/sign.go
+++ /dev/null
@@ -1,149 +0,0 @@
-package skland
-
-import (
-	"fmt"
-	"github.com/starudream/go-lib/core/v2/gh"
-	"strconv"
-	"strings"
-	"time"
-)
-
-var signGameCodeByAppCode = map[string]string{
-	GameAppCodeArknights: GameCodeArknights,
-}
-
-type SignGameData struct {
-	Ts     string         `json:"ts"`
-	Awards SignGameAwards `json:"awards"`
-}
-
-type SignGameRecord struct {
-	GameId        string
-	GameName      string
-	PlayerName    string
-	PlayerUid     string
-	PlayerChannel string
-	HasSigned     bool
-	Award         string
-}
-
-type ListAttendanceData struct {
-	CurrentTs       string                  `json:"currentTs"`
-	Calendar        []*Calendar             `json:"calendar"`
-	Records         CalendarRecords         `json:"records"`
-	ResourceInfoMap map[string]*SignGameRes `json:"resourceInfoMap"`
-}
-
-type SignGameAward struct {
-	Type     string       `json:"type"`
-	Count    int          `json:"count"`
-	Resource *SignGameRes `json:"resource"`
-}
-
-type SignGameRes struct {
-	Id     string `json:"id"`
-	Type   string `json:"type"`
-	Name   string `json:"name"`
-	Rarity int    `json:"rarity"`
-}
-
-type CalendarRecord struct {
-	Ts         string `json:"ts"`
-	ResourceId string `json:"resourceId"`
-	Type       string `json:"type"`
-	Count      int    `json:"count"`
-}
-
-type Calendar struct {
-	ResourceId string `json:"resourceId"`
-	Type       string `json:"type"`
-	Count      int    `json:"count"`
-	Available  bool   `json:"available"`
-	Done       bool   `json:"done"`
-}
-
-type CalendarRecords []*CalendarRecord
-type SignGameAwards []*SignGameAward
-
-func SignGamePlayer(player *Player, account Account) (record SignGameRecord, err error) {
-	account, err = RefreshToken(account)
-	if err != nil {
-		return
-	}
-	record.GameName = "明日方舟"
-	record.PlayerName = player.NickName
-	record.PlayerUid = player.Uid
-	record.PlayerChannel = player.ChannelName
-
-	gameId := signGameCodeByAppCode["arknights"]
-
-	record.GameId = gameId
-
-	/*list, err := listSignGame(gameId, player.Uid, account.Skland)
-	if err != nil {
-		err = fmt.Errorf("list sign game error: %w", err)
-		return
-	}
-
-	today := list.Records.today()
-	if len(today) > 0 {
-		record.HasSigned = true
-		record.Award = today.shortString(list.ResourceInfoMap)
-		return
-	}*/
-
-	signGameData, err := signGame(gameId, player.Uid, account.Skland)
-	if err != nil {
-		if err.Error() == "[skland] response status: 403 Forbidden, error: code: 10001, message: 请勿重复签到!" {
-			err = nil
-			record.HasSigned = true
-		} else {
-			err = fmt.Errorf("sign game error: %w", err)
-			return
-		}
-	} else {
-		record.Award = signGameData.Awards.shortString()
-	}
-
-	return
-}
-
-// 签到信息
-func listSignGame(gid, uid string, skland AccountSkland) (*ListAttendanceData, error) {
-	req := SKR().SetQueryParams(gh.MS{"gameId": gid, "uid": uid})
-	return SklandRequest[*ListAttendanceData](req, "GET", "/api/v1/game/attendance", skland)
-}
-
-// 签到
-func signGame(gid, uid string, skland AccountSkland) (*SignGameData, error) {
-	req := SKR().SetBody(gh.M{"gameId": gid, "uid": uid})
-	return SklandRequest[*SignGameData](req, "POST", "/api/v1/game/attendance", skland)
-}
-
-func (v1 CalendarRecords) today() (v2 CalendarRecords) {
-	now := time.Now()
-	zero := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
-	zeroTs := strconv.FormatInt(zero.Unix(), 10)
-	for _, r := range v1 {
-		if r.Ts == zeroTs {
-			v2 = append(v2, r)
-		}
-	}
-	return
-}
-
-func (v1 CalendarRecords) shortString(m map[string]*SignGameRes) string {
-	v2 := make([]string, len(v1))
-	for i, v := range v1 {
-		v2[i] = m[v.ResourceId].Name + "*" + strconv.Itoa(v.Count)
-	}
-	return strings.Join(v2, ", ")
-}
-
-func (t SignGameAwards) shortString() string {
-	v := make([]string, len(t))
-	for i, a := range t {
-		v[i] = a.Resource.Name + "*" + strconv.Itoa(a.Count)
-	}
-	return strings.Join(v, ", ")
-}
diff --git a/src/plugins/skland/skland_request.go b/src/plugins/skland/skland_request.go
new file mode 100644
index 0000000..0c3b2e6
--- /dev/null
+++ b/src/plugins/skland/skland_request.go
@@ -0,0 +1,58 @@
+package skland
+
+import (
+	"fmt"
+	"github.com/starudream/go-lib/resty/v2"
+)
+
+var sklandAddr = "https://zonai.skland.com"
+
+type SKBaseResp[T any] struct {
+	Code    *int   `json:"code"`
+	Message string `json:"message"`
+	Data    T      `json:"data,omitempty"`
+}
+
+func (t *SKBaseResp[T]) IsSuccess() bool {
+	return t != nil && t.Code != nil && *t.Code == 0
+}
+
+func (t *SKBaseResp[T]) String() string {
+	return fmt.Sprintf("code: %d, message: %s", *t.Code, t.Message)
+}
+
+func SKR() *resty.Request {
+	return resty.R().SetHeader("User-Agent", "Skland/1.5.1 (com.hypergryph.skland; build:100501001; Android 33; ) Okhttp/4.11.0").SetHeader("Accept-Encoding", "gzip")
+}
+
+func SklandRequest[T any](r *resty.Request, method, path string, vs ...any) (t T, _ error) {
+	for i := 0; i < len(vs); i++ {
+		switch v := vs[i].(type) {
+		case AccountSkland:
+			addSign(r, method, path, v)
+		}
+	}
+
+	res, err := resty.ParseResp[*SKBaseResp[any], *SKBaseResp[T]](
+		r.SetError(&SKBaseResp[any]{}).SetResult(&SKBaseResp[T]{}).Execute(method, sklandAddr+path),
+	)
+	if err != nil {
+		return t, fmt.Errorf("[skland] %w", err)
+	}
+	return res.Data, nil
+}
+
+func SklandRequestPlayerData(r *resty.Request, method, path string, vs ...any) (d string, _ error) {
+	for i := 0; i < len(vs); i++ {
+		switch v := vs[i].(type) {
+		case AccountSkland:
+			addSign(r, method, path, v)
+		}
+	}
+
+	res, err := r.Execute(method, sklandAddr+path)
+	if err != nil {
+		return d, fmt.Errorf("[skland] %w", err)
+	}
+	return string(res.Body()), nil
+}