记录一次使用OVH API监控KS闪购库存并自动抢购

活动详情

这次活动性价比还可以,cpu虽然是十年前的老宝贝,但总体性价比还可以,主要是便宜 独服。其中:

  • KS-A 买到赚到
  • KS-LE-A 6t盘,每t不到2o,用作存储还可以
  • KS-LE-B 有nvme盘,性价比较高
  • KS-LE-C 内存很大概率中奖翻倍成64g,随时可以买
  • KS-LE-E 配置更高

https://eco.ovhcloud.com/en-ie/?range=kimsufi

图片[1]-记录一次使用OVH API监控KS闪购库存并自动抢购-THsInk

ksa能买到最好,ksleb补货过几次了 准备拿下一台,kslea补货更多 目前已买到 未开机(24.11.08更新)。其余配置没有需求。

已经历两三次补货,leb和lea都补了较长时间(10-30分钟),看到了手动买也绰绰有余。但有时候凌晨放货,容易错过。

网友们的脚本

从很早之前就有老哥用脚本抢购,这次有老外先放出了用api下单的详细流程,里面详细说明了相关参数和api请求流程。ovh有官方api可以下单是我没想到的,这样稳定下单的门槛变低了不少。

随后又有老哥发出了成品脚本,这下大家都在同一起跑线了。后续还有docker版本,这个更方便和全面了,但这时候我已经用之前老哥的go脚本为基础搓完自用脚本了,就没用上。

这里为便于自己查看和防止网页失联,备份一下上面提到的脚本和其他有用的东西:

应该是第一个发出完整脚本的老哥,老哥的博客:https://blog.yessure.org/index.php/archives/203/

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"
    "strconv"
    "time"

    "github.com/ovh/go-ovh/ovh"
)

const (
    appKey      = "ovh appkey"                 
    appSecret   = "ovh appsecret" 
    consumerKey = "ovh consumerkey" 
    region      = "ovh-eu"                          
    tgtoken     = "telegrambot token"
    tgchatid    = "telegram chatid"
    iam         = "go-ovh-ie"
    zone        = "IE"
)

func runTask() {

    client, err := ovh.NewClient(region, appKey, appSecret, consumerKey)
    if err != nil {
        log.Printf("Failed to create OVH client: %v\n", err)
        return
    }

    var result []map[string]interface{}
    err = client.Get("/dedicated/server/datacenter/availabilities", &result)
    if err != nil {
        log.Printf("Failed to get datacenter availabilities: %v\n", err)
        return
    }

    foundAvailable := false
    var fqn, planCode, datacenter string

    for _, item := range result {
        if item["planCode"] == "25skleb01" {
            fqn = item["fqn"].(string)
            planCode = item["planCode"].(string)
            datacenters := item["datacenters"].([]interface{})

            for _, dcInfo := range datacenters {
                dc := dcInfo.(map[string]interface{})
                availability := dc["availability"].(string)
                datacenter = dc["datacenter"].(string)

                fmt.Printf("FQN: %s\n", fqn)
                fmt.Printf("Availability: %s\n", availability)
                fmt.Printf("Datacenter: %s\n", datacenter)
                fmt.Println("------------------------")

                if availability != "unavailable" {
                    foundAvailable = true
                    break
                }
            }

            if foundAvailable {
                fmt.Printf("Proceeding to next step with FQN: %s Datacenter: %s\n", fqn, datacenter)
                break
            }
        }
    }

    if !foundAvailable {
        log.Println("No record to buy")
        return
    }

    msg := fmt.Sprintf("%s: found ks-le-b available at %s", iam, datacenter)
    sendTelegramMsg(tgtoken, tgchatid, msg)

    fmt.Println("Create cart")
    var cartResult map[string]interface{}
    err = client.Post("/order/cart", map[string]interface{}{
        "ovhSubsidiary": zone,
    }, &cartResult)
    if err != nil {
        log.Printf("Failed to create cart: %v\n", err)
        return
    }

    cartID := cartResult["cartId"].(string)
    fmt.Printf("Cart ID: %s\n", cartID)

    fmt.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
    }

    fmt.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
    }

    fmt.Printf("Item ID: %s\n", itemID)

    fmt.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 _, config := range configurations {
        fmt.Printf("Configure %s\n", config["label"])
        err = client.Post("/order/cart/"+cartID+"/item/"+itemID+"/configuration", map[string]interface{}{
            "label": config["label"],
            "value": config["value"],
        }, nil)
        if err != nil {
            log.Printf("Failed to configure %s: %v\n", config["label"], err)
            return
        }
    }

    fmt.Println("Add options")
    options := []string{
        "bandwidth-300-25skle",
        "ram-32g-ecc-2400-25skle",
        "softraid-2x450nvme-25skle",
    }

    itemIDInt, _ := strconv.Atoi(itemID)
    for _, option := range 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
        }
    }

    fmt.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": true,
        "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 main() {
    for {
        runTask()
        time.Sleep(20 * time.Second)
    }
}

想省时间直接用这个吧

github https://github.com/katorly/ovh-auto-buy

docker https://hub.docker.com/r/katorly/ovh-auto-buy

services:
  auto-buy:
    image: katorly/ovh-auto-buy:latest
    environment:
      APP_KEY: "ovh_appkey"             # OVH的应用key
      APP_SECRET: "ovh_appsecret"       # OVH的应用secret
      CONSUMER_KEY: "ovh_consumerkey"   # OVH的消费者key
      REGION: "ovh_region"              # 区域设置为, e.g. ovh-eu
      TG_TOKEN: "telegram_bot_token"    # 你的Telegram Bot Token
      TG_CHATID: "telegram_your_chatid" # 你希望发送消息的Telegram Chat ID
      ZONE: "ovh_zone"                  # OVH子公司区域设置, e.g. IE
      PLANCODE: "product_plancode"      # 需要购买的产品的planCode, e.g. 25skleb01
      OPTIONS: "product_options"        # 选择的配置, e.g. bandwidth-300-25skle,ram-32g-ecc-2400-25skle,softraid-2x450nvme-25skle
      AUTOPAY: true                     # 是否自动支付, e.g. true

有些内容可能对你有帮助,github链接在上面段落

OVH - How to use the API to order any server? The answer is here!

# I ASK YOU IN FIRST

This process is automateable. I'll write the know-how, how to do it. I kindly ask you! Do NOT ABUSE it! Please keep the oportunity to order limited edition servers for other ones!

# How the OVH API works?

First of all, OVH provides API libraries to access the API. And for this too, they have an API console where you can try it. For now, we'll see the API console. We'll place an older. Lets go!

# In practical
## Login to API

I'll demonstrate the steps for you in IE region. Switch it if you need it.

Create an account on OVHcloud if you do not have it and login on [ovh.com/manager](https://ovh.com/manager).

Visit the API Console and authorize yourself [here](https://eu.api.ovh.com/console/?section=%2FallDom&branch=v1#auth).

## Fetch server information

First of all, we have two important parts before we order. Server catalog and availabilities.

In the catalog OVH stores the prices, the bandwidth, HDD, SSD, RAM options, commitment duration infos and so on.

On the availability API you can see the specific configurations availability. For example if we have a server with 16, 32 GB RAM options and 2 TB HDD, 450 GB NVMe it is four different options. So planCode is the same and FQN identifies the desired ones.
Not clear enough? No problem, it will be clear later!

### Check the availability

It is a good idea to start with availability API, which is [here](https://eu.api.ovh.com/console/?section=%2Fdedicated%2Fserver&branch=v1#get-/dedicated/server/datacenter/availabilities).

Auth not required. IF you fetch it, you will see an array with items. The fqn identifies the individual configs and planCode identifies the groups. So planCode for KS-1 is 24sk10 and FQN specifies the RAM and SSD options. Network does not matter here because it is dinamicaly configurable.

Select a server that fits your needs. For example, I want to buy KS-1.

KS-1 is 24sk10 and FQN is 24sk10.ram-32g-ecc-2133.softraid-2x480ssd.

Nice, note it!

### Check in catalog

For now, you need to search the planCode on the catalog. No, not for the FQN! FQN identifies the config. But on the catalog, we only can find the planCode where all of the options available. For KS-1 it is 32 GB of RAM, and 2x2 T of HDD or 2x480 G of SSD.

Go to [retrieve ECO catalog](https://eu.api.ovh.com/console/?section=%2Forder&branch=v1#get-/order/catalog/public/eco).

And get it.

Catalog is very high amount of data, so I suggest that fill out subsidiary and use the cURL command. For IE
```
curl -X GET "https://eu.api.ovh.com/v1/order/catalog/public/eco?ovhSubsidiary=IE"
```

As I mentioned data amount is high. So I suggest to download to a JSON file
```
curl -o catalog.json -X GET "https://eu.api.ovh.com/v1/order/catalog/public/eco?ovhSubsidiary=IE"
```
And open it in VsCode, or Notepad. To read it in Notepad, I suggest the Perl's json_pp command to format it to read easier.
```
cat catalog.json | json_pp > formatted.json
```

#### Search for our desired machine!

We noted that we search for KS-1 with 480 GB SSD, 32 GB of RAM and 300 Mbps public network.

First of all, look for the server. We can find that in the array of `plans`. But more easy to search in the file for this line `"planCode" : "24sk10"`.

We'll in the end of an array so scroll up ontil it starts. As if we in the end of an array, search the previous occurence of `         "addonFamilies" : [`. If we found it, we at the beginning of it. So look!

```
            {
               "addons" : [
                  "softraid-2x480ssd-24sk10",
                  "softraid-2x2000sa-24sk10"
               ],
               "default" : "softraid-2x2000sa-24sk10",
               "exclusive" : true,
               "mandatory" : true,
               "name" : "storage"
            },
            {
               "addons" : [
                  "ram-32g-ecc-2133-24sk10"
               ],
               "default" : "ram-32g-ecc-2133-24sk10",
               "exclusive" : true,
               "mandatory" : true,
               "name" : "memory"
            }
```

```
            {
               "addons" : [
                  "bandwidth-300-24sk"
               ],
               "default" : "bandwidth-300-24sk",
               "exclusive" : true,
               "mandatory" : true,
               "name" : "bandwidth"
            }
```

What are we see here? Here is the addons for the selected configuration. For mandatory addons (net, disk, RAM) you need to select only one to complete the order and note the planCodes as you see here.
```
softraid-2x480ssd-24sk10
ram-32g-ecc-2133-24sk10
bandwidth-300-24sk
```

Default means the default for this value. probably it set if you do not place any network or disk or RAM on the cart, but avoid it. Place all mandatory options by hand!

If config has more RAM options (e.g. KS-2, or more network, e.g. SYS-1), you will see longer arrays. Array is `[]` in JSON.

We will nee do note down the labels for the server.

```
         "configurations" : [
            {
               "isCustom" : false,
               "isMandatory" : false,
               "name" : "dedicated_datacenter",
               "values" : [
                  "gra",
                  "sbg",
                  "rbx",
                  "bhs",
                  "waw",
                  "fra",
                  "lon"
               ]
            },
            {
               "isCustom" : false,
               "isMandatory" : true,
               "name" : "dedicated_os",
               "values" : [
                  "none_64.en"
               ]
            },
            {
               "isCustom" : false,
               "isMandatory" : true,
               "name" : "region",
               "values" : [
                  "europe",
                  "canada"
               ]
            }
```

You need to select only one for everything. So for example
```
dedicated_os: none_64.en
region: europe
dedicated_datacenter: wav
```

Write down the proper DC and region. The DC options are all DCs where can the server appears. And availability API shows the actual availability. So it is possible that not available anywhere or everywhere.

I don't know enough about the dedicated_os but properly it means the platform (x86_64).

The prices for this server seen after the invoiceName. You can see the rental times, setup fee and etc.
> Note: Divide the prices by 100000000 to see proper numbers.

#### See the details about addons.

We selected BW, RAM and disk. Moreover, for Rise we need to select vRack option too.

So it is important to see the price for the selected addons. In the catalog.json look in the addon for the  selected disk.
```
         "invoiceName" : "2x 480Gb SSD Soft RAID",
         "planCode" : "softraid-2x480ssd-24sk10",
```

Look for the price. Do it with RAM and bandwidth too.

## Do the order process
### prepare your cart

Go to [Post cart](https://eu.api.ovh.com/console/?section=%2Forder&branch=v1#post-/order/cart).

And create your order cart. It is a good idea to modify the expire with one or two hours later to get enough time to fill up your cart.

Click on try button and see the response.
```
{
  "cartId": "XXX",
  "description": "string",
  "expire": "2024-11-05T17:29:13+00:00",
  "readOnly": false,
  "items": []
}
```

Note your cartId down.

### Bind the cart with your account

Go to [assign](https://eu.api.ovh.com/console/?section=%2Forder&branch=v1#post-/order/cart/-cartId-/assign), will up the cart ID and press try button to bind this to your account.

### Add the server to cart

Go to [POST eco](https://eu.api.ovh.com/console/?section=%2Forder&branch=v1#post-/order/cart/-cartId-/eco).
And place the server.

Set the cartId and the request body. Request body is the following.
```
{
  "duration": "P1M",
  "planCode": "24sk10",
  "pricingMode": "default",
  "quantity": 1
}
```

Look it deeper!

The three important fields are the duration, planCode and qty. I think that qty is not too hard too understand, however the planCode is the server's planCode and the rental duration is that how long we want to order it. There are options for this on the catalog, for ECO there isn't any deduction for longer contract so simply use 1mo (P1M).

Submit it with try button!

Response:
```
{
  "cartId": "",
  "configurations": [],
  "duration": "P1M",
  "itemId": 263---,
  "offerId": null,
  "options": [],
  "prices": [
    {
      "label": "TOTAL",
      "price": {
        "currencyCode": "EUR",
        "priceInUcents": 1699000000,
        "text": "€ 16.99",
        "value": 16.99
      }
    }
  ],
  "productId": "eco",
  "settings": {
    "planCode": "24sk10",
    "pricingMode": "default",
    "quantity": 1
  }
}
```

Write down the ID, it will be needed to store addons!

### Add the addons to server

Visit [POST eco options](https://eu.api.ovh.com/console/#post-/order/cart/-cartId-/eco/options).

And fill up cartID, and the request body. For the request body you need to do it three times with the three different planCodes. simply paste or edit it, and press try button.

```
{
  "duration": "P1M",
  "itemId": 261---,
  "planCode": "ram-32g-ecc-2133-24sk10",
  "pricingMode": "default",
  "quantity": 1
}

{
  "duration": "P1M",
  "itemId": 261---,
  "planCode": "softraid-2x480ssd-24sk10",
  "pricingMode": "default",
  "quantity": 1
}

{
  "duration": "P1M",
  "itemId": 261---,
  "planCode": "bandwidth-300-24sk",
  "pricingMode": "default",
  "quantity": 1
}
```

You will got different responses that shows the additional price and more info.

### Add the labels

For a complete order you need to fill up three labels (that I mentioned earlier).
Go to [POST configuration](https://eu.api.ovh.com/console/#post-/order/cart/-cartId-/item/-itemId-/configuration).
And fill up cartID, and the itemID (previous noted).

As in previous step, do it three times. Fill, try, fill, try and once fill, try.

```
{
  "label": "dedicated_datacenter",
  "value": "gra"
}

{
  "label": "region",
  "value": "europe"
}

{
  "label": "dedicated_os",
  "value": "none_64.en"
}
```

Did you got three 200 responses? Nice, we near to the final!

### Validate the order

You can validate your order [here](https://eu.api.ovh.com/console/#get-/order/cart/-cartId-/checkout)

Fill up cartId and look.

```
  "details": [
    {
      "cartItemID": 263---7,
      "description": "300Mbps unmetered public bandwidth rental - 1 month",
      "detailType": "DURATION",
      "domain": "*001.003",
      "originalTotalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "quantity": 1,
      "reductionTotalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "reductions": [],
      "totalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "unitPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      }
    },
    {
      "cartItemID": 263---7,
      "description": "300Mbps unmetered public bandwidth",
      "detailType": "INSTALLATION",
      "domain": "*001.003",
      "originalTotalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "quantity": 1,
      "reductionTotalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "reductions": [],
      "totalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "unitPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      }
    },
    {
      "cartItemID": 263---6,
      "description": "2x 2TB HDD Soft RAID rental - 1 month",
      "detailType": "DURATION",
      "domain": "*001.001",
      "originalTotalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "quantity": 1,
      "reductionTotalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "reductions": [],
      "totalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "unitPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      }
    },
    {
      "cartItemID": 263---5,
      "description": "32GB DDR4 ECC 2133MHz rental - 1 month",
      "detailType": "DURATION",
      "domain": "*001.002",
      "originalTotalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "quantity": 1,
      "reductionTotalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "reductions": [],
      "totalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "unitPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      }
    },
    {
      "cartItemID": 263---1,
      "description": "KS-1 | Intel Xeon-D 1520 rental - datacenter bhs - ",
      "detailType": "INSTALLATION",
      "domain": "*001",
      "originalTotalPrice": {
        "currencyCode": "EUR",
        "text": "€ 16.99",
        "value": 16.99
      },
      "quantity": 1,
      "reductionTotalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "reductions": [],
      "totalPrice": {
        "currencyCode": "EUR",
        "text": "€ 16.99",
        "value": 16.99
      },
      "unitPrice": {
        "currencyCode": "EUR",
        "text": "€ 16.99",
        "value": 16.99
      }
    },
    {
      "cartItemID": 263---,
      "description": "KS-1 | Intel Xeon-D 1520 rental - datacenter bhs - 1 month",
      "detailType": "DURATION",
      "domain": "*001",
      "originalTotalPrice": {
        "currencyCode": "EUR",
        "text": "€ 16.99",
        "value": 16.99
      },
      "quantity": 1,
      "reductionTotalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "reductions": [],
      "totalPrice": {
        "currencyCode": "EUR",
        "text": "€ 16.99",
        "value": 16.99
      },
      "unitPrice": {
        "currencyCode": "EUR",
        "text": "€ 16.99",
        "value": 16.99
      }
    },
    {
      "cartItemID": 263039371,
      "description": "Intel Xeon-D 1520",
      "detailType": "INSTALLATION",
      "domain": "*001",
      "originalTotalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "quantity": 1,
      "reductionTotalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "reductions": [],
      "totalPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      },
      "unitPrice": {
        "currencyCode": "EUR",
        "text": "€ 0.00",
        "value": 0
      }
    }
  ],
  "orderId": null,
  "prices": {
    "originalWithoutTax": {
      "currencyCode": "EUR",
      "text": "€ 33.98",
      "value": 33.98
    },
    "reduction": {
      "currencyCode": "EUR",
      "text": "€ 0.00",
      "value": 0
    },
    "tax": {
      "currencyCode": "EUR",
      "text": "€ 9.17",
      "value": 9.17
    },
    "withTax": {
      "currencyCode": "EUR",
      "text": "€ 43.15",
      "value": 43.15
    },
    "withoutTax": {
      "currencyCode": "EUR",
      "text": "€ 33.98",
      "value": 33.98
    }
  },
  "url": null
}
```


### Finalize the order

And we arrived to the latest step.
Visit [POST order](https://eu.api.ovh.com/console/#post-/order/cart/-cartId-/checkout).

```
{
  "autoPayWithPreferredPaymentMethod": false,
  "waiveRetractationPeriod": false
}
```

Fill it out, if you want autopay or remove the waiting time after order, and press try button.

For this, I do not have response as I not buy KS-1.
Update: I successfull bought a KS-LE-B, so this is the output of this post request. As you can see near equal with the get checkout edition. In the first part of the response there are ToS and other data for law, I removed it so this is the interesting part of the response.
```
{
  "details": [
    {
      "cartItemID": 262---38,
      "description": "300Mbps unmetered public bandwidth rental - 1 month",
      "detailType": "DURATION",
      "domain": "*001.001",
      "originalTotalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "quantity": 1,
      "reductionTotalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "reductions": [],
      "totalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "unitPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      }
    },
    {
      "cartItemID": 262--38,
      "description": "300Mbps unmetered public bandwidth",
      "detailType": "INSTALLATION",
      "domain": "*001.001",
      "originalTotalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "quantity": 1,
      "reductionTotalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "reductions": [],
      "totalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "unitPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      }
    },
    {
      "cartItemID": 262--37,
      "description": "2x 450Gb SSD NVMe Soft RAID rental - 1 month",
      "detailType": "DURATION",
      "domain": "*001.002",
      "originalTotalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "quantity": 1,
      "reductionTotalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "reductions": [],
      "totalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "unitPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      }
    },
    {
      "cartItemID": 262--39,
      "description": "32GB DDR4 ECC 2400MHz rental - 1 month",
      "detailType": "DURATION",
      "domain": "*001.003",
      "originalTotalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "quantity": 1,
      "reductionTotalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "reductions": [],
      "totalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "unitPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      }
    },
    {
      "cartItemID": 262--36,
      "description": "KS-LE-B rental - datacenter gra - ",
      "detailType": "INSTALLATION",
      "domain": "*001",
      "originalTotalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 9.99",
        "value": 9.99
      },
      "quantity": 1,
      "reductionTotalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "reductions": [],
      "totalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 9.99",
        "value": 9.99
      },
      "unitPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 9.99",
        "value": 9.99
      }
    },
    {
      "cartItemID": 262--36,
      "description": "KS-LE-B rental - datacenter gra - 1 month",
      "detailType": "DURATION",
      "domain": "*001",
      "originalTotalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 9.99",
        "value": 9.99
      },
      "quantity": 1,
      "reductionTotalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "reductions": [],
      "totalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 9.99",
        "value": 9.99
      },
      "unitPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 9.99",
        "value": 9.99
      }
    },
    {
      "cartItemID": 262--36,
      "description": "Intel Xeon E3-1245v5",
      "detailType": "INSTALLATION",
      "domain": "*001",
      "originalTotalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "quantity": 1,
      "reductionTotalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "reductions": [],
      "totalPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      },
      "unitPrice": {
        "currencyCode": "EUR",
        "text": "\u20ac 0.00",
        "value": 0
      }
    }
  ],
  "orderId": ---,
  "prices": {
    "originalWithoutTax": {
      "currencyCode": "EUR",
      "text": "\u20ac 19.98",
      "value": 19.98
    },
    "reduction": {
      "currencyCode": "EUR",
      "text": "\u20ac 0.00",
      "value": 0
    },
    "tax": {
      "currencyCode": "EUR",
      "text": "\u20ac 5.39",
      "value": 5.39
    },
    "withTax": {
      "currencyCode": "EUR",
      "text": "\u20ac 25.37",
      "value": 25.37
    },
    "withoutTax": {
      "currencyCode": "EUR",
      "text": "\u20ac 19.98",
      "value": 19.98
    }
  },
  "url": "https://www.ovh.ie/cgi-bin/order/display-order.cgi?orderId="
}
```

> You can see HDD and BHS DC. It's reason is that when I arrived to the end of this tutorial, it was the only one edition that is in stock now.

If a config not in stock in desired DC, you will got bad response error.

## Error messages

sometimes you got error messages from the API. It always has proper error code (4xx), and in the response body a request class and a response text.
The class used when you use any wrapper, E.G. python-ovh, you can catch this type of exception. And the error message informs you but sometimes not too informal.

# Footnote

- I disclaim any liability in connection with the use of this description, you should follow the instructions here at your own risk!
- I tried to do it as with my best knowledge, however I know that I can always improve me and my tutorial. Feel free to give me any feedback or a thanks!

Enjoy it!

Ohh, and on LET, you can find me as [adns](https://lowendtalk.com/profile/adns)

https://github.com/TheoBrigitte/kimsufi-notifier

t.me/KimsufiNotifierBot

其他注意事项

  • 如果你没有账号,请先提前注册并测试能否正常下单机器。可以购买vps页面的首年优惠(0.8欧/0.97刀)测试,能开机就问题不大。
  • 大部分脚本和教程默认区域是ie区,注意替换。
  • 在这里获取Application key,Application secret,consumer keyhttps://eu.api.ovh.com/createToken/ 。*允许所有请求。
图片[2]-记录一次使用OVH API监控KS闪购库存并自动抢购-THsInk
  • 配置tg通知:使用@BotFather 创建tg机器人,使用@getmyid_bot 获取会话id

我遇到的问题

如果你只是在寻找脚本,看到上面就可以了,下面只是一点个人摸鱼时无聊的记录,可能对你手搓脚本有一点帮助,但没有成品脚本。

我不会go,在老哥们的基础上用python修改了脚本,其中需要注意的是,原帖的回复中坛友使用gpt转换的python脚本无法正常使用,因为认证时没有添加application_secret。ovh有用于api的官方python库https://github.com/ovh/python-ovh,脚本可以import ovh,使用以下认证方式:

client = ovh.Client(
    endpoint='ovh-eu',
    application_key='<application key>',
    application_secret='<application secret>',
    consumer_key='<consumer key>',
)

在这种情况下,遇到问题还是比较容易解决的,api会返回具体的错误。

用python下单时,我最开始使用:

try:
            # 创建购物车
            cart_result = self.client.post("/order/cart", {
                "ovhSubsidiary": ZONE
            })
            cart_id = cart_result["cartId"]

这种格式好像不行,返回子公司无效。后来改成这样就一切正常了:

            # 创建购物车
            cart_result = self.client.post("/order/cart", ovhSubsidiary=ZONE)
            cart_id = cart_result["cartId"]

            # 分配购物车
            self.client.post(f"/order/cart/{cart_id}/assign")

            # 添加主要商品
            item_result = self.client.post(f"/order/cart/{cart_id}/eco",
                planCode=product.plan_code,
                pricingMode="default",
                duration="P1M",
                quantity=1
            )
            item_id = str(item_result["itemId"])

            # 获取所需配置
            required_config = self.client.get(
                f"/order/cart/{cart_id}/item/{item_id}/requiredConfiguration"
            )

            # 设置地区值
            region_value = None
            for config in required_config:
                if config["label"] == "region":
                    if config.get("allowedValues"):
                        region_value = config["allowedValues"][0]

            # 配置商品
            self.client.post(f"/order/cart/{cart_id}/item/{item_id}/configuration",
                label="dedicated_datacenter",
                value=selected_datacenter
            )

            self.client.post(f"/order/cart/{cart_id}/item/{item_id}/configuration",
                label="dedicated_os",
                value="none_64.en"
            )

            if region_value:
                self.client.post(f"/order/cart/{cart_id}/item/{item_id}/configuration",
                    label="region",
                    value=region_value
                )

            # 添加选项
            for option in product.options:
                self.client.post(f"/order/cart/{cart_id}/eco/options",
                    duration="P1M",
                    itemId=int(item_id),
                    planCode=option,
                    pricingMode="default",
                    quantity=1
                )

            # 结账
            self.client.post(f"/order/cart/{cart_id}/checkout",
                autoPayWithPreferredPaymentMethod=False,
                waiveRetractationPeriod=True
            )

其中autoPayWithPreferredPaymentMethod为是否自动付款。

2024.11.8 脚本各项功能正常,成功下单,但进入48小时验证。我的卡也没有扣款。分析是因为信用卡付款需要3d验证,使用api付款无法验证导致失败。这是一个惨痛的教训——即使我提前测试购买了一个kslec(也是进入48小时验证,我刚开始使用ovh,不知道这意味着什么)——晚上补货时 我错过了一台ksleb:

图片[3]-记录一次使用OVH API监控KS闪购库存并自动抢购-THsInk

之所以有上面的结论是因为我又手动下单了一个kslea,通过了3d认证,没有进入48小时验证(没有被砍单)。而上面使用api下单的三个订单都被取消了。现在我将脚本设置为只下单不付款,等待进行测试(2024.11.8)。

其余就是细枝末节的东西了,优化了tg通知,添加了对多个商品的监控和购买数量的限制,添加了购买区域的优先级:

图片[4]-记录一次使用OVH API监控KS闪购库存并自动抢购-THsInk

我目前的战况:

商品已购买数量目标购买数量目前状态用途
KS-A03等补货中便宜,总有用处
KS-LE-A11交付中存储
KS-LE-B02等补货中迁移几个小站过去

OVH对于中国用户,最大的难点不在于抢购,而是在账号注册和付款方式上。ovh的库存也是比较充足的,从每次补货都至少有十几分钟可以购买可以看出这一点——只是交付太慢。

现在api下单基本0门槛,对我们这种想自用的入门用户来说更友好了。即使你仍要从二手贩子手里购买,付出的溢价也会比之前少得多。对于ovh更多的实际用户来说,这应该是一件好事。

参考

https://gist.github.com/adns44/09e71aa60658f02357e65992af6d77ef

https://www.nodeseek.com/post-189859-1

https://help.ovhcloud.com/csm/en-gb-api-getting-started-ovhcloud-api?id=kb_article_view&sysparm_article=KB0042784

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!无需注册,过短或乱码评论会被屏蔽。
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容