diff --git a/internal/pkg/bytesEx/appender.go b/internal/pkg/bytesEx/appender.go new file mode 100644 index 0000000..ca74da6 --- /dev/null +++ b/internal/pkg/bytesEx/appender.go @@ -0,0 +1,16 @@ +package bytesEx + +import "bytes" + +func AppendPerLine(bytesToAppend []byte, appendContent string) []byte { + parts := bytes.Split(bytesToAppend, []byte{'\n'}) + var buffer bytes.Buffer + for i, part := range parts { + buffer.Write(part) + buffer.WriteString(appendContent) + if i < len(parts)-1 { + buffer.WriteByte('\n') + } + } + return buffer.Bytes() +} diff --git a/internal/pkg/bytesEx/appender_test.go b/internal/pkg/bytesEx/appender_test.go new file mode 100644 index 0000000..7d34603 --- /dev/null +++ b/internal/pkg/bytesEx/appender_test.go @@ -0,0 +1,20 @@ +package bytesEx + +import ( + "encoding/base64" + "testing" +) + +var data = "xxxx" + +func TestAppendPerLine(t *testing.T) { + dst := make([]byte, base64.StdEncoding.DecodedLen(len(data))) + n, err := base64.StdEncoding.Decode(dst, []byte(data)) + if err != nil { + t.Fatal(err) + } + dst = dst[:n] + line := AppendPerLine(dst, "🤭fucking high") + s := base64.StdEncoding.EncodeToString(line) + t.Log(s) +} diff --git a/internal/pkg/fetcher/fetcher.go b/internal/pkg/fetcher/fetcher.go index 1557d59..f3dc5db 100644 --- a/internal/pkg/fetcher/fetcher.go +++ b/internal/pkg/fetcher/fetcher.go @@ -6,6 +6,7 @@ import ( "github.com/go-resty/resty/v2" "net/http" "raycat/internal/pkg/bytesEx" + "raycat/internal/pkg/subinfo" "time" ) @@ -30,16 +31,27 @@ func (c *Client) Fetch(baseUrl string) ([]byte, error) { if err != nil { return nil, err } + var result []byte if !bytesEx.IsBase64(resp.Body()) { - return resp.Body(), nil + result = resp.Body() + } else { + decodeLen := base64.StdEncoding.EncodedLen(len(resp.Body())) + decoded := make([]byte, decodeLen) + n, err := base64.StdEncoding.Decode(decoded, resp.Body()) + if err != nil { + return nil, err + } + result = decoded[:n] } - decodeLen := base64.StdEncoding.EncodedLen(len(resp.Body())) - decoded := make([]byte, decodeLen) - n, err := base64.StdEncoding.Decode(decoded, resp.Body()) - if err != nil { - return nil, err + // check the sub has Subscription-Userinfo + subscribeInfo := resp.Header().Get("Subscription-Userinfo") + if subscribeInfo != "" { + info, err := subinfo.ParseSubscriptionInfo(subscribeInfo) + if err == nil && info != nil { + result = bytesEx.AppendPerLine(result, info.String()) + } } - return decoded[:n], nil + return result, nil } func checkResourceAvailable(url string) bool { diff --git a/internal/pkg/subinfo/subinfo.go b/internal/pkg/subinfo/subinfo.go new file mode 100644 index 0000000..d6d236a --- /dev/null +++ b/internal/pkg/subinfo/subinfo.go @@ -0,0 +1,80 @@ +package subinfo + +import ( + "errors" + "fmt" + "strconv" + "strings" + "time" +) + +const ( + units = "KMGTPE" + unit = 1024 +) + +var ( + invalidSubscribeUserInfoError = errors.New("invalid subscribe user info ") +) + +// SubscriptionInfo 结构体用于存储解析后的信息 +type SubscriptionInfo struct { + Upload int64 + Download int64 + Total int64 + Expire time.Time +} + +// ParseSubscriptionInfo parse Subscription-Userinfo +func ParseSubscriptionInfo(info string) (*SubscriptionInfo, error) { + result := &SubscriptionInfo{} + pairs := strings.Split(info, ";") + if len(pairs) == 0 { + return nil, invalidSubscribeUserInfoError + } + for _, pair := range pairs { + pair = strings.TrimSpace(pair) + kv := strings.SplitN(pair, "=", 2) + if len(kv) != 2 { + continue + } + key := strings.TrimSpace(kv[0]) + value := strings.TrimSpace(kv[1]) + + switch key { + case "upload": + result.Upload, _ = strconv.ParseInt(value, 10, 64) + case "download": + result.Download, _ = strconv.ParseInt(value, 10, 64) + case "total": + result.Total, _ = strconv.ParseInt(value, 10, 64) + case "expire": + expireTime, err := strconv.ParseInt(value, 10, 64) + if err == nil { + result.Expire = time.Unix(expireTime, 0) + } + } + } + return result, nil +} + +func formatBytes(bytes int64) string { + if bytes < unit { + return fmt.Sprintf("%d B", bytes) + } + div, exp := int64(unit), 0 + for n := bytes / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), units[exp]) +} + +// String impl Stringer to display +func (si *SubscriptionInfo) String() string { + return fmt.Sprintf("ℹ Upload: %s Download: %s Total: %s ExpireAt: %s", + formatBytes(si.Upload), + formatBytes(si.Download), + formatBytes(si.Total), + si.Expire.Format("2006-01-02 15:04")) +}