Мод-менеджер с галочками и коллекции мастерской
Вместо двух отдельных списков (Workshop ID и Mod ID) — один список модов: галочка включает мод, перетаскивание задаёт порядок загрузки, кнопка убирает пакет вместе со всеми его модами. Чтобы галочкам было за что зацепиться, панель читает mod.info уже скачанных модов: серверу в Mods нужен именно Mod ID, а он лежит внутри пакета мастерской и вручную его приходилось искать в описании мода. Пока мод не скачан, строка помечена и галочка недоступна — Mod ID неизвестен. Один пакет мастерской может содержать несколько модов; теперь они видны отдельными строками и включаются по отдельности. Коллекции: кнопка разворачивает коллекцию Steam в список входящих в неё модов, сохраняя порядок со страницы коллекции. И мод, и коллекция принимаются как ссылкой, так и голым идентификатором. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,14 +6,18 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// workshopAPI — публичный метод Steam, ключ доступа не требуется.
|
||||
const workshopAPI = "https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/"
|
||||
// Публичные методы Steam, ключ доступа не требуется.
|
||||
const (
|
||||
workshopAPI = "https://api.steampowered.com/ISteamRemoteStorage/GetPublishedFileDetails/v1/"
|
||||
collectionAPI = "https://api.steampowered.com/ISteamRemoteStorage/GetCollectionDetails/v1/"
|
||||
)
|
||||
|
||||
// WorkshopItem — то, что панель показывает про мод из мастерской.
|
||||
type WorkshopItem struct {
|
||||
@@ -151,3 +155,71 @@ func (c *WorkshopClient) fetch(ctx context.Context, ids []string) ([]WorkshopIte
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// Collection разворачивает коллекцию мастерской в список входящих в неё
|
||||
// модов. Порядок сохраняется тот же, что и на странице коллекции.
|
||||
func (c *WorkshopClient) Collection(ctx context.Context, id string) ([]string, error) {
|
||||
form := url.Values{}
|
||||
form.Set("collectioncount", "1")
|
||||
form.Set("publishedfileids[0]", id)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, collectionAPI,
|
||||
strings.NewReader(form.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp, err := c.HTTP.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Steam недоступен: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("Steam ответил %s", resp.Status)
|
||||
}
|
||||
|
||||
var payload struct {
|
||||
Response struct {
|
||||
CollectionDetails []struct {
|
||||
PublishedFileID string `json:"publishedfileid"`
|
||||
Result int `json:"result"`
|
||||
Children []struct {
|
||||
PublishedFileID string `json:"publishedfileid"`
|
||||
SortOrder int `json:"sortorder"`
|
||||
FileType int `json:"filetype"`
|
||||
} `json:"children"`
|
||||
} `json:"collectiondetails"`
|
||||
} `json:"response"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
||||
return nil, fmt.Errorf("ответ Steam не разобран: %w", err)
|
||||
}
|
||||
if len(payload.Response.CollectionDetails) == 0 {
|
||||
return nil, fmt.Errorf("коллекция %s не найдена", id)
|
||||
}
|
||||
|
||||
details := payload.Response.CollectionDetails[0]
|
||||
// result == 1 означает "найдено"; всё остальное — не коллекция, скрыта
|
||||
// или удалена.
|
||||
if details.Result != 1 {
|
||||
return nil, fmt.Errorf("коллекция %s недоступна — проверьте ссылку и что она открыта всем", id)
|
||||
}
|
||||
|
||||
children := details.Children
|
||||
sort.Slice(children, func(i, j int) bool { return children[i].SortOrder < children[j].SortOrder })
|
||||
|
||||
items := make([]string, 0, len(children))
|
||||
for _, child := range children {
|
||||
// filetype 0 — обычный элемент мастерской; вложенные коллекции
|
||||
// (filetype 2) пропускаем, иначе в список попадут их id вместо модов.
|
||||
if child.FileType != 0 {
|
||||
continue
|
||||
}
|
||||
items = append(items, child.PublishedFileID)
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return nil, fmt.Errorf("в коллекции %s нет модов", id)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user