small refactor*
This commit is contained in:
parent
b6b541e050
commit
24a4d30275
232 changed files with 2164 additions and 1906 deletions
396
plugins/qbittorrent/pkg/qbittorrent/application.go
Normal file
396
plugins/qbittorrent/pkg/qbittorrent/application.go
Normal file
|
@ -0,0 +1,396 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Application interface {
|
||||
// Version get application version
|
||||
Version(context.Context) (string, error)
|
||||
// WebApiVersion get webapi version
|
||||
WebApiVersion(context.Context) (string, error)
|
||||
// BuildInfo get build info
|
||||
BuildInfo(context.Context) (*BuildInfo, error)
|
||||
// Shutdown exit application
|
||||
Shutdown(context.Context) error
|
||||
// GetPreferences get application preferences
|
||||
GetPreferences(context.Context) (*Preferences, error)
|
||||
// SetPreferences set application preferences
|
||||
SetPreferences(context.Context, *Preferences) error
|
||||
// DefaultSavePath get default save path
|
||||
DefaultSavePath(context.Context) (string, error)
|
||||
}
|
||||
|
||||
type BuildInfo struct {
|
||||
BitNess int `json:"bitness,omitempty"`
|
||||
Boost string `json:"boost,omitempty"`
|
||||
LibTorrent string `json:"libtorrent,omitempty"`
|
||||
Openssl string `json:"openssl,omitempty"`
|
||||
QT string `json:"qt,omitempty"`
|
||||
Zlib string `json:"zlib,omitempty"`
|
||||
}
|
||||
|
||||
type Preferences struct {
|
||||
AddToTopOfQueue bool `json:"add_to_top_of_queue,omitempty"`
|
||||
AddTrackers string `json:"add_trackers,omitempty"`
|
||||
AddTrackersEnabled bool `json:"add_trackers_enabled,omitempty"`
|
||||
AltDlLimit int `json:"alt_dl_limit,omitempty"`
|
||||
AltUpLimit int `json:"alt_up_limit,omitempty"`
|
||||
AlternativeWebuiEnabled bool `json:"alternative_webui_enabled,omitempty"`
|
||||
AlternativeWebuiPath string `json:"alternative_webui_path,omitempty"`
|
||||
AnnounceIP string `json:"announce_ip,omitempty"`
|
||||
AnnounceToAllTiers bool `json:"announce_to_all_tiers,omitempty"`
|
||||
AnnounceToAllTrackers bool `json:"announce_to_all_trackers,omitempty"`
|
||||
AnonymousMode bool `json:"anonymous_mode,omitempty"`
|
||||
AsyncIoThreads int `json:"async_io_threads,omitempty"`
|
||||
AutoDeleteMode int `json:"auto_delete_mode,omitempty"`
|
||||
AutoTmmEnabled bool `json:"auto_tmm_enabled,omitempty"`
|
||||
AutorunEnabled bool `json:"autorun_enabled,omitempty"`
|
||||
AutorunOnTorrentAddedEnabled bool `json:"autorun_on_torrent_added_enabled,omitempty"`
|
||||
AutorunOnTorrentAddedProgram string `json:"autorun_on_torrent_added_program,omitempty"`
|
||||
AutorunProgram string `json:"autorun_program,omitempty"`
|
||||
BannedIPs string `json:"banned_IPs,omitempty"`
|
||||
BdecodeDepthLimit int `json:"bdecode_depth_limit,omitempty"`
|
||||
BdecodeTokenLimit int `json:"bdecode_token_limit,omitempty"`
|
||||
BittorrentProtocol int `json:"bittorrent_protocol,omitempty"`
|
||||
BlockPeersOnPrivilegedPorts bool `json:"block_peers_on_privileged_ports,omitempty"`
|
||||
BypassAuthSubnetWhitelist string `json:"bypass_auth_subnet_whitelist,omitempty"`
|
||||
BypassAuthSubnetWhitelistEnabled bool `json:"bypass_auth_subnet_whitelist_enabled,omitempty"`
|
||||
BypassLocalAuth bool `json:"bypass_local_auth,omitempty"`
|
||||
CategoryChangedTmmEnabled bool `json:"category_changed_tmm_enabled,omitempty"`
|
||||
CheckingMemoryUse int `json:"checking_memory_use,omitempty"`
|
||||
ConnectionSpeed int `json:"connection_speed,omitempty"`
|
||||
CurrentInterfaceAddress string `json:"current_interface_address,omitempty"`
|
||||
CurrentInterfaceName string `json:"current_interface_name,omitempty"`
|
||||
CurrentNetworkInterface string `json:"current_network_interface,omitempty"`
|
||||
Dht bool `json:"dht,omitempty"`
|
||||
DiskCache int `json:"disk_cache,omitempty"`
|
||||
DiskCacheTTL int `json:"disk_cache_ttl,omitempty"`
|
||||
DiskIoReadMode int `json:"disk_io_read_mode,omitempty"`
|
||||
DiskIoType int `json:"disk_io_type,omitempty"`
|
||||
DiskIoWriteMode int `json:"disk_io_write_mode,omitempty"`
|
||||
DiskQueueSize int `json:"disk_queue_size,omitempty"`
|
||||
DlLimit int `json:"dl_limit,omitempty"`
|
||||
DontCountSlowTorrents bool `json:"dont_count_slow_torrents,omitempty"`
|
||||
DyndnsDomain string `json:"dyndns_domain,omitempty"`
|
||||
DyndnsEnabled bool `json:"dyndns_enabled,omitempty"`
|
||||
DyndnsPassword string `json:"dyndns_password,omitempty"`
|
||||
DyndnsService int `json:"dyndns_service,omitempty"`
|
||||
DyndnsUsername string `json:"dyndns_username,omitempty"`
|
||||
EmbeddedTrackerPort int `json:"embedded_tracker_port,omitempty"`
|
||||
EmbeddedTrackerPortForwarding bool `json:"embedded_tracker_port_forwarding,omitempty"`
|
||||
EnableCoalesceReadWrite bool `json:"enable_coalesce_read_write,omitempty"`
|
||||
EnableEmbeddedTracker bool `json:"enable_embedded_tracker,omitempty"`
|
||||
EnableMultiConnectionsFromSameIP bool `json:"enable_multi_connections_from_same_ip,omitempty"`
|
||||
EnablePieceExtentAffinity bool `json:"enable_piece_extent_affinity,omitempty"`
|
||||
EnableUploadSuggestions bool `json:"enable_upload_suggestions,omitempty"`
|
||||
Encryption int `json:"encryption,omitempty"`
|
||||
ExcludedFileNames string `json:"excluded_file_names,omitempty"`
|
||||
ExcludedFileNamesEnabled bool `json:"excluded_file_names_enabled,omitempty"`
|
||||
ExportDir string `json:"export_dir,omitempty"`
|
||||
ExportDirFin string `json:"export_dir_fin,omitempty"`
|
||||
FileLogAge int `json:"file_log_age,omitempty"`
|
||||
FileLogAgeType int `json:"file_log_age_type,omitempty"`
|
||||
FileLogBackupEnabled bool `json:"file_log_backup_enabled,omitempty"`
|
||||
FileLogDeleteOld bool `json:"file_log_delete_old,omitempty"`
|
||||
FileLogEnabled bool `json:"file_log_enabled,omitempty"`
|
||||
FileLogMaxSize int `json:"file_log_max_size,omitempty"`
|
||||
FileLogPath string `json:"file_log_path,omitempty"`
|
||||
FilePoolSize int `json:"file_pool_size,omitempty"`
|
||||
HashingThreads int `json:"hashing_threads,omitempty"`
|
||||
I2PAddress string `json:"i2p_address,omitempty"`
|
||||
I2PEnabled bool `json:"i2p_enabled,omitempty"`
|
||||
I2PInboundLength int `json:"i2p_inbound_length,omitempty"`
|
||||
I2PInboundQuantity int `json:"i2p_inbound_quantity,omitempty"`
|
||||
I2PMixedMode bool `json:"i2p_mixed_mode,omitempty"`
|
||||
I2POutboundLength int `json:"i2p_outbound_length,omitempty"`
|
||||
I2POutboundQuantity int `json:"i2p_outbound_quantity,omitempty"`
|
||||
I2PPort int `json:"i2p_port,omitempty"`
|
||||
IdnSupportEnabled bool `json:"idn_support_enabled,omitempty"`
|
||||
IncompleteFilesExt bool `json:"incomplete_files_ext,omitempty"`
|
||||
IPFilterEnabled bool `json:"ip_filter_enabled,omitempty"`
|
||||
IPFilterPath string `json:"ip_filter_path,omitempty"`
|
||||
IPFilterTrackers bool `json:"ip_filter_trackers,omitempty"`
|
||||
LimitLanPeers bool `json:"limit_lan_peers,omitempty"`
|
||||
LimitTCPOverhead bool `json:"limit_tcp_overhead,omitempty"`
|
||||
LimitUtpRate bool `json:"limit_utp_rate,omitempty"`
|
||||
ListenPort int `json:"listen_port,omitempty"`
|
||||
Locale string `json:"locale,omitempty"`
|
||||
Lsd bool `json:"lsd,omitempty"`
|
||||
MailNotificationAuthEnabled bool `json:"mail_notification_auth_enabled,omitempty"`
|
||||
MailNotificationEmail string `json:"mail_notification_email,omitempty"`
|
||||
MailNotificationEnabled bool `json:"mail_notification_enabled,omitempty"`
|
||||
MailNotificationPassword string `json:"mail_notification_password,omitempty"`
|
||||
MailNotificationSender string `json:"mail_notification_sender,omitempty"`
|
||||
MailNotificationSMTP string `json:"mail_notification_smtp,omitempty"`
|
||||
MailNotificationSslEnabled bool `json:"mail_notification_ssl_enabled,omitempty"`
|
||||
MailNotificationUsername string `json:"mail_notification_username,omitempty"`
|
||||
MaxActiveCheckingTorrents int `json:"max_active_checking_torrents,omitempty"`
|
||||
MaxActiveDownloads int `json:"max_active_downloads,omitempty"`
|
||||
MaxActiveTorrents int `json:"max_active_torrents,omitempty"`
|
||||
MaxActiveUploads int `json:"max_active_uploads,omitempty"`
|
||||
MaxConcurrentHTTPAnnounces int `json:"max_concurrent_http_announces,omitempty"`
|
||||
MaxConnec int `json:"max_connec,omitempty"`
|
||||
MaxConnecPerTorrent int `json:"max_connec_per_torrent,omitempty"`
|
||||
MaxInactiveSeedingTime int `json:"max_inactive_seeding_time,omitempty"`
|
||||
MaxInactiveSeedingTimeEnabled bool `json:"max_inactive_seeding_time_enabled,omitempty"`
|
||||
MaxRatio int `json:"max_ratio,omitempty"`
|
||||
MaxRatioAct int `json:"max_ratio_act,omitempty"`
|
||||
MaxRatioEnabled bool `json:"max_ratio_enabled,omitempty"`
|
||||
MaxSeedingTime int `json:"max_seeding_time,omitempty"`
|
||||
MaxSeedingTimeEnabled bool `json:"max_seeding_time_enabled,omitempty"`
|
||||
MaxUploads int `json:"max_uploads,omitempty"`
|
||||
MaxUploadsPerTorrent int `json:"max_uploads_per_torrent,omitempty"`
|
||||
MemoryWorkingSetLimit int `json:"memory_working_set_limit,omitempty"`
|
||||
MergeTrackers bool `json:"merge_trackers,omitempty"`
|
||||
OutgoingPortsMax int `json:"outgoing_ports_max,omitempty"`
|
||||
OutgoingPortsMin int `json:"outgoing_ports_min,omitempty"`
|
||||
PeerTos int `json:"peer_tos,omitempty"`
|
||||
PeerTurnover int `json:"peer_turnover,omitempty"`
|
||||
PeerTurnoverCutoff int `json:"peer_turnover_cutoff,omitempty"`
|
||||
PeerTurnoverInterval int `json:"peer_turnover_interval,omitempty"`
|
||||
PerformanceWarning bool `json:"performance_warning,omitempty"`
|
||||
Pex bool `json:"pex,omitempty"`
|
||||
PreallocateAll bool `json:"preallocate_all,omitempty"`
|
||||
ProxyAuthEnabled bool `json:"proxy_auth_enabled,omitempty"`
|
||||
ProxyBittorrent bool `json:"proxy_bittorrent,omitempty"`
|
||||
ProxyHostnameLookup bool `json:"proxy_hostname_lookup,omitempty"`
|
||||
ProxyIP string `json:"proxy_ip,omitempty"`
|
||||
ProxyMisc bool `json:"proxy_misc,omitempty"`
|
||||
ProxyPassword string `json:"proxy_password,omitempty"`
|
||||
ProxyPeerConnections bool `json:"proxy_peer_connections,omitempty"`
|
||||
ProxyPort int `json:"proxy_port,omitempty"`
|
||||
ProxyRss bool `json:"proxy_rss,omitempty"`
|
||||
ProxyType string `json:"proxy_type,omitempty"`
|
||||
ProxyUsername string `json:"proxy_username,omitempty"`
|
||||
QueueingEnabled bool `json:"queueing_enabled,omitempty"`
|
||||
RandomPort bool `json:"random_port,omitempty"`
|
||||
ReannounceWhenAddressChanged bool `json:"reannounce_when_address_changed,omitempty"`
|
||||
RecheckCompletedTorrents bool `json:"recheck_completed_torrents,omitempty"`
|
||||
RefreshInterval int `json:"refresh_interval,omitempty"`
|
||||
RequestQueueSize int `json:"request_queue_size,omitempty"`
|
||||
ResolvePeerCountries bool `json:"resolve_peer_countries,omitempty"`
|
||||
ResumeDataStorageType string `json:"resume_data_storage_type,omitempty"`
|
||||
RssAutoDownloadingEnabled bool `json:"rss_auto_downloading_enabled,omitempty"`
|
||||
RssDownloadRepackProperEpisodes bool `json:"rss_download_repack_proper_episodes,omitempty"`
|
||||
RssMaxArticlesPerFeed int `json:"rss_max_articles_per_feed,omitempty"`
|
||||
RssProcessingEnabled bool `json:"rss_processing_enabled,omitempty"`
|
||||
RssRefreshInterval int `json:"rss_refresh_interval,omitempty"`
|
||||
RssSmartEpisodeFilters string `json:"rss_smart_episode_filters,omitempty"`
|
||||
SavePath string `json:"save_path,omitempty"`
|
||||
SavePathChangedTmmEnabled bool `json:"save_path_changed_tmm_enabled,omitempty"`
|
||||
SaveResumeDataInterval int `json:"save_resume_data_interval,omitempty"`
|
||||
ScheduleFromHour int `json:"schedule_from_hour,omitempty"`
|
||||
ScheduleFromMin int `json:"schedule_from_min,omitempty"`
|
||||
ScheduleToHour int `json:"schedule_to_hour,omitempty"`
|
||||
ScheduleToMin int `json:"schedule_to_min,omitempty"`
|
||||
SchedulerDays int `json:"scheduler_days,omitempty"`
|
||||
SchedulerEnabled bool `json:"scheduler_enabled,omitempty"`
|
||||
SendBufferLowWatermark int `json:"send_buffer_low_watermark,omitempty"`
|
||||
SendBufferWatermark int `json:"send_buffer_watermark,omitempty"`
|
||||
SendBufferWatermarkFactor int `json:"send_buffer_watermark_factor,omitempty"`
|
||||
SlowTorrentDlRateThreshold int `json:"slow_torrent_dl_rate_threshold,omitempty"`
|
||||
SlowTorrentInactiveTimer int `json:"slow_torrent_inactive_timer,omitempty"`
|
||||
SlowTorrentUlRateThreshold int `json:"slow_torrent_ul_rate_threshold,omitempty"`
|
||||
SocketBacklogSize int `json:"socket_backlog_size,omitempty"`
|
||||
SocketReceiveBufferSize int `json:"socket_receive_buffer_size,omitempty"`
|
||||
SocketSendBufferSize int `json:"socket_send_buffer_size,omitempty"`
|
||||
SsrfMitigation bool `json:"ssrf_mitigation,omitempty"`
|
||||
StartPausedEnabled bool `json:"start_paused_enabled,omitempty"`
|
||||
StopTrackerTimeout int `json:"stop_tracker_timeout,omitempty"`
|
||||
TempPath string `json:"temp_path,omitempty"`
|
||||
TempPathEnabled bool `json:"temp_path_enabled,omitempty"`
|
||||
TorrentChangedTmmEnabled bool `json:"torrent_changed_tmm_enabled,omitempty"`
|
||||
TorrentContentLayout string `json:"torrent_content_layout,omitempty"`
|
||||
TorrentFileSizeLimit int `json:"torrent_file_size_limit,omitempty"`
|
||||
TorrentStopCondition string `json:"torrent_stop_condition,omitempty"`
|
||||
UpLimit int `json:"up_limit,omitempty"`
|
||||
UploadChokingAlgorithm int `json:"upload_choking_algorithm,omitempty"`
|
||||
UploadSlotsBehavior int `json:"upload_slots_behavior,omitempty"`
|
||||
Upnp bool `json:"upnp,omitempty"`
|
||||
UpnpLeaseDuration int `json:"upnp_lease_duration,omitempty"`
|
||||
UseCategoryPathsInManualMode bool `json:"use_category_paths_in_manual_mode,omitempty"`
|
||||
UseHTTPS bool `json:"use_https,omitempty"`
|
||||
UseSubcategories bool `json:"use_subcategories,omitempty"`
|
||||
UtpTCPMixedMode int `json:"utp_tcp_mixed_mode,omitempty"`
|
||||
ValidateHTTPSTrackerCertificate bool `json:"validate_https_tracker_certificate,omitempty"`
|
||||
WebUIAddress string `json:"web_ui_address,omitempty"`
|
||||
WebUIBanDuration int `json:"web_ui_ban_duration,omitempty"`
|
||||
WebUIClickjackingProtectionEnabled bool `json:"web_ui_clickjacking_protection_enabled,omitempty"`
|
||||
WebUICsrfProtectionEnabled bool `json:"web_ui_csrf_protection_enabled,omitempty"`
|
||||
WebUICustomHTTPHeaders string `json:"web_ui_custom_http_headers,omitempty"`
|
||||
WebUIDomainList string `json:"web_ui_domain_list,omitempty"`
|
||||
WebUIHostHeaderValidationEnabled bool `json:"web_ui_host_header_validation_enabled,omitempty"`
|
||||
WebUIHTTPSCertPath string `json:"web_ui_https_cert_path,omitempty"`
|
||||
WebUIHTTPSKeyPath string `json:"web_ui_https_key_path,omitempty"`
|
||||
WebUIMaxAuthFailCount int `json:"web_ui_max_auth_fail_count,omitempty"`
|
||||
WebUIPort int `json:"web_ui_port,omitempty"`
|
||||
WebUIReverseProxiesList string `json:"web_ui_reverse_proxies_list,omitempty"`
|
||||
WebUIReverseProxyEnabled bool `json:"web_ui_reverse_proxy_enabled,omitempty"`
|
||||
WebUISecureCookieEnabled bool `json:"web_ui_secure_cookie_enabled,omitempty"`
|
||||
WebUISessionTimeout int `json:"web_ui_session_timeout,omitempty"`
|
||||
WebUIUpnp bool `json:"web_ui_upnp,omitempty"`
|
||||
WebUIUseCustomHTTPHeadersEnabled bool `json:"web_ui_use_custom_http_headers_enabled,omitempty"`
|
||||
WebUIUsername string `json:"web_ui_username,omitempty"`
|
||||
}
|
||||
|
||||
func (c *client) Version(ctx context.Context) (string, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Application.Version")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/app/version", c.config.Address)
|
||||
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return "", errors.New("get version failed: " + string(result.body))
|
||||
}
|
||||
|
||||
return string(result.body), nil
|
||||
}
|
||||
|
||||
func (c *client) WebApiVersion(ctx context.Context) (string, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Application.WebApiVersion")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/app/webapiVersion", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return "", errors.New("get version failed: " + string(result.body))
|
||||
}
|
||||
|
||||
return string(result.body), nil
|
||||
}
|
||||
|
||||
func (c *client) BuildInfo(ctx context.Context) (*BuildInfo, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Application.BuildInfo")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/app/buildInfo", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return nil, errors.New("get build info failed: " + string(result.body))
|
||||
}
|
||||
|
||||
var build = new(BuildInfo)
|
||||
if err := json.Unmarshal(result.body, build); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return build, nil
|
||||
}
|
||||
|
||||
func (c *client) Shutdown(ctx context.Context) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Application.Shutdown")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/app/shutdown", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
method: http.MethodPost,
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("shutdown application failed: " + string(result.body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) GetPreferences(ctx context.Context) (*Preferences, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Application.GetPreferences")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/app/preferences", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return nil, errors.New("get preference failed: " + string(result.body))
|
||||
}
|
||||
|
||||
var preferences = new(Preferences)
|
||||
if err := json.Unmarshal(result.body, preferences); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return preferences, nil
|
||||
}
|
||||
|
||||
func (c *client) SetPreferences(ctx context.Context, prefs *Preferences) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Application.SetPreferences")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/app/setPreferences", c.config.Address)
|
||||
data, err := json.Marshal(prefs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var formData bytes.Buffer
|
||||
formData.Write([]byte("json="))
|
||||
formData.Write(data)
|
||||
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
method: http.MethodPost,
|
||||
url: apiUrl,
|
||||
contentType: ContentTypeFormUrlEncoded,
|
||||
body: &formData,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("set preference failed: " + string(result.body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) DefaultSavePath(ctx context.Context) (string, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Application.DefaultSavePath")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/app/defaultSavePath", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return "", errors.New("get default save path failed: " + string(result.body))
|
||||
}
|
||||
|
||||
return string(result.body), nil
|
||||
}
|
73
plugins/qbittorrent/pkg/qbittorrent/application_test.go
Normal file
73
plugins/qbittorrent/pkg/qbittorrent/application_test.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClient_Version(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
version, err := c.Application().Version(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(version)
|
||||
}
|
||||
|
||||
func TestClient_WebApiVersion(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
version, err := c.Application().WebApiVersion(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(version)
|
||||
}
|
||||
|
||||
func TestClient_BuildInfo(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
info, err := c.Application().BuildInfo(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("build: %+v", info)
|
||||
}
|
||||
|
||||
func TestClient_Shutdown(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
if err := c.Application().Shutdown(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("shutting down")
|
||||
}
|
||||
|
||||
func TestClient_GetPreferences(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
prefs, err := c.Application().GetPreferences(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("prefs: %+v", prefs)
|
||||
}
|
||||
|
||||
func TestClient_SetPreferences(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
prefs, err := c.Application().GetPreferences(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
prefs.FileLogAge = 301
|
||||
if err := c.Application().SetPreferences(ctx, prefs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("success")
|
||||
}
|
||||
|
||||
func TestClient_DefaultSavePath(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
path, err := c.Application().DefaultSavePath(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("path: %s", path)
|
||||
}
|
91
plugins/qbittorrent/pkg/qbittorrent/authentication.go
Normal file
91
plugins/qbittorrent/pkg/qbittorrent/authentication.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Authentication interface {
|
||||
// Login cookie-based authentication, after calling NewClient, do not need to call Login again,
|
||||
// it is the default behavior
|
||||
Login(ctx context.Context) error
|
||||
// Logout deactivate cookies
|
||||
Logout(ctx context.Context) error
|
||||
}
|
||||
|
||||
func (c *client) Login(ctx context.Context) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Authentication.Login")
|
||||
defer span.End()
|
||||
|
||||
if c.config.Username == "" || c.config.Password == "" {
|
||||
return errors.New("username or password is empty")
|
||||
}
|
||||
|
||||
formData := url.Values{}
|
||||
formData.Set("username", c.config.Username)
|
||||
formData.Set("password", c.config.Password)
|
||||
encodedFormData := formData.Encode()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/auth/login", c.config.Address)
|
||||
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
method: http.MethodPost,
|
||||
url: apiUrl,
|
||||
body: strings.NewReader(encodedFormData),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("login failed: " + string(result.body))
|
||||
}
|
||||
|
||||
if string(result.body) == "Fails." {
|
||||
return ErrAuthFailed
|
||||
}
|
||||
|
||||
if string(result.body) != "Ok." {
|
||||
return errors.New("login failed: " + string(result.body))
|
||||
}
|
||||
|
||||
if c.client.Jar == nil {
|
||||
c.client.Jar, err = cookiejar.New(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
u, err := url.Parse(c.config.Address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.client.Jar.SetCookies(u, result.cookies)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) Logout(ctx context.Context) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Authentication.Logout")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/auth/logout", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
method: http.MethodPost,
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("logout failed: " + string(result.body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
24
plugins/qbittorrent/pkg/qbittorrent/authentication_test.go
Normal file
24
plugins/qbittorrent/pkg/qbittorrent/authentication_test.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClient_Login(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
if err := c.Authentication().Login(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_Logout(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
if err := c.Authentication().Login(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := c.Authentication().Logout(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
72
plugins/qbittorrent/pkg/qbittorrent/client.go
Normal file
72
plugins/qbittorrent/pkg/qbittorrent/client.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
)
|
||||
|
||||
var trace = otel.Tracer("git.kmsign.ru/royalcat/tstor/server/pkg/qbittorrent")
|
||||
|
||||
// Client represents a qBittorrent client
|
||||
type Client interface {
|
||||
// Authentication auth qBittorrent client
|
||||
Authentication() Authentication
|
||||
// Application get qBittorrent application info
|
||||
Application() Application
|
||||
// Log get qBittorrent log
|
||||
Log() Log
|
||||
// Sync get qBittorrent events
|
||||
Sync() Sync
|
||||
// Transfer transfer manage
|
||||
Transfer() Transfer
|
||||
// Torrent manage for torrent
|
||||
Torrent() Torrent
|
||||
// Search api for search
|
||||
Search() Search
|
||||
// RSS api for rss
|
||||
RSS() RSS
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, cfg *Config) (Client, error) {
|
||||
var c = &client{config: cfg, client: newClient(cfg.ConnectionMaxIdles, cfg.ConnectionTimeout)}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func LoginClient(ctx context.Context, cfg *Config) (Client, error) {
|
||||
var c = &client{config: cfg, client: newClient(cfg.ConnectionMaxIdles, cfg.ConnectionTimeout)}
|
||||
if err := c.Authentication().Login(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cfg.RefreshCookie {
|
||||
go c.refreshCookie()
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// newClient creates and returns a new clientPool
|
||||
func newClient(maxIdle int, timeout time.Duration) *http.Client {
|
||||
if maxIdle == 0 {
|
||||
maxIdle = 128
|
||||
}
|
||||
if timeout == 0 {
|
||||
timeout = time.Second * 3
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
MaxIdleConns: maxIdle,
|
||||
},
|
||||
Timeout: timeout,
|
||||
}
|
||||
}
|
128
plugins/qbittorrent/pkg/qbittorrent/client_impl.go
Normal file
128
plugins/qbittorrent/pkg/qbittorrent/client_impl.go
Normal file
|
@ -0,0 +1,128 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type responseResult struct {
|
||||
code int
|
||||
body []byte
|
||||
cookies []*http.Cookie
|
||||
}
|
||||
|
||||
type requestData struct {
|
||||
method string
|
||||
url string
|
||||
contentType string
|
||||
body io.Reader
|
||||
}
|
||||
|
||||
var _ Client = (*client)(nil)
|
||||
|
||||
type client struct {
|
||||
config *Config
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (c *client) Authentication() Authentication {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *client) Application() Application {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *client) Log() Log {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *client) Sync() Sync {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *client) Transfer() Transfer {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *client) Torrent() Torrent {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *client) Search() Search {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *client) RSS() RSS {
|
||||
return c
|
||||
}
|
||||
|
||||
// doRequest send request
|
||||
func (c *client) doRequest(ctx context.Context, data *requestData) (*responseResult, error) {
|
||||
if data.method == "" {
|
||||
data.method = "GET"
|
||||
}
|
||||
if data.contentType == "" {
|
||||
data.contentType = ContentTypeFormUrlEncoded
|
||||
}
|
||||
request, err := http.NewRequestWithContext(ctx, data.method, data.url, data.body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Set("Content-Type", data.contentType)
|
||||
for key, value := range c.config.CustomHeaders {
|
||||
request.Header.Set(key, value)
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
readAll, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &responseResult{code: resp.StatusCode, body: readAll, cookies: resp.Cookies()}, nil
|
||||
}
|
||||
|
||||
func (c *client) cookies() (string, error) {
|
||||
if c.client.Jar == nil {
|
||||
return "", ErrNotLogin
|
||||
}
|
||||
u, err := url.Parse(c.config.Address)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cookies := c.client.Jar.Cookies(u)
|
||||
if len(cookies) == 0 {
|
||||
return "", ErrNotLogin
|
||||
}
|
||||
var builder strings.Builder
|
||||
for _, cookie := range cookies {
|
||||
builder.WriteString(fmt.Sprintf("%s=%s; ", cookie.Name, cookie.Value))
|
||||
}
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
func (c *client) refreshCookie() {
|
||||
ctx := context.Background()
|
||||
if c.config.RefreshIntervals == 0 {
|
||||
c.config.RefreshIntervals = time.Hour
|
||||
}
|
||||
var ticker = time.NewTicker(c.config.RefreshIntervals)
|
||||
for range ticker.C {
|
||||
if err := c.Authentication().Logout(ctx); err != nil {
|
||||
// todo
|
||||
}
|
||||
}
|
||||
}
|
56
plugins/qbittorrent/pkg/qbittorrent/client_test.go
Normal file
56
plugins/qbittorrent/pkg/qbittorrent/client_test.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
c Client
|
||||
)
|
||||
|
||||
func init() {
|
||||
ctx := context.Background()
|
||||
var err error
|
||||
c, err = LoginClient(ctx, &Config{
|
||||
Address: "http://192.168.3.33:38080",
|
||||
Username: "admin",
|
||||
Password: "J0710cz5",
|
||||
RefreshIntervals: time.Hour,
|
||||
ConnectionTimeout: time.Second * 3,
|
||||
CustomHeaders: map[string]string{
|
||||
//"Origin": "http://192.168.3.33:8080",
|
||||
//"Referer": "http://192.168.3.33:8080",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormEncoder(t *testing.T) {
|
||||
var option = LogOption{
|
||||
Normal: true,
|
||||
Info: true,
|
||||
Warning: false,
|
||||
Critical: false,
|
||||
LastKnownId: 0,
|
||||
}
|
||||
var form = url.Values{}
|
||||
err := encoder.Encode(option, form)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(form)
|
||||
}
|
||||
|
||||
func TestFormEncode(t *testing.T) {
|
||||
var form = url.Values{}
|
||||
form.Add("username", "admin hahaha")
|
||||
form.Add("password", "J0710c?//&z5")
|
||||
fe := form.Encode()
|
||||
t.Log(fe)
|
||||
}
|
10
plugins/qbittorrent/pkg/qbittorrent/common.go
Normal file
10
plugins/qbittorrent/pkg/qbittorrent/common.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package qbittorrent
|
||||
|
||||
import "github.com/gorilla/schema"
|
||||
|
||||
const (
|
||||
ContentTypeJSON = "application/json"
|
||||
ContentTypeFormUrlEncoded = "application/x-www-form-urlencoded"
|
||||
)
|
||||
|
||||
var encoder = schema.NewEncoder()
|
25
plugins/qbittorrent/pkg/qbittorrent/config.go
Normal file
25
plugins/qbittorrent/pkg/qbittorrent/config.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package qbittorrent
|
||||
|
||||
import "time"
|
||||
|
||||
type Config struct {
|
||||
// Address qBittorrent endpoint
|
||||
Address string
|
||||
// Username used to access the WebUI
|
||||
Username string
|
||||
// Password used to access the WebUI
|
||||
Password string
|
||||
|
||||
// HTTP configuration
|
||||
|
||||
// CustomHeaders custom headers
|
||||
CustomHeaders map[string]string
|
||||
// ConnectionTimeout request timeout, default 3 seconds
|
||||
ConnectionTimeout time.Duration
|
||||
// ConnectionMaxIdles http client pool, default 128
|
||||
ConnectionMaxIdles int
|
||||
// RefreshCookie whether to automatically refresh cookies
|
||||
RefreshCookie bool
|
||||
// SessionTimeout interval for refreshing cookies, default 1 hour
|
||||
RefreshIntervals time.Duration
|
||||
}
|
8
plugins/qbittorrent/pkg/qbittorrent/error_code.go
Normal file
8
plugins/qbittorrent/pkg/qbittorrent/error_code.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package qbittorrent
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrNotLogin = errors.New("not login")
|
||||
ErrAuthFailed = errors.New("auth failed")
|
||||
)
|
95
plugins/qbittorrent/pkg/qbittorrent/log.go
Normal file
95
plugins/qbittorrent/pkg/qbittorrent/log.go
Normal file
|
@ -0,0 +1,95 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LogOption struct {
|
||||
Normal bool `schema:"normal,omitempty"` // include normal messages
|
||||
Info bool `schema:"info,omitempty"` // include info messages
|
||||
Warning bool `schema:"warning,omitempty"` // include warning messages
|
||||
Critical bool `schema:"critical,omitempty"` // include critical messages
|
||||
LastKnownId int64 `schema:"last_known_id,omitempty"` // exclude messages with "message id" <= (default: last_known_id-1)
|
||||
}
|
||||
|
||||
type LogEntry struct {
|
||||
Id int `json:"id,omitempty"` // id of the message or peer
|
||||
Timestamp int `json:"timestamp,omitempty"` // seconds since epoch
|
||||
Type int `json:"type,omitempty"` // type of the message, Log::NORMAL: 1, Log::INFO: 2, Log::WARNING: 4, Log::CRITICAL: 8
|
||||
Message string `json:"message,omitempty"` // text of the message
|
||||
IP string `json:"ip"` // ip of the peer
|
||||
Blocked bool `json:"blocked,omitempty"` // whether the peer was blocked
|
||||
Reason string `json:"reason,omitempty"` // Reason of the block
|
||||
}
|
||||
|
||||
type Log interface {
|
||||
// GetLog get log
|
||||
GetLog(ctx context.Context, option *LogOption) ([]*LogEntry, error)
|
||||
// GetPeerLog get peer log
|
||||
GetPeerLog(ctx context.Context, lastKnownId int) ([]*LogEntry, error)
|
||||
}
|
||||
|
||||
func (c *client) GetLog(ctx context.Context, option *LogOption) ([]*LogEntry, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Log.GetLog")
|
||||
defer span.End()
|
||||
|
||||
var form = url.Values{}
|
||||
err := encoder.Encode(option, form)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/log/main?%s", c.config.Address, form.Encode())
|
||||
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
body: strings.NewReader(form.Encode()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return nil, errors.New("get log failed: " + string(result.body))
|
||||
}
|
||||
|
||||
var logs []*LogEntry
|
||||
if err := json.Unmarshal(result.body, &logs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
func (c *client) GetPeerLog(ctx context.Context, lastKnownId int) ([]*LogEntry, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Log.GetPeerLog")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/log/peers", c.config.Address)
|
||||
var form = url.Values{}
|
||||
form.Add("last_known_id", strconv.Itoa(lastKnownId))
|
||||
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
body: strings.NewReader(form.Encode()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return nil, errors.New("get peer log failed: " + string(result.body))
|
||||
}
|
||||
|
||||
var logs []*LogEntry
|
||||
if err := json.Unmarshal(result.body, &logs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return logs, nil
|
||||
}
|
39
plugins/qbittorrent/pkg/qbittorrent/log_test.go
Normal file
39
plugins/qbittorrent/pkg/qbittorrent/log_test.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClient_GetLog(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
entries, err := c.Log().GetLog(ctx, &LogOption{
|
||||
Normal: true,
|
||||
Info: true,
|
||||
Warning: true,
|
||||
Critical: true,
|
||||
LastKnownId: 0,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes, err := json.Marshal(entries)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(bytes))
|
||||
}
|
||||
|
||||
func TestClient_GetPeerLog(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
entries, err := c.Log().GetPeerLog(ctx, -1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes, err := json.Marshal(entries)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(bytes))
|
||||
}
|
359
plugins/qbittorrent/pkg/qbittorrent/rss.go
Normal file
359
plugins/qbittorrent/pkg/qbittorrent/rss.go
Normal file
|
@ -0,0 +1,359 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RSS interface {
|
||||
// AddFolder create new folder for rss, full path of added folder such as "The Pirate Bay\Top100"
|
||||
AddFolder(ctx context.Context, path string) error
|
||||
// AddFeed add feed
|
||||
AddFeed(ctx context.Context, option *RssAddFeedOption) error
|
||||
// RemoveItem remove folder or feed
|
||||
RemoveItem(ctx context.Context, path string) error
|
||||
// MoveItem move or rename folder or feed
|
||||
MoveItem(ctx context.Context, srcPath, destPath string) error
|
||||
// GetItems list all items, if withData is true, will return all data
|
||||
GetItems(ctx context.Context, withData bool) (map[string]interface{}, error)
|
||||
// MarkAsRead if articleId is provided only the article is marked as read otherwise the whole feed
|
||||
// is going to be marked as read.
|
||||
MarkAsRead(ctx context.Context, option *RssMarkAsReadOption) error
|
||||
// RefreshItem refresh folder or feed
|
||||
RefreshItem(ctx context.Context, itemPath string) error
|
||||
// SetAutoDownloadingRule set auto-downloading rule
|
||||
SetAutoDownloadingRule(ctx context.Context, ruleName string, ruleDef *RssAutoDownloadingRuleDef) error
|
||||
// RenameAutoDownloadingRule rename auto-downloading rule
|
||||
RenameAutoDownloadingRule(ctx context.Context, ruleName, newRuleName string) error
|
||||
// RemoveAutoDownloadingRule remove auto-downloading rule
|
||||
RemoveAutoDownloadingRule(ctx context.Context, ruleName string) error
|
||||
// GetAllAutoDownloadingRules get all auto-downloading rules
|
||||
GetAllAutoDownloadingRules(ctx context.Context) (map[string]*RssAutoDownloadingRuleDef, error)
|
||||
// GetAllArticlesMatchingRule get all articles matching a rule
|
||||
GetAllArticlesMatchingRule(ctx context.Context, ruleName string) (map[string][]string, error)
|
||||
}
|
||||
|
||||
type RssAddFeedOption struct {
|
||||
// URL feed of rss such as http://thepiratebay.org/rss//top100/200
|
||||
URL string `schema:"url"`
|
||||
// Folder full path of added folder, optional
|
||||
Folder string `schema:"path,omitempty"`
|
||||
}
|
||||
|
||||
type RssMarkAsReadOption struct {
|
||||
// ItemPath current full path of item
|
||||
ItemPath string `schema:"itemPath"`
|
||||
// ArticleId id of article, optional
|
||||
ArticleId string `schema:"articleId,omitempty"`
|
||||
}
|
||||
|
||||
type RssAutoDownloadingRuleDefTorrentParams struct {
|
||||
Category string `json:"category,omitempty"`
|
||||
DownloadLimit int `json:"download_limit,omitempty"`
|
||||
DownloadPath int `json:"download_path,omitempty"`
|
||||
InactiveSeedingTimeLimit int `json:"inactive_seeding_time_limit,omitempty"`
|
||||
OperatingMode string `json:"operating_mode,omitempty"`
|
||||
RatioLimit int `json:"ratio_limit,omitempty"`
|
||||
SavePath string `json:"save_path,omitempty"`
|
||||
SeedingTimeLimit int `json:"seeding_time_limit,omitempty"`
|
||||
SkipChecking bool `json:"skip_checking,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
UploadLimit int `json:"upload_limit,omitempty"`
|
||||
Stopped bool `json:"stopped,omitempty"`
|
||||
UseAutoTMM bool `json:"use_auto_tmm,omitempty"`
|
||||
}
|
||||
|
||||
type RssAutoDownloadingRuleDef struct {
|
||||
AddPaused bool `json:"addPaused,omitempty"`
|
||||
AffectedFeeds []string `json:"affectedFeeds,omitempty"`
|
||||
AssignedCategory string `json:"assignedCategory,omitempty"`
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
EpisodeFilter string `json:"episodeFilter,omitempty"`
|
||||
IgnoreDays int `json:"ignoreDays,omitempty"`
|
||||
LastMatch string `json:"lastMatch,omitempty"`
|
||||
MustContain string `json:"mustContain,omitempty"`
|
||||
MustNotContain string `json:"mustNotContain,omitempty"`
|
||||
PreviouslyMatchedEpisodes []string `json:"previouslyMatchedEpisodes,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
SavePath string `json:"savePath,omitempty"`
|
||||
SmartFilter bool `json:"smartFilter,omitempty"`
|
||||
TorrentParams *RssAutoDownloadingRuleDefTorrentParams `json:"torrentParams,omitempty"`
|
||||
UseRegex bool `json:"useRegex,omitempty"`
|
||||
}
|
||||
|
||||
func (c *client) AddFolder(ctx context.Context, path string) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.RSS.AddFolder")
|
||||
defer span.End()
|
||||
|
||||
var formData = url.Values{}
|
||||
formData.Add("path", path)
|
||||
var apiUrl = fmt.Sprintf("%s/api/v2/rss/addFolder", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
method: http.MethodPost,
|
||||
body: strings.NewReader(formData.Encode()),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("add rss folder failed: " + string(result.body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) AddFeed(ctx context.Context, opt *RssAddFeedOption) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.RSS.AddFeed")
|
||||
defer span.End()
|
||||
|
||||
var formData = url.Values{}
|
||||
err := encoder.Encode(opt, formData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var apiUrl = fmt.Sprintf("%s/api/v2/rss/addFolder", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
method: http.MethodPost,
|
||||
body: strings.NewReader(formData.Encode()),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("add rss feed failed: " + string(result.body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) RemoveItem(ctx context.Context, path string) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.RSS.RemoveItem")
|
||||
defer span.End()
|
||||
|
||||
var formData = url.Values{}
|
||||
formData.Add("path", path)
|
||||
var apiUrl = fmt.Sprintf("%s/api/v2/rss/removeItem", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
method: http.MethodPost,
|
||||
body: strings.NewReader(formData.Encode()),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("remove rss item failed: " + string(result.body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) MoveItem(ctx context.Context, srcPath, destPath string) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.RSS.MoveItem")
|
||||
defer span.End()
|
||||
|
||||
var formData = url.Values{}
|
||||
formData.Add("itemPath", srcPath)
|
||||
formData.Add("destPath", destPath)
|
||||
var apiUrl = fmt.Sprintf("%s/api/v2/rss/moveItem", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
method: http.MethodPost,
|
||||
body: strings.NewReader(formData.Encode()),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("move rss item failed: " + string(result.body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) GetItems(ctx context.Context, withData bool) (map[string]interface{}, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.RSS.GetItems")
|
||||
defer span.End()
|
||||
|
||||
var apiUrl = fmt.Sprintf("%s/api/v2/rss/items?withData=%t", c.config.Address, withData)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
method: http.MethodGet,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return nil, errors.New("get rss items failed: " + string(result.body))
|
||||
}
|
||||
var data = make(map[string]interface{})
|
||||
err = json.Unmarshal(result.body, &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (c *client) MarkAsRead(ctx context.Context, opt *RssMarkAsReadOption) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.RSS.MarkAsRead")
|
||||
defer span.End()
|
||||
|
||||
var formData = url.Values{}
|
||||
err := encoder.Encode(opt, formData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var apiUrl = fmt.Sprintf("%s/api/v2/rss/markAsRead", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
method: http.MethodPost,
|
||||
body: strings.NewReader(formData.Encode()),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("mark as read rss item failed: " + string(result.body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) RefreshItem(ctx context.Context, itemPath string) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.RSS.RefreshItem")
|
||||
defer span.End()
|
||||
|
||||
var formData = url.Values{}
|
||||
formData.Add("itemPath", itemPath)
|
||||
var apiUrl = fmt.Sprintf("%s/api/v2/rss/refreshItem", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
method: http.MethodPost,
|
||||
body: strings.NewReader(formData.Encode()),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("refresh rss item failed: " + string(result.body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) SetAutoDownloadingRule(ctx context.Context, ruleName string, ruleDef *RssAutoDownloadingRuleDef) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.RSS.SetAutoDownloadingRule")
|
||||
defer span.End()
|
||||
|
||||
var formData = url.Values{}
|
||||
formData.Add("ruleName", ruleName)
|
||||
ruleDefBytes, err := json.Marshal(ruleDef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
formData.Add("ruleDef", string(ruleDefBytes))
|
||||
var apiUrl = fmt.Sprintf("%s/api/v2/rss/setRule", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
method: http.MethodPost,
|
||||
body: strings.NewReader(formData.Encode()),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("set auto downloading rule failed: " + string(result.body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) RenameAutoDownloadingRule(ctx context.Context, ruleName, newRuleName string) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.RSS.RenameAutoDownloadingRule")
|
||||
defer span.End()
|
||||
|
||||
var formData = url.Values{}
|
||||
formData.Add("ruleName", ruleName)
|
||||
formData.Add("newRuleName", newRuleName)
|
||||
var apiUrl = fmt.Sprintf("%s/api/v2/rss/renameRule", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
method: http.MethodPost,
|
||||
body: strings.NewReader(formData.Encode()),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("rename auto downloading rule failed: " + string(result.body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) RemoveAutoDownloadingRule(ctx context.Context, ruleName string) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.RSS.RemoveAutoDownloadingRule")
|
||||
defer span.End()
|
||||
|
||||
var formData = url.Values{}
|
||||
formData.Add("ruleName", ruleName)
|
||||
var apiUrl = fmt.Sprintf("%s/api/v2/rss/removeRule", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
method: http.MethodPost,
|
||||
body: strings.NewReader(formData.Encode()),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("remove auto downloading rule failed: " + string(result.body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) GetAllAutoDownloadingRules(ctx context.Context) (map[string]*RssAutoDownloadingRuleDef, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.RSS.GetAllAutoDownloadingRules")
|
||||
defer span.End()
|
||||
|
||||
var apiUrl = fmt.Sprintf("%s/api/v2/rss/matchingArticles", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result.code != 200 {
|
||||
return nil, errors.New("get rss rules failed: " + string(result.body))
|
||||
}
|
||||
var data = make(map[string]*RssAutoDownloadingRuleDef)
|
||||
err = json.Unmarshal(result.body, &data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (c *client) GetAllArticlesMatchingRule(ctx context.Context, ruleName string) (map[string][]string, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.RSS.GetAllArticlesMatchingRule")
|
||||
defer span.End()
|
||||
|
||||
var formData = url.Values{}
|
||||
formData.Add("ruleName", ruleName)
|
||||
var apiUrl = fmt.Sprintf("%s/api/v2/rss/matchingArticles?%s", c.config.Address, formData.Encode())
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result.code != 200 {
|
||||
return nil, errors.New("get rss rule match articles failed: " + string(result.body))
|
||||
}
|
||||
var data = make(map[string][]string)
|
||||
err = json.Unmarshal(result.body, &data)
|
||||
return data, err
|
||||
}
|
64
plugins/qbittorrent/pkg/qbittorrent/search.go
Normal file
64
plugins/qbittorrent/pkg/qbittorrent/search.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package qbittorrent
|
||||
|
||||
type Search interface {
|
||||
Start()
|
||||
Stop()
|
||||
Status()
|
||||
Results()
|
||||
Delete()
|
||||
Plugins()
|
||||
InstallPlugins()
|
||||
UninstallPlugins()
|
||||
EnableSearchPlugins()
|
||||
UpdateSearchPlugins()
|
||||
}
|
||||
|
||||
func (c *client) Start() {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *client) Stop() {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *client) Status() {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *client) Results() {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *client) Delete() {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *client) Plugins() {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *client) InstallPlugins() {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *client) UninstallPlugins() {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *client) EnableSearchPlugins() {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *client) UpdateSearchPlugins() {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
127
plugins/qbittorrent/pkg/qbittorrent/sync.go
Normal file
127
plugins/qbittorrent/pkg/qbittorrent/sync.go
Normal file
|
@ -0,0 +1,127 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Sync interface {
|
||||
// MainData get sync main data, rid is Response ID. if not provided, will be assumed.
|
||||
// if the given is different from the one of last server reply, will be (see the server reply details for more info)
|
||||
MainData(ctx context.Context, rid int) (*SyncMainData, error)
|
||||
// TorrentPeersData get sync torrent peer data, hash is torrent hash, rid is response id
|
||||
TorrentPeersData(ctx context.Context, hash string, rid int) (*SyncTorrentPeers, error)
|
||||
}
|
||||
|
||||
type SyncMainData struct {
|
||||
Rid int `json:"rid,omitempty"`
|
||||
FullUpdate bool `json:"full_update,omitempty"`
|
||||
ServerState ServerState `json:"server_state,omitempty"`
|
||||
Torrents map[string]SyncTorrentInfo `json:"torrents,omitempty"`
|
||||
}
|
||||
|
||||
type ServerState struct {
|
||||
AllTimeDl int64 `json:"alltime_dl,omitempty"`
|
||||
AllTimeUl int64 `json:"alltime_ul,omitempty"`
|
||||
AverageTimeQueue int `json:"average_time_queue,omitempty"`
|
||||
DlInfoData int64 `json:"dl_info_data,omitempty"`
|
||||
DlInfoSpeed int `json:"dl_info_speed,omitempty"`
|
||||
QueuedIoJobs int `json:"queued_io_jobs,omitempty"`
|
||||
TotalBuffersSize int `json:"total_buffers_size,omitempty"`
|
||||
UpInfoData int64 `json:"up_info_data,omitempty"`
|
||||
UpInfoSpeed int `json:"up_info_speed,omitempty"`
|
||||
WriteCacheOverload string `json:"write_cache_overload,omitempty"`
|
||||
}
|
||||
|
||||
type SyncTorrentInfo struct {
|
||||
AmountLeft int64 `json:"amount_left,omitempty"`
|
||||
Completed int `json:"completed,omitempty"`
|
||||
DlSpeed int `json:"dlspeed,omitempty"`
|
||||
Downloaded int `json:"downloaded,omitempty"`
|
||||
DownloadedSession int `json:"downloaded_session,omitempty"`
|
||||
Eta int `json:"eta,omitempty"`
|
||||
Progress float64 `json:"progress,omitempty"`
|
||||
SeenComplete int `json:"seen_complete,omitempty"`
|
||||
TimeActive int `json:"time_active,omitempty"`
|
||||
}
|
||||
|
||||
type SyncTorrentPeers struct {
|
||||
Rid int `json:"rid,omitempty"`
|
||||
FullUpdate bool `json:"full_update,omitempty"`
|
||||
ShowFlags bool `json:"show_flags,omitempty"`
|
||||
Peers map[string]SyncTorrentPeer `json:"peers,omitempty"`
|
||||
}
|
||||
|
||||
type SyncTorrentPeer struct {
|
||||
Client string `json:"client,omitempty"`
|
||||
Connection string `json:"connection,omitempty"`
|
||||
Country string `json:"country,omitempty"`
|
||||
CountryCode string `json:"country_code,omitempty"`
|
||||
DlSpeed int `json:"dl_speed,omitempty"`
|
||||
Downloaded int `json:"downloaded,omitempty"`
|
||||
Files string `json:"files,omitempty"`
|
||||
Flags string `json:"flags,omitempty"`
|
||||
FlagsDesc string `json:"flags_desc,omitempty"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
PeerIDClient string `json:"peer_id_client,omitempty"`
|
||||
Port int `json:"port,omitempty"`
|
||||
Progress float64 `json:"progress,omitempty"`
|
||||
Relevance float64 `json:"relevance,omitempty"`
|
||||
UpSpeed int `json:"up_speed,omitempty"`
|
||||
Uploaded int `json:"uploaded,omitempty"`
|
||||
}
|
||||
|
||||
func (c *client) MainData(ctx context.Context, rid int) (*SyncMainData, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Sync.MainData")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/sync/maindata?rid=%d", c.config.Address, rid)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return nil, errors.New("get main data failed: " + string(result.body))
|
||||
}
|
||||
|
||||
var mainData = new(SyncMainData)
|
||||
if err := json.Unmarshal(result.body, mainData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mainData, nil
|
||||
}
|
||||
|
||||
func (c *client) TorrentPeersData(ctx context.Context, hash string, rid int) (*SyncTorrentPeers, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Sync.TorrentPeersData")
|
||||
defer span.End()
|
||||
|
||||
var formData = url.Values{}
|
||||
formData.Add("hash", hash)
|
||||
formData.Add("rid", strconv.Itoa(rid))
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/sync/torrentPeers?%s", c.config.Address, formData.Encode())
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return nil, errors.New("get torrent peers data failed: " + string(result.body))
|
||||
}
|
||||
|
||||
var mainData = new(SyncTorrentPeers)
|
||||
if err := json.Unmarshal(result.body, mainData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mainData, nil
|
||||
}
|
37
plugins/qbittorrent/pkg/qbittorrent/sync_test.go
Normal file
37
plugins/qbittorrent/pkg/qbittorrent/sync_test.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestClient_MainData(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
syncMainData, err := c.Sync().MainData(ctx, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("sync main data: %+v", syncMainData)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
syncMainData, err = c.Sync().MainData(ctx, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("sync main data: %+v", syncMainData)
|
||||
}
|
||||
|
||||
func TestClient_TorrentPeersData(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
peersData, err := c.Sync().TorrentPeersData(ctx, "f23daefbe8d24d3dd882b44cb0b4f762bc23b4fc", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes, err := json.Marshal(peersData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(bytes))
|
||||
}
|
1530
plugins/qbittorrent/pkg/qbittorrent/torrent.go
Normal file
1530
plugins/qbittorrent/pkg/qbittorrent/torrent.go
Normal file
File diff suppressed because it is too large
Load diff
299
plugins/qbittorrent/pkg/qbittorrent/torrent_test.go
Normal file
299
plugins/qbittorrent/pkg/qbittorrent/torrent_test.go
Normal file
|
@ -0,0 +1,299 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClient_GetTorrents(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
torrents, err := c.Torrent().GetTorrents(ctx, &TorrentOption{
|
||||
Filter: "",
|
||||
Category: "movies",
|
||||
Tag: "hdtime",
|
||||
Sort: "",
|
||||
Reverse: false,
|
||||
Limit: 0,
|
||||
Offset: 0,
|
||||
Hashes: nil,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes, err := json.Marshal(torrents)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(bytes))
|
||||
}
|
||||
|
||||
func TestClient_GetProperties(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
properties, err := c.Torrent().GetProperties(ctx, "f23daefbe8d24d3dd882b44cb0b4f762bc23b4fc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes, err := json.Marshal(properties)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(bytes))
|
||||
}
|
||||
|
||||
func TestClient_GetTrackers(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
trackers, err := c.Torrent().GetTrackers(ctx, "f23daefbe8d24d3dd882b44cb0b4f762bc23b4fc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes, err := json.Marshal(trackers)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(bytes))
|
||||
}
|
||||
|
||||
func TestClient_GetWebSeeds(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
webSeeds, err := c.Torrent().GetWebSeeds(ctx, "f23daefbe8d24d3dd882b44cb0b4f762bc23b4fc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes, err := json.Marshal(webSeeds)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(bytes))
|
||||
}
|
||||
|
||||
func TestClient_GetContents(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
contents, err := c.Torrent().GetContents(ctx, "f23daefbe8d24d3dd882b44cb0b4f762bc23b4fc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes, err := json.Marshal(contents)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(bytes))
|
||||
}
|
||||
|
||||
func TestClient_GetPiecesStates(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
states, err := c.Torrent().GetPiecesStates(ctx, "f23daefbe8d24d3dd882b44cb0b4f762bc23b4fc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(states)
|
||||
}
|
||||
|
||||
func TestClient_GetPiecesHashes(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
hashes, err := c.Torrent().GetPiecesHashes(ctx, "f23daefbe8d24d3dd882b44cb0b4f762bc23b4fc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(hashes)
|
||||
}
|
||||
|
||||
func TestClient_PauseTorrents(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := c.Torrent().PauseTorrents(ctx, []string{"202382999be6a4fab395cd9c2c9d294177587904"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent paused")
|
||||
}
|
||||
|
||||
func TestClient_ResumeTorrents(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := c.Torrent().ResumeTorrents(ctx, []string{"fd3b4bf1937c59a8fd1a240cddc07172e0b979a2"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent resumed")
|
||||
}
|
||||
|
||||
func TestClient_DeleteTorrents(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := c.Torrent().DeleteTorrents(ctx, []string{"202382999be6a4fab395cd9c2c9d294177587904"}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent deleted")
|
||||
}
|
||||
|
||||
func TestClient_RecheckTorrents(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := c.Torrent().RecheckTorrents(ctx, []string{"fd3b4bf1937c59a8fd1a240cddc07172e0b979a2"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent rechecked")
|
||||
}
|
||||
|
||||
func TestClient_ReAnnounceTorrents(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := c.Torrent().ReAnnounceTorrents(ctx, []string{"fd3b4bf1937c59a8fd1a240cddc07172e0b979a2"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent reannounceed")
|
||||
}
|
||||
|
||||
func TestClient_AddNewTorrent(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fileContent, err := os.ReadFile("C:\\Users\\xuthu\\Downloads\\bbbbb.torrent")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = c.Torrent().AddNewTorrent(ctx, &TorrentAddOption{
|
||||
Torrents: []*TorrentAddFileMetadata{
|
||||
{
|
||||
//Filename: "ttttt.torrent",
|
||||
Data: fileContent,
|
||||
},
|
||||
},
|
||||
Category: "movies",
|
||||
Tags: []string{"d", "e", "f"},
|
||||
SkipChecking: false,
|
||||
Paused: false,
|
||||
RootFolder: false,
|
||||
Rename: "",
|
||||
UpLimit: 0,
|
||||
DlLimit: 0,
|
||||
RatioLimit: 0,
|
||||
SeedingTimeLimit: 0,
|
||||
AutoTMM: false,
|
||||
SequentialDownload: "",
|
||||
FirstLastPiecePrio: "",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent added")
|
||||
}
|
||||
|
||||
func TestClient_AddTrackers(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := c.Torrent().AddTrackers(ctx, "ca4523a3db9c6c3a13d7d7f3a545f97b75083032", []string{"https://hddtime.org/announce"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent trackers added")
|
||||
}
|
||||
|
||||
func TestClient_EditTrackers(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := c.Torrent().EditTrackers(ctx, "ca4523a3db9c6c3a13d7d7f3a545f97b75083032", "https://hddtime.org/announce", "https://hdctime.org/announce")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent trackers edited")
|
||||
}
|
||||
|
||||
func TestClient_RemoveTrackers(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := c.Torrent().RemoveTrackers(ctx, "ca4523a3db9c6c3a13d7d7f3a545f97b75083032", []string{"https://hdctime.org/announce"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent trackers removed")
|
||||
}
|
||||
|
||||
func TestClient_AddPeers(t *testing.T) {
|
||||
// todo no test
|
||||
//c.Torrent().AddPeers([]string{"ca4523a3db9c6c3a13d7d7f3a545f97b75083032"}, []string{"10.0.0.1:38080"})
|
||||
}
|
||||
|
||||
func TestClient_IncreasePriority(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := c.Torrent().IncreasePriority(ctx, []string{"916a250d32822adca39eb2b53efadfda1a15f902"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent priority increased")
|
||||
}
|
||||
|
||||
func TestClient_DecreasePriority(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := c.Torrent().DecreasePriority(ctx, []string{"916a250d32822adca39eb2b53efadfda1a15f902"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent priority decreased")
|
||||
}
|
||||
|
||||
func TestClient_MaxPriority(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := c.Torrent().MaxPriority(ctx, []string{"916a250d32822adca39eb2b53efadfda1a15f902"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent priority maxed")
|
||||
}
|
||||
|
||||
func TestClient_MinPriority(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := c.Torrent().MinPriority(ctx, []string{"916a250d32822adca39eb2b53efadfda1a15f902"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent priority mined")
|
||||
}
|
||||
|
||||
func TestClient_SetFilePriority(t *testing.T) {
|
||||
// todo no test
|
||||
}
|
||||
|
||||
func TestClient_GetDownloadLimit(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
downloadLimit, err := c.Torrent().GetDownloadLimit(ctx, []string{"916a250d32822adca39eb2b53efadfda1a15f902"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent download limit", downloadLimit)
|
||||
}
|
||||
|
||||
func TestClient_SetDownloadLimit(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := c.Torrent().SetDownloadLimit(ctx, []string{"916a250d32822adca39eb2b53efadfda1a15f902"}, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent download limit setted")
|
||||
}
|
||||
|
||||
func TestClient_SetShareLimit(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := c.Torrent().SetShareLimit(ctx, []string{"916a250d32822adca39eb2b53efadfda1a15f902"}, -2, -2, -2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent share limit setted")
|
||||
}
|
||||
|
||||
func TestClient_GetUploadLimit(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
limit, err := c.Torrent().GetUploadLimit(ctx, []string{"916a250d32822adca39eb2b53efadfda1a15f902"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent upload limit", limit)
|
||||
}
|
||||
|
||||
func TestClient_SetUploadLimit(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := c.Torrent().SetUploadLimit(ctx, []string{"916a250d32822adca39eb2b53efadfda1a15f902"}, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("torrent upload limit setted")
|
||||
}
|
||||
|
||||
func TestClient_SetLocation(t *testing.T) {
|
||||
// todo test
|
||||
}
|
209
plugins/qbittorrent/pkg/qbittorrent/transfer.go
Normal file
209
plugins/qbittorrent/pkg/qbittorrent/transfer.go
Normal file
|
@ -0,0 +1,209 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TransferStatusBar struct {
|
||||
ConnectionStatus string `json:"connection_status,omitempty"`
|
||||
DhtNodes int `json:"dht_nodes,omitempty"`
|
||||
DlInfoData int64 `json:"dl_info_data,omitempty"`
|
||||
DlInfoSpeed int `json:"dl_info_speed,omitempty"`
|
||||
DlRateLimit int `json:"dl_rate_limit,omitempty"`
|
||||
UpInfoData int `json:"up_info_data,omitempty"`
|
||||
UpInfoSpeed int `json:"up_info_speed,omitempty"`
|
||||
UpRateLimit int `json:"up_rate_limit,omitempty"`
|
||||
Queueing bool `json:"queueing,omitempty"`
|
||||
UseAltSpeedLimits bool `json:"use_alt_speed_limits,omitempty"`
|
||||
RefreshInterval int `json:"refresh_interval,omitempty"`
|
||||
}
|
||||
|
||||
type Transfer interface {
|
||||
// GlobalStatusBar usually see in qBittorrent status bar
|
||||
GlobalStatusBar(ctx context.Context) (*TransferStatusBar, error)
|
||||
// BanPeers the peer to ban, or multiple peers separated by a pipe.
|
||||
// each peer is host:port
|
||||
BanPeers(ctx context.Context, peers []string) error
|
||||
// GetSpeedLimitsMode get alternative speed limits state
|
||||
GetSpeedLimitsMode(ctx context.Context) (string, error)
|
||||
// ToggleSpeedLimitsMode toggle alternative speed limits
|
||||
ToggleSpeedLimitsMode(ctx context.Context) error
|
||||
// GetGlobalUploadLimit get global upload limit, the response is the value of current global download speed
|
||||
// limit in bytes/second; this value will be zero if no limit is applied.
|
||||
GetGlobalUploadLimit(ctx context.Context) (string, error)
|
||||
// SetGlobalUploadLimit set global upload limit, set in bytes/second
|
||||
SetGlobalUploadLimit(ctx context.Context, limit int) error
|
||||
// GetGlobalDownloadLimit get global download limit, the response is the value of current global download speed
|
||||
// limit in bytes/second; this value will be zero if no limit is applied.
|
||||
GetGlobalDownloadLimit(ctx context.Context) (string, error)
|
||||
// SetGlobalDownloadLimit set global download limit, set in bytes/second
|
||||
SetGlobalDownloadLimit(ctx context.Context, limit int) error
|
||||
}
|
||||
|
||||
func (c *client) GlobalStatusBar(ctx context.Context) (*TransferStatusBar, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Transfer.GlobalStatusBar")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/transfer/info", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return nil, errors.New("get global transfer status bar failed: " + string(result.body))
|
||||
}
|
||||
|
||||
var data = new(TransferStatusBar)
|
||||
if err := json.Unmarshal(result.body, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (c *client) BanPeers(ctx context.Context, peers []string) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Transfer.BanPeers")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/transfer/banPeers", c.config.Address)
|
||||
var form = url.Values{}
|
||||
form.Add("peers", strings.Join(peers, "|"))
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
method: http.MethodPost,
|
||||
body: strings.NewReader(form.Encode()),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("ban peers failed: " + string(result.body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) GetSpeedLimitsMode(ctx context.Context) (string, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Transfer.GetSpeedLimitsMode")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/transfer/speedLimitsMode", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return "", errors.New("ban peers failed: " + string(result.body))
|
||||
}
|
||||
|
||||
return string(result.body), nil
|
||||
}
|
||||
|
||||
func (c *client) ToggleSpeedLimitsMode(ctx context.Context) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Transfer.ToggleSpeedLimitsMode")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/transfer/toggleSpeedLimitsMode", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
method: http.MethodPost,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("ban peers failed: " + string(result.body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) GetGlobalUploadLimit(ctx context.Context) (string, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Transfer.GetGlobalUploadLimit")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/transfer/uploadLimit", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return "", errors.New("get global upload limit failed: " + string(result.body))
|
||||
}
|
||||
|
||||
return string(result.body), nil
|
||||
}
|
||||
|
||||
func (c *client) SetGlobalUploadLimit(ctx context.Context, limit int) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Transfer.SetGlobalUploadLimit")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/transfer/setUploadLimit?limit=%d", c.config.Address, limit)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("set global upload limit failed: " + string(result.body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) GetGlobalDownloadLimit(ctx context.Context) (string, error) {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Transfer.GetGlobalDownloadLimit")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/transfer/downloadLimit", c.config.Address)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return "", errors.New("get global download limit failed: " + string(result.body))
|
||||
}
|
||||
|
||||
return string(result.body), nil
|
||||
}
|
||||
|
||||
func (c *client) SetGlobalDownloadLimit(ctx context.Context, limit int) error {
|
||||
ctx, span := trace.Start(ctx, "qbittorrent.Transfer.SetGlobalDownloadLimit")
|
||||
defer span.End()
|
||||
|
||||
apiUrl := fmt.Sprintf("%s/api/v2/transfer/setDownloadLimit?limit=%d", c.config.Address, limit)
|
||||
result, err := c.doRequest(ctx, &requestData{
|
||||
url: apiUrl,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.code != 200 {
|
||||
return errors.New("set global download limit failed: " + string(result.body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue