Files
go_win_template/internal/license/license.go
Geliebte e266d3098a feat: 抖音直播间弹幕监控 - Go+Wails 实现
后端:
- SQLite 数据层 (rooms/users/chat_logs/settings/error_logs)
- 抖音 WebSocket 抓取器 (proto 解析, 心跳, watchdog)
- __ac_signature 算法 (纯 Go 移植)
- a_bogus/sign.js 签名 (goja JS 引擎)
- Echo HTTP API 服务器 (端口 5000)
- SSE 事件广播
- 自动保活 + 授权守护
- PAYS 云授权对接

前端:
- 顶栏: 品牌 + 日志/调试/客户名单/授权/文档/房间控制/主题切换
- 添加直播间 (链接解析 + 关键词)
-  Dashboard (卡片/列表视图, 公开/匿名房筛选, 分页)
- 详情页 (多 tab 消息流, 右侧用户面板, 颜色标记, 精准客户, 已看标记)
- 精准客户名单 (跨房间汇总)
- 订阅授权页面 (登录/注册/购买)
- 使用帮助 + 系统调试
- 白昼/黑夜主题切换 (CSS 变量 + iconfont)
- 底部版权栏
2026-09-18 04:25:57 +08:00

344 lines
9.7 KiB
Go
Raw Blame History

package license
import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"os"
"runtime"
"strings"
"time"
"github.com/google/uuid"
"github.com/qingwuyingxing/douyin-live-fetcher/internal/db"
)
const (
DefaultServer = "http://42.193.21.75:7777"
DefaultProductCode = "douyin-live-fetcher"
)
const (
keyServerURL = "license_server_url"
keyAccountToken = "license_account_token"
keyCustomerCode = "license_customer_code"
keyProductCode = "license_product_code"
keySessionToken = "license_session_token"
keyLastCheckAt = "license_last_check_at"
keyLastAllowed = "license_last_allowed"
keyLastStatus = "license_last_status"
keyCustomerName = "license_customer_name"
keyProductName = "license_product_name"
keyStartAt = "license_start_at"
keyEndAt = "license_end_at"
keyPaymentOpts = "license_payment_options"
keyDeviceID = "device_id"
)
const checkInterval = 300
func LicenseConfig() map[string]string {
return map[string]string{
"server_url": strings.TrimRight(funcWithDefault(keyServerURL, DefaultServer), "/"),
"account_token": strings.TrimSpace(funcWithDefault(keyAccountToken, "")),
"customer_code": strings.TrimSpace(funcWithDefault(keyCustomerCode, "")),
"product_code": strings.TrimSpace(funcWithDefault(keyProductCode, DefaultProductCode)),
"session_token": strings.TrimSpace(funcWithDefault(keySessionToken, "")),
"device_id": DeviceID(),
}
}
func SaveLicenseConfig(server, customerCode, productCode, accountToken string) {
db.SetSetting(keyServerURL, strings.TrimRight(server, "/"))
db.SetSetting(keyCustomerCode, strings.TrimSpace(customerCode))
db.SetSetting(keyProductCode, strings.TrimSpace(productCode))
db.SetSetting(keyAccountToken, strings.TrimSpace(accountToken))
}
func ClearLicenseLogin() {
for _, k := range []string{
keyAccountToken, keyCustomerCode, keySessionToken,
keyLastCheckAt, keyLastAllowed, keyLastStatus,
keyCustomerName, keyProductName, keyStartAt, keyEndAt, keyPaymentOpts,
} {
db.SetSetting(k, "")
}
}
func LoginAccount(server, login, password string) (map[string]interface{}, error) {
base := strings.TrimRight(server, "/")
payload, _ := json.Marshal(map[string]string{"login": login, "password": password})
resp, err := http.Post(base+"/api/yser/public/auth/login", "application/json", io.NopCloser(strings.NewReader(string(payload))))
if err != nil {
return nil, fmt.Errorf("login request failed: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result map[string]interface{}
json.Unmarshal(body, &result)
if code, _ := result["code"].(float64); code != 0 {
return nil, fmt.Errorf("%v", result["message"])
}
data, _ := result["data"].(map[string]interface{})
token, _ := data["api_token"].(string)
if token == "" {
return nil, fmt.Errorf("login success but no token returned")
}
customer, _ := data["customer"].(map[string]interface{})
customerCode, _ := customer["code"].(string)
if customerCode == "" {
if vc, ok := data["customer_code"].(string); ok {
customerCode = vc
}
}
if customerCode == "" {
return nil, fmt.Errorf("login success but no customer code returned")
}
cfg := LicenseConfig()
SaveLicenseConfig(base, customerCode, cfg["product_code"], token)
return data, nil
}
func CheckCloudLicense(force bool) map[string]interface{} {
cfg := LicenseConfig()
if cfg["account_token"] == "" && cfg["customer_code"] == "" {
return map[string]interface{}{"configured": false, "allowed": true, "state": "unconfigured", "note": "登录订阅账号以解锁全部功能"}
}
now := time.Now().Unix()
lastAt := int64(0)
if v := funcWithDefault(keyLastCheckAt, "0"); v != "" {
fmt.Sscanf(v, "%d", &lastAt)
}
cached := buildCached(cfg, lastAt)
if !force && lastAt > 0 && now-lastAt < checkInterval {
return cached
}
endpoint := "/api/yser/license/check"
payload := map[string]interface{}{
"product_code": cfg["product_code"],
"device_id": cfg["device_id"],
}
if cfg["account_token"] != "" {
endpoint = "/api/yser/account/license/check"
payload["account_token"] = cfg["account_token"]
payload["device_name"] = getHostname()
payload["fingerprint"] = runtime.GOOS + "/" + runtime.GOARCH
payload["session_token"] = cfg["session_token"]
} else {
payload["customer_code"] = cfg["customer_code"]
payload["hostname"] = getHostname()
payload["platform"] = runtime.GOOS + "/" + runtime.GOARCH
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", cfg["server_url"]+endpoint, strings.NewReader(string(body)))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "DouyinLiveFetcher/1.0.0")
client := &http.Client{Timeout: 8 * time.Second}
resp, err := client.Do(req)
if err != nil {
endTS := parseTS(cached["end_at"].(string))
if cached["allowed"].(bool) && (endTS == 0 || endTS > now) {
cached["note"] = "暂时无法连接授权服务器,使用上次有效授权"
cached["offline"] = true
return cached
}
cached["allowed"] = false
cached["state"] = "offline"
cached["note"] = "授权服务器连接失<E68EA5>? " + err.Error()
return cached
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var r map[string]interface{}
json.Unmarshal(respBody, &r)
info, _ := r["data"].(map[string]interface{})
allowed := info["allowed"] == true || info["allowed"] == 1
stateVal, _ := info["status"].(string)
if stateVal == "" {
if allowed {
stateVal = "active"
} else {
stateVal = "suspended"
}
}
db.SetSetting(keyLastCheckAt, fmt.Sprintf("%d", now))
db.SetSetting(keyLastAllowed, fmt.Sprintf("%d", btoi(allowed)))
db.SetSetting(keyLastStatus, stateVal)
if v, ok := info["customer_name"].(string); ok {
db.SetSetting(keyCustomerName, v)
}
if v, ok := info["product_name"].(string); ok {
db.SetSetting(keyProductName, v)
}
if v, ok := info["start_at"].(string); ok {
db.SetSetting(keyStartAt, v)
}
if v, ok := info["end_at"].(string); ok {
db.SetSetting(keyEndAt, v)
}
if opts, ok := info["payment_options"].([]interface{}); ok {
ob, _ := json.Marshal(opts)
db.SetSetting(keyPaymentOpts, string(ob))
}
if v, ok := info["session_token"].(string); ok && v != "" {
db.SetSetting(keySessionToken, v)
}
cached["allowed"] = allowed
cached["state"] = stateVal
cached["checked_at"] = now
if v, ok := info["customer_name"].(string); ok {
cached["customer_name"] = v
}
if v, ok := info["product_name"].(string); ok {
cached["product_name"] = v
}
if v, ok := info["start_at"].(string); ok {
cached["start_at"] = v
}
if v, ok := info["end_at"].(string); ok {
cached["end_at"] = v
}
if opts, ok := info["payment_options"].([]interface{}); ok {
cached["payment_options"] = opts
}
if allowed {
cached["note"] = "云授权有效"
} else {
if reason, ok := info["reason"].(string); ok {
cached["note"] = reason
} else {
cached["note"] = "云授权不可用: " + stateVal
}
}
return cached
}
func AppLicenseState(force bool) map[string]interface{} {
cloud := CheckCloudLicense(force)
ts := trialState()
if configured, _ := cloud["configured"].(bool); configured {
if allowed, _ := cloud["allowed"].(bool); allowed {
cloud["state"] = "active"
cloud["days_left"] = nil
return cloud
}
cloud["days_left"] = 0
return cloud
}
ts["configured"] = false
if ts["state"] == "unconfigured" || (ts["state"] == "expired" && ts["days_left"] == 0) {
ts["allowed"] = true
} else {
ts["allowed"] = ts["state"] != "expired"
}
ts["server_url"] = DefaultServer
ts["product_code"] = DefaultProductCode
ts["device_id"] = DeviceID()
return ts
}
func LicenseAllowed(force bool) bool {
s := AppLicenseState(force)
allowed, _ := s["allowed"].(bool)
return allowed
}
func DeviceID() string {
did := funcWithDefault(keyDeviceID, "")
if did != "" {
return did
}
mac := getFirstMac()
raw := fmt.Sprintf("%s|%s|%s", mac, getHostname(), runtime.GOOS+"/"+runtime.GOARCH)
did = uuid.NewSHA1(uuid.NameSpaceDNS, []byte(raw)).String()
db.SetSetting(keyDeviceID, did)
return did
}
func buildCached(cfg map[string]string, lastAt int64) map[string]interface{} {
popts := []interface{}{}
if raw := funcWithDefault(keyPaymentOpts, "[]"); raw != "[]" {
json.Unmarshal([]byte(raw), &popts)
}
return map[string]interface{}{
"configured": true,
"allowed": funcWithDefault(keyLastAllowed, "0") == "1",
"state": funcWithDefault(keyLastStatus, "unknown"),
"account_token": cfg["account_token"],
"customer_code": cfg["customer_code"],
"product_code": cfg["product_code"],
"customer_name": funcWithDefault(keyCustomerName, ""),
"product_name": funcWithDefault(keyProductName, ""),
"start_at": funcWithDefault(keyStartAt, ""),
"end_at": funcWithDefault(keyEndAt, ""),
"server_url": cfg["server_url"],
"device_id": cfg["device_id"],
"checked_at": lastAt,
"payment_options": popts,
}
}
func trialState() map[string]interface{} {
if funcWithDefault("license", "") == "activated" {
return map[string]interface{}{"state": "activated", "days_left": nil, "configured": false, "allowed": true}
}
return map[string]interface{}{"state": "unconfigured", "days_left": nil}
}
func getFirstMac() string {
ifaces, err := net.Interfaces()
if err != nil {
return ""
}
for _, iface := range ifaces {
if iface.HardwareAddr != nil && len(iface.HardwareAddr) >= 6 {
return strings.ReplaceAll(iface.HardwareAddr.String(), ":", "")
}
}
return ""
}
func getHostname() string {
h, _ := os.Hostname()
return h
}
func parseTS(s string) int64 {
if s == "" {
return 0
}
t, err := time.Parse(time.RFC3339, s[:19])
if err != nil {
return 0
}
return t.Unix()
}
func btoi(b bool) int {
if b {
return 1
}
return 0
}
func strconvParseInt(s string) int64 {
var n int64
fmt.Sscanf(s, "%d", &n)
return n
}
func funcWithDefault(key, def string) string {
return db.GetSetting(key)
}