更新一下ovh抢购代码
最后再更新一版,因为原始版有个别地方存在缺陷,我怕谁受伤了来砍我。
这一版,主要是解决了原始代码中的一些问题和不足,以前算毛胚,现在算简装吧。 我希望大家不要直接使用这代码而只是借鉴,所以,相应的yaml我就不发了。具体改了些什么以及原因我也不说明了。算是给开发者的福利。
没有版权,随便使用。但尽量请提供原始出处。我这blog还是需要多点外链。
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"log"
"math/rand"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/ovh/go-ovh/ovh"
"gopkg.in/yaml.v2"
)
/*
获得全部可用产品
curl 'https://ca.api.ovh.com/1.0/order/catalog/public/eco?ovhSubsidiary=ASIA'
搜索返回值,查找 "planCode": "25skleb01",获得options
*/
type Config struct {
App struct {
Key string `yaml:"key"`
Secret string `yaml:"secret"`
ConsumerKey string `yaml:"consumer_key"`
Region string `yaml:"region"`
Interval int `yaml:"interval"`
} `yaml:"app"`
Telegram struct {
Token string `yaml:"token"`
ChatID string `yaml:"chat_id"`
} `yaml:"telegram"`
Server struct {
IAM string `yaml:"iam"`
Zone string `yaml:"zone"`
RequiredPlanCode string `yaml:"required_plan_code"`
RequiredDisk string `yaml:"required_disk"`
RequiredMemory string `yaml:"required_memory"`
RequiredDatacenter string `yaml:"required_datacenter"`
PlanName string `yaml:"plan_name"`
AutoPay bool `yaml:"autopay"`
Options []string `yaml:"options"`
Coupon string `yaml:"coupon"`
} `yaml:"server"`
}
var config Config
func loadConfig(configPath string) error {
configFile, err := os.ReadFile(configPath)
if err != nil {
return fmt.Errorf("error reading config file: %v", err)
}
err = yaml.Unmarshal(configFile, &config)
if err != nil {
return fmt.Errorf("error parsing config file: %v", err)
}
return nil
}
func runTask() {
client, err := ovh.NewClient(
config.App.Region,
config.App.Key,
config.App.Secret,
config.App.ConsumerKey,
)
if err != nil {
log.Printf("Failed to create OVH client: %v\n", err)
return
}
// 获取数据中心可用性
var result []map[string]interface{}
url := "https://eu.api.ovh.com/1.0/dedicated/server/datacenter/availabilities?planCode=" + config.Server.RequiredPlanCode
if config.Server.RequiredDisk != "" {
url = url + "&storage=" + config.Server.RequiredDisk
}
if config.Server.RequiredMemory != "" {
url = url + "&memory=" + config.Server.RequiredMemory
}
resp, err := http.Get(url)
if err != nil {
log.Printf("Failed to get datacenter availabilities: %v\n", err)
return
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
log.Printf("Failed to decode datacenter availabilities response: %v\n", err)
return
}
foundAvailable := false
var fqn, planCode, datacenter string
for _, item := range result {
if item["planCode"] == config.Server.RequiredPlanCode {
fqn = item["fqn"].(string)
planCode = item["planCode"].(string)
datacenters := item["datacenters"].([]interface{})
var availableDCs []map[string]interface{}
for _, dcInfo := range datacenters {
dc := dcInfo.(map[string]interface{})
availability := dc["availability"].(string)
datacenter = dc["datacenter"].(string)
if datacenter == "sgp" || datacenter == "syd" {
//正常是不会到这里的,但为了防止成大冤种,自保一下
continue
}
log.Printf("FQN: %s\n", fqn)
log.Printf("Availability: %s\n", availability)
log.Printf("Datacenter: %s\n", datacenter)
log.Println("------------------------")
if availability != "unavailable" {
if config.Server.RequiredDatacenter == "" || contains(strings.Split(config.Server.RequiredDatacenter, ","), datacenter) {
availableDCs = append(availableDCs, dc)
}
}
}
if len(availableDCs) > 0 {
popi := rand.Intn(len(availableDCs))
selectedDC := availableDCs[popi]
datacenter = selectedDC["datacenter"].(string)
availability := selectedDC["availability"].(string)
foundAvailable = true
log.Printf("RECROD FOUND,SELECT:\n")
log.Printf("FQN: %s\n", fqn)
log.Printf("Availability: %s\n", availability)
log.Printf("Datacenter: %s\n", datacenter)
break
}
}
}
if !foundAvailable {
log.Printf("all out of stock %s\n", planCode)
return
}
msg := fmt.Sprintf("%s: found %s available at %s", config.Server.IAM, config.Server.PlanName, datacenter)
sendTelegramMsg(config.Telegram.Token, config.Telegram.ChatID, msg)
// 创建购物车
log.Println("Create cart")
var cartResult map[string]interface{}
err = client.Post("/order/cart", map[string]interface{}{
"ovhSubsidiary": config.Server.Zone,
}, &cartResult)
if err != nil {
log.Printf("Failed to create cart: %v\n", err)
return
}
cartID := cartResult["cartId"].(string)
log.Printf("Cart ID: %s\n", cartID)
// 分配购物车
log.Println("Assign cart")
err = client.Post("/order/cart/"+cartID+"/assign", nil, nil)
if err != nil {
log.Printf("Failed to assign cart: %v\n", err)
return
}
// 将商品放入购物车
log.Println("Put item into cart")
var itemResult map[string]interface{}
err = client.Post("/order/cart/"+cartID+"/eco", map[string]interface{}{
"planCode": planCode,
"pricingMode": "default",
"duration": "P1M",
"quantity": 1,
}, &itemResult)
if err != nil {
log.Printf("Failed to add item to cart: %v\n", err)
return
}
var itemID string
if v, ok := itemResult["itemId"].(json.Number); ok {
itemID = v.String()
} else if v, ok := itemResult["itemId"].(string); ok {
itemID = v
} else {
log.Printf("Unexpected type for itemId, expected json.Number or string, got %T\n", itemResult["itemId"])
return
}
log.Printf("Item ID: %s\n", itemID)
// 检查所需配置
log.Println("Checking required configuration")
var requiredConfig []map[string]interface{}
err = client.Get("/order/cart/"+cartID+"/item/"+itemID+"/requiredConfiguration", &requiredConfig)
if err != nil {
log.Printf("Failed to get required configuration: %v\n", err)
return
}
dedicatedOs := "none_64.en"
var regionValue string
for _, config := range requiredConfig {
if config["label"] == "region" {
if allowedValues, ok := config["allowedValues"].([]interface{}); ok && len(allowedValues) > 0 {
regionValue = allowedValues[0].(string)
}
}
}
// 配置数据中心、操作系统和区域
configurations := []map[string]interface{}{
{"label": "dedicated_datacenter", "value": datacenter},
{"label": "dedicated_os", "value": dedicatedOs},
{"label": "region", "value": regionValue},
}
for _, conf := range configurations {
log.Printf("Configure %s\n", conf["label"])
err = client.Post("/order/cart/"+cartID+"/item/"+itemID+"/configuration", map[string]interface{}{
"label": conf["label"],
"value": conf["value"],
}, nil)
if err != nil {
log.Printf("Failed to configure %s: %v\n", conf["label"], err)
return
}
}
// 添加选项
log.Println("Add options")
itemIDInt, _ := strconv.Atoi(itemID)
for _, option := range config.Server.Options {
err = client.Post("/order/cart/"+cartID+"/eco/options", map[string]interface{}{
"duration": "P1M",
"itemId": itemIDInt,
"planCode": option,
"pricingMode": "default",
"quantity": 1,
}, nil)
if err != nil {
log.Printf("Failed to add option %s: %v\n", option, err)
return
}
}
//有优惠码
if config.Server.Coupon != "" {
log.Println("Apply coupon")
client.Post("/order/cart/"+cartID+"/coupon", map[string]interface{}{
"coupon": config.Server.Coupon,
}, nil)
//不处理返回,不管是否成功均需要正常结帐
}
// 结账
log.Println("Checkout")
var checkoutResult map[string]interface{}
err = client.Get("/order/cart/"+cartID+"/checkout", &checkoutResult)
if err != nil {
log.Printf("Failed to get checkout: %v\n", err)
return
}
err = client.Post("/order/cart/"+cartID+"/checkout", map[string]interface{}{
"autoPayWithPreferredPaymentMethod": config.Server.AutoPay,
"waiveRetractationPeriod": true,
}, nil)
if err != nil {
log.Printf("Failed to checkout: %v\n", err)
return
}
log.Println("Bingo!")
os.Exit(0)
}
func sendTelegramMsg(botToken, chatID, message string) error {
url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", botToken)
payload := map[string]string{
"chat_id": chatID,
"text": message,
}
jsonData, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("error encoding JSON: %v", err)
}
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("error sending request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("received non-OK response status: %v", resp.Status)
}
return nil
}
func contains(slice []string, item string) bool {
normalizedItem := strings.ToLower(strings.TrimSpace(item))
for _, v := range slice {
if strings.ToLower(strings.TrimSpace(v)) == normalizedItem {
return true
}
}
return false
}
func main() {
var configPath string
flag.StringVar(&configPath, "config", "", "path to config file (required)")
flag.Parse()
if configPath == "" {
flag.Usage()
log.Fatal("config file path is required")
}
err := loadConfig(configPath)
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
if !config.Server.AutoPay {
log.Println("WARNNING: NO AUTOPAY")
}
if config.App.Interval == 0 {
config.App.Interval = 10
}
for {
runTask()
time.Sleep(time.Duration(config.App.Interval) * time.Second)
}
}
nodeseek