225 lines
7.5 KiB
Go
225 lines
7.5 KiB
Go
|
|
//go:build llm_script
|
|||
|
|
|
|||
|
|
package main
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"fmt"
|
|||
|
|
"regexp"
|
|||
|
|
"strings"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
defaultCTYunCodingPlanURL = "https://www.ctyun.cn/document/11061839/11092368"
|
|||
|
|
defaultCTYunTokenPlanURL = "https://www.ctyun.cn/act/AI/zhuanxiang"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
func parseCTYunSubscriptionCatalog(codingRaw string, tokenRaw string) ([]subscriptionImportRecord, error) {
|
|||
|
|
publishedAt, known := publishedAtFromText(firstNonEmptyText(codingRaw, tokenRaw))
|
|||
|
|
codingRecords, err := parseCTYunCodingPlan(codingRaw, publishedAt)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
tokenRecords, err := parseCTYunTokenPlan(tokenRaw, publishedAt)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
records := append(codingRecords, tokenRecords...)
|
|||
|
|
for i := range records {
|
|||
|
|
records[i].PublishedAtKnown = known
|
|||
|
|
}
|
|||
|
|
return records, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func parseCTYunCodingPlan(raw string, publishedAt string) ([]subscriptionImportRecord, error) {
|
|||
|
|
if !strings.Contains(raw, "GLM Lite") || !strings.Contains(raw, "GLM Max") {
|
|||
|
|
return nil, fmt.Errorf("ctyun coding plan tiers not found")
|
|||
|
|
}
|
|||
|
|
pricePattern := regexp.MustCompile(`包月价格\s+(\d+)元/月\s+(\d+)元/月\s+(\d+)元/月`)
|
|||
|
|
priceMatch := pricePattern.FindStringSubmatch(raw)
|
|||
|
|
if len(priceMatch) != 4 {
|
|||
|
|
return nil, fmt.Errorf("ctyun coding plan monthly prices not found")
|
|||
|
|
}
|
|||
|
|
limitPattern := regexp.MustCompile(`每月最多约([\d,]+)次prompts`)
|
|||
|
|
limitMatches := limitPattern.FindAllStringSubmatch(raw, -1)
|
|||
|
|
if len(limitMatches) < 3 {
|
|||
|
|
return nil, fmt.Errorf("ctyun coding plan monthly limits not found")
|
|||
|
|
}
|
|||
|
|
modelScope := extractCTYunCodingModels(raw)
|
|||
|
|
|
|||
|
|
records := []subscriptionImportRecord{
|
|||
|
|
{
|
|||
|
|
ProviderName: "Telecom",
|
|||
|
|
ProviderNameCn: "中国电信",
|
|||
|
|
ProviderCountry: "CN",
|
|||
|
|
ProviderWebsite: "https://www.ctyun.cn",
|
|||
|
|
OperatorName: "CTYun",
|
|||
|
|
OperatorNameCn: "天翼云",
|
|||
|
|
OperatorCountry: "CN",
|
|||
|
|
OperatorWebsite: "https://www.ctyun.cn",
|
|||
|
|
OperatorType: "cloud",
|
|||
|
|
PlanFamily: "coding_plan",
|
|||
|
|
PlanCode: "ctyun-coding-plan-lite-monthly",
|
|||
|
|
PlanName: "天翼云 Coding Plan Lite(月付)",
|
|||
|
|
Tier: "Lite",
|
|||
|
|
BillingCycle: "monthly",
|
|||
|
|
Currency: "CNY",
|
|||
|
|
ListPrice: mustParseSubscriptionPrice(priceMatch[1]),
|
|||
|
|
PriceUnit: "CNY/month",
|
|||
|
|
QuotaValue: mustParseSubscriptionInt64(limitMatches[0][1]),
|
|||
|
|
QuotaUnit: "prompts/month",
|
|||
|
|
PlanScope: "Coding Plan",
|
|||
|
|
ModelScope: modelScope,
|
|||
|
|
SourceURL: defaultCTYunCodingPlanURL,
|
|||
|
|
PublishedAt: publishedAt,
|
|||
|
|
EffectiveDate: effectiveDateFromPublishedAt(publishedAt),
|
|||
|
|
Notes: "每 5 小时约 80 次 prompts;每周约 400 次 prompts。",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
ProviderName: "Telecom",
|
|||
|
|
ProviderNameCn: "中国电信",
|
|||
|
|
ProviderCountry: "CN",
|
|||
|
|
ProviderWebsite: "https://www.ctyun.cn",
|
|||
|
|
OperatorName: "CTYun",
|
|||
|
|
OperatorNameCn: "天翼云",
|
|||
|
|
OperatorCountry: "CN",
|
|||
|
|
OperatorWebsite: "https://www.ctyun.cn",
|
|||
|
|
OperatorType: "cloud",
|
|||
|
|
PlanFamily: "coding_plan",
|
|||
|
|
PlanCode: "ctyun-coding-plan-pro-monthly",
|
|||
|
|
PlanName: "天翼云 Coding Plan Pro(月付)",
|
|||
|
|
Tier: "Pro",
|
|||
|
|
BillingCycle: "monthly",
|
|||
|
|
Currency: "CNY",
|
|||
|
|
ListPrice: mustParseSubscriptionPrice(priceMatch[2]),
|
|||
|
|
PriceUnit: "CNY/month",
|
|||
|
|
QuotaValue: mustParseSubscriptionInt64(limitMatches[1][1]),
|
|||
|
|
QuotaUnit: "prompts/month",
|
|||
|
|
PlanScope: "Coding Plan",
|
|||
|
|
ModelScope: modelScope,
|
|||
|
|
SourceURL: defaultCTYunCodingPlanURL,
|
|||
|
|
PublishedAt: publishedAt,
|
|||
|
|
EffectiveDate: effectiveDateFromPublishedAt(publishedAt),
|
|||
|
|
Notes: "每 5 小时约 400 次 prompts;每周约 2,000 次 prompts。",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
ProviderName: "Telecom",
|
|||
|
|
ProviderNameCn: "中国电信",
|
|||
|
|
ProviderCountry: "CN",
|
|||
|
|
ProviderWebsite: "https://www.ctyun.cn",
|
|||
|
|
OperatorName: "CTYun",
|
|||
|
|
OperatorNameCn: "天翼云",
|
|||
|
|
OperatorCountry: "CN",
|
|||
|
|
OperatorWebsite: "https://www.ctyun.cn",
|
|||
|
|
OperatorType: "cloud",
|
|||
|
|
PlanFamily: "coding_plan",
|
|||
|
|
PlanCode: "ctyun-coding-plan-max-monthly",
|
|||
|
|
PlanName: "天翼云 Coding Plan Max(月付)",
|
|||
|
|
Tier: "Max",
|
|||
|
|
BillingCycle: "monthly",
|
|||
|
|
Currency: "CNY",
|
|||
|
|
ListPrice: mustParseSubscriptionPrice(priceMatch[3]),
|
|||
|
|
PriceUnit: "CNY/month",
|
|||
|
|
QuotaValue: mustParseSubscriptionInt64(limitMatches[2][1]),
|
|||
|
|
QuotaUnit: "prompts/month",
|
|||
|
|
PlanScope: "Coding Plan",
|
|||
|
|
ModelScope: modelScope,
|
|||
|
|
SourceURL: defaultCTYunCodingPlanURL,
|
|||
|
|
PublishedAt: publishedAt,
|
|||
|
|
EffectiveDate: effectiveDateFromPublishedAt(publishedAt),
|
|||
|
|
Notes: "每 5 小时约 1,600 次 prompts;每周约 8,000 次 prompts。",
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
return records, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func parseCTYunTokenPlan(raw string, publishedAt string) ([]subscriptionImportRecord, error) {
|
|||
|
|
pattern := regexp.MustCompile(`Token Plan ([^\n]+?)(\d+(?:\.\d+)?亿|\d+万)Tokens包[\s\S]*?支持模型:([^\n]+)[\s\S]*?(\d+\s*\.\s*\d+)\s*元/个`)
|
|||
|
|
matches := pattern.FindAllStringSubmatch(raw, -1)
|
|||
|
|
if len(matches) != 6 {
|
|||
|
|
return nil, fmt.Errorf("unexpected ctyun token plan count: %d", len(matches))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
codeByTier := map[string]string{
|
|||
|
|
"Lite": "lite",
|
|||
|
|
"Pro": "pro",
|
|||
|
|
"Max": "max",
|
|||
|
|
"轻享包": "starter",
|
|||
|
|
"畅享包": "plus",
|
|||
|
|
"尊享包": "vip",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
records := make([]subscriptionImportRecord, 0, len(matches))
|
|||
|
|
for _, match := range matches {
|
|||
|
|
rawTier := strings.TrimSpace(match[1])
|
|||
|
|
tierCode := codeByTier[rawTier]
|
|||
|
|
quotaValue := parseChineseTokenQuota(match[2])
|
|||
|
|
price := mustParseSubscriptionPrice(strings.ReplaceAll(match[4], " ", ""))
|
|||
|
|
planName := "天翼云 Token Plan " + rawTier
|
|||
|
|
if rawTier == "Lite" || rawTier == "Pro" || rawTier == "Max" {
|
|||
|
|
planName = "天翼云 Token Plan " + rawTier
|
|||
|
|
}
|
|||
|
|
records = append(records, subscriptionImportRecord{
|
|||
|
|
ProviderName: "Telecom",
|
|||
|
|
ProviderNameCn: "中国电信",
|
|||
|
|
ProviderCountry: "CN",
|
|||
|
|
ProviderWebsite: "https://www.ctyun.cn",
|
|||
|
|
OperatorName: "CTYun",
|
|||
|
|
OperatorNameCn: "天翼云",
|
|||
|
|
OperatorCountry: "CN",
|
|||
|
|
OperatorWebsite: "https://www.ctyun.cn",
|
|||
|
|
OperatorType: "cloud",
|
|||
|
|
PlanFamily: "token_plan",
|
|||
|
|
PlanCode: "ctyun-token-plan-" + tierCode,
|
|||
|
|
PlanName: planName,
|
|||
|
|
Tier: rawTier,
|
|||
|
|
BillingCycle: "monthly",
|
|||
|
|
Currency: "CNY",
|
|||
|
|
ListPrice: price,
|
|||
|
|
PriceUnit: "CNY/pack",
|
|||
|
|
QuotaValue: quotaValue,
|
|||
|
|
QuotaUnit: "tokens/pack",
|
|||
|
|
PlanScope: "Token Plan",
|
|||
|
|
ModelScope: []string{strings.TrimSpace(match[3])},
|
|||
|
|
SourceURL: defaultCTYunTokenPlanURL,
|
|||
|
|
PublishedAt: publishedAt,
|
|||
|
|
EffectiveDate: effectiveDateFromPublishedAt(publishedAt),
|
|||
|
|
Notes: "天翼云大模型 AI 专项活动页套餐。",
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
return records, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func parseChineseTokenQuota(raw string) int64 {
|
|||
|
|
cleaned := strings.TrimSpace(strings.TrimSuffix(raw, "Tokens包"))
|
|||
|
|
cleaned = strings.ReplaceAll(cleaned, " ", "")
|
|||
|
|
switch {
|
|||
|
|
case strings.Contains(cleaned, "亿"):
|
|||
|
|
return parseDecimalMultiplier(strings.TrimSuffix(cleaned, "亿"), 100000000)
|
|||
|
|
case strings.Contains(cleaned, "万"):
|
|||
|
|
return parseDecimalMultiplier(strings.TrimSuffix(cleaned, "万"), 10000)
|
|||
|
|
default:
|
|||
|
|
return mustParseSubscriptionInt64(cleaned)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func extractCTYunCodingModels(raw string) []string {
|
|||
|
|
lines := strings.Split(raw, "\n")
|
|||
|
|
models := make([]string, 0, 8)
|
|||
|
|
capturing := false
|
|||
|
|
for _, line := range lines {
|
|||
|
|
line = strings.TrimSpace(line)
|
|||
|
|
switch {
|
|||
|
|
case line == "支持模型":
|
|||
|
|
capturing = true
|
|||
|
|
continue
|
|||
|
|
case line == "用量限制":
|
|||
|
|
return models
|
|||
|
|
case !capturing || line == "":
|
|||
|
|
continue
|
|||
|
|
default:
|
|||
|
|
models = append(models, line)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return models
|
|||
|
|
}
|