590 lines
17 KiB
Go
590 lines
17 KiB
Go
|
|
package client
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"log"
|
||
|
|
"net/http"
|
||
|
|
"net/url"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"gorm.io/gorm"
|
||
|
|
)
|
||
|
|
|
||
|
|
const baseURL = "https://debrid-link.com/api/v2/"
|
||
|
|
|
||
|
|
type Client struct {
|
||
|
|
http *http.Client
|
||
|
|
db *gorm.DB
|
||
|
|
clientID string
|
||
|
|
clientSecret string
|
||
|
|
account *DebridAccount
|
||
|
|
}
|
||
|
|
|
||
|
|
type DebridAccount struct {
|
||
|
|
ID uint `gorm:"primaryKey"`
|
||
|
|
Host string `gorm:"column:host" json:"host"`
|
||
|
|
Username string `gorm:"column:username" json:"username"`
|
||
|
|
Password string `gorm:"column:password" json:"password"`
|
||
|
|
IsActive bool `gorm:"column:is_active" json:"is_active"`
|
||
|
|
AccessToken string `gorm:"column:access_token"`
|
||
|
|
RefreshToken string `gorm:"column:refresh_token"`
|
||
|
|
ExpiresAt time.Time `gorm:"column:expires_at"`
|
||
|
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||
|
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type TokenResponse struct {
|
||
|
|
AccessToken string `json:"access_token"`
|
||
|
|
RefreshToken string `json:"refresh_token"`
|
||
|
|
ExpiresIn int64 `json:"expires_in"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewClient(db *gorm.DB) *Client {
|
||
|
|
return &Client{
|
||
|
|
http: &http.Client{Timeout: 15 * time.Second},
|
||
|
|
db: db,
|
||
|
|
clientID: "bMs32shaby43qVGVKnkRqw",
|
||
|
|
clientSecret: "XRMJ8JNJtJZBsRP8gzBnYc6huLK2cqXay3ihKId9mt4",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
func (c *Client) SetAccount(account *DebridAccount) {
|
||
|
|
c.account = account
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) refreshAccessToken(ctx context.Context) error {
|
||
|
|
form := url.Values{}
|
||
|
|
form.Set("client_id", c.clientID)
|
||
|
|
form.Set("client_secret", c.clientSecret)
|
||
|
|
form.Set("grant_type", "refresh_token")
|
||
|
|
form.Set("refresh_token", c.account.RefreshToken)
|
||
|
|
|
||
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+"oauth/token", strings.NewReader(form.Encode()))
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||
|
|
|
||
|
|
resp, err := c.http.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode >= 400 {
|
||
|
|
b, _ := io.ReadAll(resp.Body)
|
||
|
|
return fmt.Errorf("refresh failed: %s", string(b))
|
||
|
|
}
|
||
|
|
|
||
|
|
var tokenResp TokenResponse
|
||
|
|
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
c.account.AccessToken = tokenResp.AccessToken
|
||
|
|
c.account.RefreshToken = tokenResp.RefreshToken
|
||
|
|
c.account.ExpiresAt = time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second)
|
||
|
|
|
||
|
|
return c.db.Save(c.account).Error
|
||
|
|
}
|
||
|
|
func (c *Client) ToggleActiveStatus(ctx context.Context, id uint) error {
|
||
|
|
if c.db == nil {
|
||
|
|
return errors.New("la connexion à la base de données est manquante")
|
||
|
|
}
|
||
|
|
|
||
|
|
var account DebridAccount
|
||
|
|
if err := c.db.First(&account, id).Error; err != nil {
|
||
|
|
return fmt.Errorf("compte introuvable : %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
account.IsActive = !account.IsActive
|
||
|
|
account.UpdatedAt = time.Now()
|
||
|
|
|
||
|
|
if err := c.db.Save(&account).Error; err != nil {
|
||
|
|
return fmt.Errorf("échec de la mise à jour : %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
// ===========================
|
||
|
|
// CRUD pour DebridAccount
|
||
|
|
// ===========================
|
||
|
|
|
||
|
|
func (c *Client) CreateDebridAccount(ctx context.Context, acc *DebridAccount) error {
|
||
|
|
return c.db.Create(acc).Error
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) GetDebridAccount(ctx context.Context, id uint) (*DebridAccount, error) {
|
||
|
|
var acc DebridAccount
|
||
|
|
if err := c.db.First(&acc, id).Error; err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return &acc, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) ListDebridAccounts(ctx context.Context) ([]DebridAccount, error) {
|
||
|
|
var accounts []DebridAccount
|
||
|
|
if err := c.db.Order("id desc").Find(&accounts).Error; err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return accounts, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) UpdateDebridAccount(ctx context.Context, acc *DebridAccount) error {
|
||
|
|
return c.db.Save(acc).Error
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) DeleteDebridAccount(ctx context.Context, id uint) error {
|
||
|
|
return c.db.Delete(&DebridAccount{}, id).Error
|
||
|
|
}
|
||
|
|
func (c *Client) RequestDeviceCodeWithCredentials(ctx context.Context, username, password string) (*DeviceCodeResponse, error) {
|
||
|
|
|
||
|
|
form := url.Values{}
|
||
|
|
log.Println("[DEBUG] Envoi device_code avec :")
|
||
|
|
log.Println("client_id:", c.clientID)
|
||
|
|
log.Println("username:", username)
|
||
|
|
log.Println("password (len):", len(password))
|
||
|
|
log.Println("scope:", form.Get("scope"))
|
||
|
|
log.Println("URL:", "https://debrid-link.com/api/oauth/device/code")
|
||
|
|
form.Set("client_id", c.clientID)
|
||
|
|
form.Set("grant_type", "password") // possible variation
|
||
|
|
form.Set("username", username)
|
||
|
|
form.Set("password", password)
|
||
|
|
form.Set("scope", "get.post.downloader get.post.seedbox get.account get.post.stream")
|
||
|
|
|
||
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://debrid-link.com/api/oauth/device/code", 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, err
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode >= 400 {
|
||
|
|
body, _ := io.ReadAll(resp.Body)
|
||
|
|
return nil, fmt.Errorf("device code request failed: %s", string(body))
|
||
|
|
}
|
||
|
|
|
||
|
|
var result DeviceCodeResponse
|
||
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return &result, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
func (c *Client) RequestDeviceCode(ctx context.Context) (*DeviceCodeResponse, error) {
|
||
|
|
form := url.Values{}
|
||
|
|
form.Set("client_id", c.clientID)
|
||
|
|
form.Set("scope", "get.post.downloader get.post.seedbox get.account")
|
||
|
|
|
||
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://debrid-link.com/api/oauth/device/code", 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, err
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode >= 400 {
|
||
|
|
body, _ := io.ReadAll(resp.Body)
|
||
|
|
return nil, fmt.Errorf("device code request failed: %s", string(body))
|
||
|
|
}
|
||
|
|
|
||
|
|
var result DeviceCodeResponse
|
||
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return &result, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
type DeviceCodeResponse struct {
|
||
|
|
DeviceCode string `json:"device_code"`
|
||
|
|
UserCode string `json:"user_code"`
|
||
|
|
VerificationURL string `json:"verification_url"`
|
||
|
|
ExpiresIn int `json:"expires_in"`
|
||
|
|
Interval int `json:"interval"`
|
||
|
|
}
|
||
|
|
func (c *Client) PasswordGrant(ctx context.Context, username, password string) (*TokenResponse, error) {
|
||
|
|
form := url.Values{}
|
||
|
|
form.Set("client_id", c.clientID)
|
||
|
|
form.Set("client_secret", c.clientSecret)
|
||
|
|
form.Set("grant_type", "password")
|
||
|
|
form.Set("username", username)
|
||
|
|
form.Set("password", password)
|
||
|
|
|
||
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://debrid-link.com/api/oauth/token", 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, err
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode >= 400 {
|
||
|
|
body, _ := io.ReadAll(resp.Body)
|
||
|
|
return nil, fmt.Errorf("auth error: %s", string(body))
|
||
|
|
}
|
||
|
|
|
||
|
|
var tokens TokenResponse
|
||
|
|
if err := json.NewDecoder(resp.Body).Decode(&tokens); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
return &tokens, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) PollDeviceToken(ctx context.Context, deviceCode string, interval int) (*TokenResponse, error) {
|
||
|
|
form := url.Values{}
|
||
|
|
form.Set("client_id", c.clientID)
|
||
|
|
form.Set("grant_type", "urn:ietf:params:oauth:grant-type:device_code")
|
||
|
|
form.Set("code", deviceCode)
|
||
|
|
|
||
|
|
for {
|
||
|
|
select {
|
||
|
|
case <-ctx.Done():
|
||
|
|
return nil, ctx.Err()
|
||
|
|
case <-time.After(time.Duration(interval) * time.Second):
|
||
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://debrid-link.com/api/oauth/token", 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, err
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode == 200 {
|
||
|
|
var tokens TokenResponse
|
||
|
|
if err := json.NewDecoder(resp.Body).Decode(&tokens); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return &tokens, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
body, _ := io.ReadAll(resp.Body)
|
||
|
|
if strings.Contains(string(body), "authorization_pending") {
|
||
|
|
continue // Attente de validation utilisateur
|
||
|
|
}
|
||
|
|
return nil, fmt.Errorf("device auth failed: %s", string(body))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
func (c *Client) doJSON(ctx context.Context, method, path string, params url.Values, body, out interface{}) error {
|
||
|
|
if c.account == nil {
|
||
|
|
return errors.New("no active Debrid account")
|
||
|
|
}
|
||
|
|
if time.Now().After(c.account.ExpiresAt) {
|
||
|
|
if err := c.refreshAccessToken(ctx); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
urlStr := baseURL + path
|
||
|
|
if params != nil {
|
||
|
|
urlStr += "?" + params.Encode()
|
||
|
|
}
|
||
|
|
|
||
|
|
var reqBody io.Reader
|
||
|
|
if body != nil {
|
||
|
|
b, err := json.Marshal(body)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
reqBody = bytes.NewReader(b)
|
||
|
|
|
||
|
|
// 👇 DEBUG : afficher le body
|
||
|
|
log.Printf("➡️ [API %s] URL: %s\nBody: %s\n", method, urlStr, string(b))
|
||
|
|
} else {
|
||
|
|
log.Printf("➡️ [API %s] URL: %s (empty body)", method, urlStr)
|
||
|
|
}
|
||
|
|
|
||
|
|
req, err := http.NewRequestWithContext(ctx, method, urlStr, reqBody)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
// 👇 DEBUG : afficher le header Authorization
|
||
|
|
authHeader := "Bearer " + c.account.AccessToken
|
||
|
|
log.Printf("➡️ Authorization Header: %s\n", authHeader)
|
||
|
|
|
||
|
|
req.Header.Set("Authorization", authHeader)
|
||
|
|
req.Header.Set("Content-Type", "application/json")
|
||
|
|
|
||
|
|
resp, err := c.http.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode >= 400 {
|
||
|
|
data, _ := io.ReadAll(resp.Body)
|
||
|
|
return fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(data))
|
||
|
|
}
|
||
|
|
|
||
|
|
if out != nil {
|
||
|
|
return json.NewDecoder(resp.Body).Decode(out)
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// =========================== RSS ===========================
|
||
|
|
type RSSFeed struct {
|
||
|
|
ID string `json:"id" gorm:"column:id;primaryKey"`
|
||
|
|
URL string `json:"url" gorm:"column:url"`
|
||
|
|
Enabled bool `json:"enabled" gorm:"column:enabled"`
|
||
|
|
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||
|
|
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type RSSItem struct {
|
||
|
|
Title string `json:"title" gorm:"column:title"`
|
||
|
|
Link string `json:"link" gorm:"column:link"`
|
||
|
|
PubDate time.Time `json:"pubDate" gorm:"column:pub_date"`
|
||
|
|
}
|
||
|
|
func (c *Client) ListRSS(ctx context.Context) ([]RSSFeed, error) {
|
||
|
|
var feeds []RSSFeed
|
||
|
|
if err := c.doJSON(ctx, "GET", "rss", nil, nil, &feeds); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return feeds, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) AddRSS(ctx context.Context, url string) (*RSSFeed, error) {
|
||
|
|
var feed RSSFeed
|
||
|
|
body := map[string]string{"url": url}
|
||
|
|
if err := c.doJSON(ctx, "POST", "rss", nil, body, &feed); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return &feed, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) TestRSS(ctx context.Context, url string) ([]RSSItem, error) {
|
||
|
|
var items []RSSItem
|
||
|
|
body := map[string]string{"url": url}
|
||
|
|
if err := c.doJSON(ctx, "POST", "rss/test", nil, body, &items); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return items, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) DeleteRSS(ctx context.Context, id string) error {
|
||
|
|
return c.doJSON(ctx, "DELETE", fmt.Sprintf("rss/%s", id), nil, nil, nil)
|
||
|
|
}
|
||
|
|
|
||
|
|
// =========================== Seedbox ===========================
|
||
|
|
type Torrent struct {
|
||
|
|
ID string `json:"id" gorm:"column:id;primaryKey"`
|
||
|
|
Name string `json:"name" gorm:"column:name"`
|
||
|
|
Status string `json:"status" gorm:"column:status"`
|
||
|
|
Progress int `json:"progress" gorm:"column:progress"`
|
||
|
|
Added time.Time `json:"added" gorm:"column:added"`
|
||
|
|
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||
|
|
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) ListTorrents(ctx context.Context) ([]Torrent, error) {
|
||
|
|
var torrents []Torrent
|
||
|
|
if err := c.doJSON(ctx, "GET", "seedbox/torrents", nil, nil, &torrents); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return torrents, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) AddTorrent(ctx context.Context, link string) (*Torrent, error) {
|
||
|
|
var t Torrent
|
||
|
|
body := map[string]string{"link": link}
|
||
|
|
if err := c.doJSON(ctx, "POST", "seedbox/torrents", nil, body, &t); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return &t, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Client) RemoveTorrents(ctx context.Context, ids []string) error {
|
||
|
|
body := map[string][]string{"ids": ids}
|
||
|
|
return c.doJSON(ctx, "DELETE", "seedbox/torrents", nil, body, nil)
|
||
|
|
}
|
||
|
|
|
||
|
|
// =========================== Downloader ===========================
|
||
|
|
type Link struct {
|
||
|
|
ID string `json:"id" gorm:"primaryKey;column:id"`
|
||
|
|
Name string `json:"name" gorm:"column:name"`
|
||
|
|
URL string `json:"url" gorm:"column:url"` // Lien d'origine
|
||
|
|
DownloadURL string `json:"downloadUrl" gorm:"column:download_url"` // Lien débridé direct
|
||
|
|
Host string `json:"host" gorm:"column:host"` // Nom de l'hébergeur
|
||
|
|
Size int64 `json:"size" gorm:"column:size"` // Taille en octets
|
||
|
|
Chunk int `json:"chunk" gorm:"column:chunk"` // Nombre de chunks
|
||
|
|
Expired bool `json:"expired" gorm:"column:expired"` // Lien expiré ou non
|
||
|
|
Created int64 `json:"created" gorm:"column:created"` // Timestamp
|
||
|
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||
|
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||
|
|
}
|
||
|
|
type StreamInfo struct {
|
||
|
|
ID string `json:"id" gorm:"primaryKey;column:id"`
|
||
|
|
StreamURL string `json:"streamUrl" gorm:"column:stream_url"`
|
||
|
|
DownloadURL string `json:"downloadUrl" gorm:"column:download_url"`
|
||
|
|
Type string `json:"type" gorm:"column:type"` // hls ou mp4
|
||
|
|
MimeType string `json:"mimetype" gorm:"column:mimetype"` // ex: video/mp4
|
||
|
|
Domain string `json:"domain" gorm:"column:domain"`
|
||
|
|
|
||
|
|
// Champs du fichier lié (ex : nom de la vidéo)
|
||
|
|
FileID string `json:"-" gorm:"column:file_id"` // lien avec le champ File.ID ci-dessous
|
||
|
|
FileName string `json:"-" gorm:"column:file_name"` // nom fichier
|
||
|
|
FileSize int64 `json:"-" gorm:"column:file_size"` // taille fichier
|
||
|
|
|
||
|
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||
|
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
func (c *Client) ListLinks(ctx context.Context) ([]Link, error) {
|
||
|
|
var links []Link
|
||
|
|
if err := c.doJSON(ctx, "GET", "downloader/links", nil, nil, &links); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return links, nil
|
||
|
|
}
|
||
|
|
func (c *Client) AddLink(ctx context.Context, link string) ([]Link, error) {
|
||
|
|
var envelope struct {
|
||
|
|
Success bool `json:"success"`
|
||
|
|
Value json.RawMessage `json:"value"`
|
||
|
|
}
|
||
|
|
|
||
|
|
body := map[string]string{"url": link}
|
||
|
|
|
||
|
|
// Requête brute
|
||
|
|
if err := c.doJSON(ctx, "POST", "downloader/add", nil, body, &envelope); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
var links []Link
|
||
|
|
|
||
|
|
switch envelope.Value[0] {
|
||
|
|
case '{':
|
||
|
|
var single Link
|
||
|
|
if err := json.Unmarshal(envelope.Value, &single); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
links = append(links, single)
|
||
|
|
case '[':
|
||
|
|
if err := json.Unmarshal(envelope.Value, &links); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
default:
|
||
|
|
return nil, errors.New("format de réponse inattendu")
|
||
|
|
}
|
||
|
|
|
||
|
|
return links, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
func (c *Client) RemoveLinks(ctx context.Context, ids []string) error {
|
||
|
|
body := map[string][]string{"ids": ids}
|
||
|
|
return c.doJSON(ctx, "DELETE", "downloader/links", nil, body, nil)
|
||
|
|
}
|
||
|
|
|
||
|
|
// =========================== Files ===========================
|
||
|
|
type File struct {
|
||
|
|
ID string `json:"id" gorm:"column:id;primaryKey"`
|
||
|
|
Name string `json:"name" gorm:"column:name"`
|
||
|
|
Size int64 `json:"size" gorm:"column:size"`
|
||
|
|
Link string `json:"link" gorm:"column:link"`
|
||
|
|
ParentID string `json:"parent_id" gorm:"column:parent_id"`
|
||
|
|
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||
|
|
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
func (c *Client) ListFiles(ctx context.Context, parentID string) ([]File, error) {
|
||
|
|
var files []File
|
||
|
|
path := fmt.Sprintf("files/%s", parentID)
|
||
|
|
if err := c.doJSON(ctx, "GET", path, nil, nil, &files); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return files, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// =========================== Stream ===========================
|
||
|
|
// func (c *Client) CreateTranscode(ctx context.Context, fileID, preset string) (string, error) {
|
||
|
|
// var resp struct{ TranscodeID string `json:"transcodeId"` }
|
||
|
|
// body := map[string]string{"fileId": fileID}
|
||
|
|
// if err := c.doJSON(ctx, "POST", "stream/transcode", nil, body, &resp); err != nil {
|
||
|
|
// return "", err
|
||
|
|
// }
|
||
|
|
// return resp.TranscodeID, nil
|
||
|
|
// }
|
||
|
|
|
||
|
|
func (c *Client) CreateTranscode(ctx context.Context, fileID string) (*StreamInfo, error) {
|
||
|
|
body := map[string]string{"id": fileID}
|
||
|
|
|
||
|
|
var raw struct {
|
||
|
|
Success bool `json:"success"`
|
||
|
|
Value struct {
|
||
|
|
ID string `json:"id"`
|
||
|
|
StreamURL string `json:"streamUrl"`
|
||
|
|
DownloadURL string `json:"downloadUrl"`
|
||
|
|
Type string `json:"type"`
|
||
|
|
MimeType string `json:"mimetype"`
|
||
|
|
Domain string `json:"domain"`
|
||
|
|
File struct {
|
||
|
|
ID string `json:"id"`
|
||
|
|
Name string `json:"name"`
|
||
|
|
Size int64 `json:"size"`
|
||
|
|
Source string `json:"source"`
|
||
|
|
} `json:"file"`
|
||
|
|
} `json:"value"`
|
||
|
|
}
|
||
|
|
|
||
|
|
path := "stream/transcode/add"
|
||
|
|
if err := c.doJSON(ctx, "POST", path, nil, body, &raw); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
info := &StreamInfo{
|
||
|
|
ID: raw.Value.ID,
|
||
|
|
StreamURL: raw.Value.StreamURL,
|
||
|
|
DownloadURL: raw.Value.DownloadURL,
|
||
|
|
Type: raw.Value.Type,
|
||
|
|
MimeType: raw.Value.MimeType,
|
||
|
|
Domain: raw.Value.Domain,
|
||
|
|
FileID: raw.Value.File.ID,
|
||
|
|
FileName: raw.Value.File.Name,
|
||
|
|
FileSize: raw.Value.File.Size,
|
||
|
|
}
|
||
|
|
return info, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|