tsarr 2.9.1 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +120 -233
  3. package/dist/cli/commands/doctor.d.ts.map +1 -1
  4. package/dist/cli/commands/manual-import.d.ts +30 -0
  5. package/dist/cli/commands/manual-import.d.ts.map +1 -0
  6. package/dist/cli/commands/radarr.d.ts.map +1 -1
  7. package/dist/cli/commands/sonarr.d.ts.map +1 -1
  8. package/dist/cli/index.js +5230 -4783
  9. package/dist/cli/index.js.map +133 -0
  10. package/dist/clients/base.d.ts +8 -3
  11. package/dist/clients/base.d.ts.map +1 -1
  12. package/dist/clients/bazarr.d.ts +125 -124
  13. package/dist/clients/bazarr.d.ts.map +1 -1
  14. package/dist/clients/bazarr.js +183 -100
  15. package/dist/clients/bazarr.js.map +22 -0
  16. package/dist/clients/lidarr.d.ts +148 -147
  17. package/dist/clients/lidarr.d.ts.map +1 -1
  18. package/dist/clients/lidarr.js +190 -102
  19. package/dist/clients/lidarr.js.map +23 -0
  20. package/dist/clients/prowlarr.d.ts +30 -29
  21. package/dist/clients/prowlarr.d.ts.map +1 -1
  22. package/dist/clients/prowlarr.js +190 -102
  23. package/dist/clients/prowlarr.js.map +23 -0
  24. package/dist/clients/qbittorrent.d.ts +3 -1
  25. package/dist/clients/qbittorrent.d.ts.map +1 -1
  26. package/dist/clients/qbittorrent.js +213 -107
  27. package/dist/clients/qbittorrent.js.map +21 -0
  28. package/dist/clients/radarr.d.ts +195 -150
  29. package/dist/clients/radarr.d.ts.map +1 -1
  30. package/dist/clients/radarr.js +230 -102
  31. package/dist/clients/radarr.js.map +23 -0
  32. package/dist/clients/readarr.d.ts +150 -149
  33. package/dist/clients/readarr.d.ts.map +1 -1
  34. package/dist/clients/readarr.js +190 -102
  35. package/dist/clients/readarr.js.map +23 -0
  36. package/dist/clients/seerr-types.d.ts +1 -1
  37. package/dist/clients/seerr-types.d.ts.map +1 -1
  38. package/dist/clients/seerr.d.ts +17 -16
  39. package/dist/clients/seerr.d.ts.map +1 -1
  40. package/dist/clients/seerr.js +183 -100
  41. package/dist/clients/seerr.js.map +22 -0
  42. package/dist/clients/sonarr.d.ts +203 -168
  43. package/dist/clients/sonarr.d.ts.map +1 -1
  44. package/dist/clients/sonarr.js +206 -112
  45. package/dist/clients/sonarr.js.map +23 -0
  46. package/dist/core/client.d.ts +2 -0
  47. package/dist/core/client.d.ts.map +1 -1
  48. package/dist/core/fetch.d.ts +23 -0
  49. package/dist/core/fetch.d.ts.map +1 -0
  50. package/dist/core/index.d.ts +1 -0
  51. package/dist/core/index.d.ts.map +1 -1
  52. package/dist/core/types.d.ts +7 -0
  53. package/dist/core/types.d.ts.map +1 -1
  54. package/dist/generated/bazarr/client/client.gen.d.ts.map +1 -1
  55. package/dist/generated/bazarr/client/types.gen.d.ts +5 -2
  56. package/dist/generated/bazarr/client/types.gen.d.ts.map +1 -1
  57. package/dist/generated/bazarr/client/utils.gen.d.ts +5 -1
  58. package/dist/generated/bazarr/client/utils.gen.d.ts.map +1 -1
  59. package/dist/generated/lidarr/client/client.gen.d.ts.map +1 -1
  60. package/dist/generated/lidarr/client/types.gen.d.ts +5 -2
  61. package/dist/generated/lidarr/client/types.gen.d.ts.map +1 -1
  62. package/dist/generated/lidarr/client/utils.gen.d.ts +5 -1
  63. package/dist/generated/lidarr/client/utils.gen.d.ts.map +1 -1
  64. package/dist/generated/prowlarr/client/client.gen.d.ts.map +1 -1
  65. package/dist/generated/prowlarr/client/types.gen.d.ts +5 -2
  66. package/dist/generated/prowlarr/client/types.gen.d.ts.map +1 -1
  67. package/dist/generated/prowlarr/client/utils.gen.d.ts +5 -1
  68. package/dist/generated/prowlarr/client/utils.gen.d.ts.map +1 -1
  69. package/dist/generated/qbittorrent/client/client.gen.d.ts.map +1 -1
  70. package/dist/generated/qbittorrent/client/types.gen.d.ts +5 -2
  71. package/dist/generated/qbittorrent/client/types.gen.d.ts.map +1 -1
  72. package/dist/generated/qbittorrent/client/utils.gen.d.ts +5 -1
  73. package/dist/generated/qbittorrent/client/utils.gen.d.ts.map +1 -1
  74. package/dist/generated/radarr/client/client.gen.d.ts.map +1 -1
  75. package/dist/generated/radarr/client/types.gen.d.ts +5 -2
  76. package/dist/generated/radarr/client/types.gen.d.ts.map +1 -1
  77. package/dist/generated/radarr/client/utils.gen.d.ts +5 -1
  78. package/dist/generated/radarr/client/utils.gen.d.ts.map +1 -1
  79. package/dist/generated/readarr/client/client.gen.d.ts.map +1 -1
  80. package/dist/generated/readarr/client/types.gen.d.ts +5 -2
  81. package/dist/generated/readarr/client/types.gen.d.ts.map +1 -1
  82. package/dist/generated/readarr/client/utils.gen.d.ts +5 -1
  83. package/dist/generated/readarr/client/utils.gen.d.ts.map +1 -1
  84. package/dist/generated/seerr/client/client.gen.d.ts.map +1 -1
  85. package/dist/generated/seerr/client/types.gen.d.ts +5 -2
  86. package/dist/generated/seerr/client/types.gen.d.ts.map +1 -1
  87. package/dist/generated/seerr/client/utils.gen.d.ts +5 -1
  88. package/dist/generated/seerr/client/utils.gen.d.ts.map +1 -1
  89. package/dist/generated/seerr/index.d.ts +2 -2
  90. package/dist/generated/seerr/index.d.ts.map +1 -1
  91. package/dist/generated/seerr/sdk.gen.d.ts +13 -1
  92. package/dist/generated/seerr/sdk.gen.d.ts.map +1 -1
  93. package/dist/generated/seerr/types.gen.d.ts +78 -7
  94. package/dist/generated/seerr/types.gen.d.ts.map +1 -1
  95. package/dist/generated/sonarr/client/client.gen.d.ts.map +1 -1
  96. package/dist/generated/sonarr/client/types.gen.d.ts +5 -2
  97. package/dist/generated/sonarr/client/types.gen.d.ts.map +1 -1
  98. package/dist/generated/sonarr/client/utils.gen.d.ts +5 -1
  99. package/dist/generated/sonarr/client/utils.gen.d.ts.map +1 -1
  100. package/dist/index.js +4 -1
  101. package/dist/index.js.map +12 -0
  102. package/dist/tsarr-2.11.0.tgz +0 -0
  103. package/package.json +18 -9
  104. package/dist/tsarr-2.9.1.tgz +0 -0
@@ -18,6 +18,93 @@ class ConnectionError extends TsarrError {
18
18
  }
19
19
  }
20
20
 
21
+ // src/core/fetch.ts
22
+ var DEFAULT_TIMEOUT = 30000;
23
+ var DEFAULT_MAX_RETRIES = 3;
24
+ var DEFAULT_INITIAL_DELAY = 1000;
25
+ var DEFAULT_MAX_DELAY = 1e4;
26
+ var RETRYABLE_STATUS_CODES = new Set([408, 429, 502, 503, 504]);
27
+ function isRetryable(error) {
28
+ if (error instanceof DOMException && error.name === "AbortError") {
29
+ return false;
30
+ }
31
+ if (error instanceof TypeError) {
32
+ return true;
33
+ }
34
+ return false;
35
+ }
36
+ function getRetryDelay(attempt, initialDelayMs, maxDelayMs) {
37
+ const delay = initialDelayMs * 2 ** attempt;
38
+ const jitter = delay * 0.2 * Math.random();
39
+ return Math.min(delay + jitter, maxDelayMs);
40
+ }
41
+ function createResilientFetch(options = {}) {
42
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
43
+ const maxRetries = options.retry ? options.retry.maxRetries ?? DEFAULT_MAX_RETRIES : 0;
44
+ const initialDelayMs = options.retry?.initialDelayMs ?? DEFAULT_INITIAL_DELAY;
45
+ const maxDelayMs = options.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY;
46
+ const resilientFetch = async (input, init) => {
47
+ let lastError;
48
+ const template = createRequestTemplate(input, init);
49
+ for (let attempt = 0;attempt <= maxRetries; attempt++) {
50
+ const controller = new AbortController;
51
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
52
+ const callerSignal = init?.signal;
53
+ if (callerSignal?.aborted) {
54
+ clearTimeout(timeoutId);
55
+ throw callerSignal.reason ?? new DOMException("The operation was aborted.", "AbortError");
56
+ }
57
+ const onCallerAbort = () => controller.abort(callerSignal.reason);
58
+ callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
59
+ try {
60
+ const response = await globalThis.fetch(new Request(template.clone(), { signal: controller.signal }));
61
+ clearTimeout(timeoutId);
62
+ callerSignal?.removeEventListener("abort", onCallerAbort);
63
+ if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < maxRetries) {
64
+ lastError = new ConnectionError(`Request failed with status ${response.status}`);
65
+ const delay = getRetryDelay(attempt, initialDelayMs, maxDelayMs);
66
+ await new Promise((resolve) => setTimeout(resolve, delay));
67
+ continue;
68
+ }
69
+ return response;
70
+ } catch (error) {
71
+ clearTimeout(timeoutId);
72
+ callerSignal?.removeEventListener("abort", onCallerAbort);
73
+ if (callerSignal?.aborted) {
74
+ throw callerSignal.reason ?? new DOMException("The operation was aborted.", "AbortError");
75
+ }
76
+ if (error instanceof DOMException && error.name === "AbortError") {
77
+ lastError = new ConnectionError(`Request timed out after ${timeout}ms`);
78
+ if (attempt < maxRetries) {
79
+ const delay = getRetryDelay(attempt, initialDelayMs, maxDelayMs);
80
+ await new Promise((resolve) => setTimeout(resolve, delay));
81
+ continue;
82
+ }
83
+ throw lastError;
84
+ }
85
+ if (isRetryable(error) && attempt < maxRetries) {
86
+ lastError = error;
87
+ const delay = getRetryDelay(attempt, initialDelayMs, maxDelayMs);
88
+ await new Promise((resolve) => setTimeout(resolve, delay));
89
+ continue;
90
+ }
91
+ throw error;
92
+ }
93
+ }
94
+ throw lastError;
95
+ };
96
+ return Object.assign(resilientFetch, {
97
+ preconnect: globalThis.fetch.preconnect?.bind(globalThis.fetch)
98
+ });
99
+ }
100
+ function createRequestTemplate(input, init) {
101
+ const { signal: _signal, ...requestInit } = init ?? {};
102
+ if (input instanceof Request) {
103
+ return init ? new Request(input.clone(), requestInit) : input.clone();
104
+ }
105
+ return new Request(input, requestInit);
106
+ }
107
+
21
108
  // src/generated/qbittorrent/core/bodySerializer.gen.ts
22
109
  var serializeUrlSearchParamsPair = (data, key, value) => {
23
110
  if (typeof value === "string") {
@@ -658,126 +745,115 @@ var createClient = (config = {}) => {
658
745
  return { opts: resolvedOpts, url };
659
746
  };
660
747
  const request = async (options) => {
661
- const { opts, url } = await beforeRequest(options);
662
- const requestInit = {
663
- redirect: "follow",
664
- ...opts,
665
- body: getValidRequestBody(opts)
666
- };
667
- let request2 = new Request(url, requestInit);
668
- for (const fn of interceptors.request.fns) {
669
- if (fn) {
670
- request2 = await fn(request2, opts);
671
- }
672
- }
673
- const _fetch = opts.fetch;
748
+ const throwOnError = options.throwOnError ?? _config.throwOnError;
749
+ const responseStyle = options.responseStyle ?? _config.responseStyle;
750
+ let request2;
674
751
  let response;
675
752
  try {
676
- response = await _fetch(request2);
677
- } catch (error2) {
678
- let finalError2 = error2;
679
- for (const fn of interceptors.error.fns) {
753
+ const { opts, url } = await beforeRequest(options);
754
+ const requestInit = {
755
+ redirect: "follow",
756
+ ...opts,
757
+ body: getValidRequestBody(opts)
758
+ };
759
+ request2 = new Request(url, requestInit);
760
+ for (const fn of interceptors.request.fns) {
680
761
  if (fn) {
681
- finalError2 = await fn(error2, undefined, request2, opts);
762
+ request2 = await fn(request2, opts);
682
763
  }
683
764
  }
684
- finalError2 = finalError2 || {};
685
- if (opts.throwOnError) {
686
- throw finalError2;
765
+ const _fetch = opts.fetch;
766
+ response = await _fetch(request2);
767
+ for (const fn of interceptors.response.fns) {
768
+ if (fn) {
769
+ response = await fn(response, request2, opts);
770
+ }
687
771
  }
688
- return opts.responseStyle === "data" ? undefined : {
689
- error: finalError2,
772
+ const result = {
690
773
  request: request2,
691
- response: undefined
774
+ response
692
775
  };
693
- }
694
- for (const fn of interceptors.response.fns) {
695
- if (fn) {
696
- response = await fn(response, request2, opts);
697
- }
698
- }
699
- const result = {
700
- request: request2,
701
- response
702
- };
703
- if (response.ok) {
704
- const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
705
- if (response.status === 204 || response.headers.get("Content-Length") === "0") {
706
- let emptyData;
776
+ if (response.ok) {
777
+ const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
778
+ if (response.status === 204 || response.headers.get("Content-Length") === "0") {
779
+ let emptyData;
780
+ switch (parseAs) {
781
+ case "arrayBuffer":
782
+ case "blob":
783
+ case "text":
784
+ emptyData = await response[parseAs]();
785
+ break;
786
+ case "formData":
787
+ emptyData = new FormData;
788
+ break;
789
+ case "stream":
790
+ emptyData = response.body;
791
+ break;
792
+ case "json":
793
+ default:
794
+ emptyData = {};
795
+ break;
796
+ }
797
+ return opts.responseStyle === "data" ? emptyData : {
798
+ data: emptyData,
799
+ ...result
800
+ };
801
+ }
802
+ let data;
707
803
  switch (parseAs) {
708
804
  case "arrayBuffer":
709
805
  case "blob":
806
+ case "formData":
710
807
  case "text":
711
- emptyData = await response[parseAs]();
808
+ data = await response[parseAs]();
712
809
  break;
713
- case "formData":
714
- emptyData = new FormData;
810
+ case "json": {
811
+ const text = await response.text();
812
+ data = text ? JSON.parse(text) : {};
715
813
  break;
814
+ }
716
815
  case "stream":
717
- emptyData = response.body;
718
- break;
719
- case "json":
720
- default:
721
- emptyData = {};
722
- break;
816
+ return opts.responseStyle === "data" ? response.body : {
817
+ data: response.body,
818
+ ...result
819
+ };
820
+ }
821
+ if (parseAs === "json") {
822
+ if (opts.responseValidator) {
823
+ await opts.responseValidator(data);
824
+ }
825
+ if (opts.responseTransformer) {
826
+ data = await opts.responseTransformer(data);
827
+ }
723
828
  }
724
- return opts.responseStyle === "data" ? emptyData : {
725
- data: emptyData,
829
+ return opts.responseStyle === "data" ? data : {
830
+ data,
726
831
  ...result
727
832
  };
728
833
  }
729
- let data;
730
- switch (parseAs) {
731
- case "arrayBuffer":
732
- case "blob":
733
- case "formData":
734
- case "text":
735
- data = await response[parseAs]();
736
- break;
737
- case "json": {
738
- const text = await response.text();
739
- data = text ? JSON.parse(text) : {};
740
- break;
834
+ const textError = await response.text();
835
+ let jsonError;
836
+ try {
837
+ jsonError = JSON.parse(textError);
838
+ } catch {}
839
+ throw jsonError ?? textError;
840
+ } catch (error) {
841
+ let finalError = error;
842
+ for (const fn of interceptors.error.fns) {
843
+ if (fn) {
844
+ finalError = await fn(finalError, response, request2, options);
741
845
  }
742
- case "stream":
743
- return opts.responseStyle === "data" ? response.body : {
744
- data: response.body,
745
- ...result
746
- };
747
846
  }
748
- if (parseAs === "json") {
749
- if (opts.responseValidator) {
750
- await opts.responseValidator(data);
751
- }
752
- if (opts.responseTransformer) {
753
- data = await opts.responseTransformer(data);
754
- }
847
+ finalError = finalError || {};
848
+ if (throwOnError) {
849
+ throw finalError;
755
850
  }
756
- return opts.responseStyle === "data" ? data : {
757
- data,
758
- ...result
851
+ return responseStyle === "data" ? undefined : {
852
+ error: finalError,
853
+ request: request2,
854
+ response
759
855
  };
760
856
  }
761
- const textError = await response.text();
762
- let jsonError;
763
- try {
764
- jsonError = JSON.parse(textError);
765
- } catch {}
766
- const error = jsonError ?? textError;
767
- let finalError = error;
768
- for (const fn of interceptors.error.fns) {
769
- if (fn) {
770
- finalError = await fn(error, response, request2, opts);
771
- }
772
- }
773
- finalError = finalError || {};
774
- if (opts.throwOnError) {
775
- throw finalError;
776
- }
777
- return opts.responseStyle === "data" ? undefined : {
778
- error: finalError,
779
- ...result
780
- };
781
857
  };
782
858
  const makeMethodFn = (method) => (options) => request({ ...options, method });
783
859
  const makeSseFn = (method) => async (options) => {
@@ -785,7 +861,6 @@ var createClient = (config = {}) => {
785
861
  return createSseClient({
786
862
  ...opts,
787
863
  body: opts.body,
788
- headers: opts.headers,
789
864
  method,
790
865
  onRequest: async (url2, init) => {
791
866
  let request2 = new Request(url2, init);
@@ -924,7 +999,8 @@ class QBittorrentClient {
924
999
  username;
925
1000
  password;
926
1001
  sid = null;
927
- timeoutMs;
1002
+ cookieName = "SID";
1003
+ fetch;
928
1004
  constructor(config) {
929
1005
  if (!config.baseUrl) {
930
1006
  throw new ConnectionError("No base URL provided");
@@ -932,13 +1008,40 @@ class QBittorrentClient {
932
1008
  this.baseUrl = config.baseUrl.replace(/\/$/, "");
933
1009
  this.username = config.username;
934
1010
  this.password = config.password;
935
- this.timeoutMs = config.timeout ?? DEFAULT_TIMEOUT_MS;
1011
+ const baseFetch = createResilientFetch({
1012
+ timeout: config.timeout ?? DEFAULT_TIMEOUT_MS,
1013
+ retry: config.retry
1014
+ });
1015
+ this.fetch = (input, init) => this.fetchWithCookieName(baseFetch, input, init);
936
1016
  client.setConfig({
937
1017
  baseUrl: `${this.baseUrl}/api/v2`,
938
1018
  auth: () => this.ensureAuth(),
939
- signal: AbortSignal.timeout(this.timeoutMs)
1019
+ fetch: this.fetch
940
1020
  });
941
1021
  }
1022
+ async fetchWithCookieName(baseFetch, input, init) {
1023
+ if (this.cookieName === "SID") {
1024
+ return baseFetch(input, init);
1025
+ }
1026
+ if (input instanceof Request) {
1027
+ const cookie = input.headers.get("cookie");
1028
+ if (cookie?.includes("SID=")) {
1029
+ const headers = new Headers(input.headers);
1030
+ headers.set("cookie", cookie.replace(/(^|;\s*)SID=/, `$1${this.cookieName}=`));
1031
+ return baseFetch(new Request(input, { headers }), init);
1032
+ }
1033
+ return baseFetch(input, init);
1034
+ }
1035
+ if (init?.headers) {
1036
+ const headers = new Headers(init.headers);
1037
+ const cookie = headers.get("cookie");
1038
+ if (cookie?.includes("SID=")) {
1039
+ headers.set("cookie", cookie.replace(/(^|;\s*)SID=/, `$1${this.cookieName}=`));
1040
+ return baseFetch(input, { ...init, headers });
1041
+ }
1042
+ }
1043
+ return baseFetch(input, init);
1044
+ }
942
1045
  async ensureAuth() {
943
1046
  if (!this.sid) {
944
1047
  await this.login();
@@ -946,7 +1049,7 @@ class QBittorrentClient {
946
1049
  return this.sid;
947
1050
  }
948
1051
  async login() {
949
- const response = await fetch(`${this.baseUrl}/api/v2/auth/login`, {
1052
+ const response = await this.fetch(`${this.baseUrl}/api/v2/auth/login`, {
950
1053
  method: "POST",
951
1054
  headers: {
952
1055
  "Content-Type": "application/x-www-form-urlencoded",
@@ -955,22 +1058,22 @@ class QBittorrentClient {
955
1058
  body: new URLSearchParams({
956
1059
  username: this.username,
957
1060
  password: this.password
958
- }),
959
- signal: AbortSignal.timeout(this.timeoutMs)
1061
+ })
960
1062
  });
961
1063
  if (!response.ok) {
962
1064
  throw new ConnectionError(`qBittorrent login failed (${response.status})`);
963
1065
  }
964
- const text = await response.text();
965
- if (text.trim() !== "Ok.") {
1066
+ const text = (await response.text()).trim();
1067
+ if (text && text !== "Ok.") {
966
1068
  throw new ConnectionError("qBittorrent authentication failed: invalid username or password");
967
1069
  }
968
1070
  const setCookie = response.headers.get("set-cookie");
969
- const sidMatch = setCookie?.match(/SID=([^;]+)/);
1071
+ const sidMatch = setCookie?.match(/(SID|QBT_SID_\d+)=([^;]+)/);
970
1072
  if (!sidMatch) {
971
1073
  throw new ConnectionError("qBittorrent login succeeded but no SID cookie received");
972
1074
  }
973
- this.sid = sidMatch[1];
1075
+ this.cookieName = sidMatch[1];
1076
+ this.sid = sidMatch[2];
974
1077
  }
975
1078
  async getAppVersion() {
976
1079
  const result = await appVersionGet();
@@ -1015,3 +1118,6 @@ class QBittorrentClient {
1015
1118
  export {
1016
1119
  QBittorrentClient
1017
1120
  };
1121
+
1122
+ //# debugId=EEFE0D3C2E02FFD464756E2164756E21
1123
+ //# sourceMappingURL=qbittorrent.js.map