mirror of
https://github.com/zoriya/flood.git
synced 2025-12-05 23:06:20 +00:00
dependencies: bump (major)
This commit is contained in:
@@ -20,7 +20,7 @@
|
|||||||
{
|
{
|
||||||
"files": ["*.js", "*.jsx"],
|
"files": ["*.js", "*.jsx"],
|
||||||
"extends": ["prettier"],
|
"extends": ["prettier"],
|
||||||
"parser": "babel-eslint",
|
"parser": "@babel/eslint-parser",
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/no-var-requires": 0
|
"@typescript-eslint/no-var-requires": 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"bracketSpacing": false,
|
"bracketSpacing": false,
|
||||||
"jsxBracketSameLine": true,
|
|
||||||
"printWidth": 120,
|
"printWidth": 120,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all"
|
"trailingComma": "all"
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import axios, {AxiosError, AxiosResponse} from 'axios';
|
import axios, {AxiosError} from 'axios';
|
||||||
|
|
||||||
import AuthStore from '@client/stores/AuthStore';
|
import AuthStore from '@client/stores/AuthStore';
|
||||||
import ConfigStore from '@client/stores/ConfigStore';
|
import ConfigStore from '@client/stores/ConfigStore';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AuthAuthenticationOptions,
|
AuthAuthenticationOptions,
|
||||||
|
AuthAuthenticationResponse,
|
||||||
AuthRegistrationOptions,
|
AuthRegistrationOptions,
|
||||||
|
AuthRegistrationResponse,
|
||||||
AuthUpdateUserOptions,
|
AuthUpdateUserOptions,
|
||||||
AuthVerificationResponse,
|
AuthVerificationResponse,
|
||||||
} from '@shared/schema/api/auth';
|
} from '@shared/schema/api/auth';
|
||||||
@@ -20,10 +22,9 @@ const {baseURI} = ConfigStore;
|
|||||||
const AuthActions = {
|
const AuthActions = {
|
||||||
authenticate: (options: AuthAuthenticationOptions) =>
|
authenticate: (options: AuthAuthenticationOptions) =>
|
||||||
axios
|
axios
|
||||||
.post(`${baseURI}api/auth/authenticate`, options)
|
.post<AuthAuthenticationResponse>(`${baseURI}api/auth/authenticate`, options)
|
||||||
.then((json) => json.data)
|
|
||||||
.then(
|
.then(
|
||||||
(data) => {
|
({data}) => {
|
||||||
AuthStore.handleLoginSuccess(data);
|
AuthStore.handleLoginSuccess(data);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
@@ -53,36 +54,27 @@ const AuthActions = {
|
|||||||
),
|
),
|
||||||
|
|
||||||
createUser: (options: AuthRegistrationOptions) =>
|
createUser: (options: AuthRegistrationOptions) =>
|
||||||
axios
|
axios.post<AuthRegistrationResponse>(`${baseURI}api/auth/register?cookie=false`, options).then(({data}) => {
|
||||||
.post(`${baseURI}api/auth/register?cookie=false`, options)
|
AuthStore.handleCreateUserSuccess(data);
|
||||||
.then((json) => json.data)
|
}),
|
||||||
.then((data) => {
|
|
||||||
AuthStore.handleCreateUserSuccess(data);
|
|
||||||
}),
|
|
||||||
|
|
||||||
updateUser: (username: Credentials['username'], options: AuthUpdateUserOptions) =>
|
updateUser: (username: Credentials['username'], options: AuthUpdateUserOptions) =>
|
||||||
axios.patch(`${baseURI}api/auth/users/${encodeURIComponent(username)}`, options).then((json) => json.data),
|
axios.patch(`${baseURI}api/auth/users/${encodeURIComponent(username)}`, options).then((res) => res.data),
|
||||||
|
|
||||||
deleteUser: (username: Credentials['username']) =>
|
deleteUser: (username: Credentials['username']) =>
|
||||||
axios
|
axios.delete(`${baseURI}api/auth/users/${encodeURIComponent(username)}`).then(
|
||||||
.delete(`${baseURI}api/auth/users/${encodeURIComponent(username)}`)
|
() => {
|
||||||
.then((json) => json.data)
|
// do nothing.
|
||||||
.then(
|
},
|
||||||
() => {
|
() => {
|
||||||
// do nothing.
|
// do nothing.
|
||||||
},
|
},
|
||||||
() => {
|
),
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
fetchUsers: () =>
|
fetchUsers: () =>
|
||||||
axios
|
axios.get<Array<Credentials>>(`${baseURI}api/auth/users`).then(({data}) => {
|
||||||
.get(`${baseURI}api/auth/users`)
|
AuthStore.handleListUsersSuccess(data);
|
||||||
.then((json) => json.data)
|
}),
|
||||||
.then((data) => {
|
|
||||||
AuthStore.handleListUsersSuccess(data);
|
|
||||||
}),
|
|
||||||
|
|
||||||
logout: () =>
|
logout: () =>
|
||||||
axios.get(`${baseURI}api/auth/logout`).then(
|
axios.get(`${baseURI}api/auth/logout`).then(
|
||||||
@@ -95,47 +87,42 @@ const AuthActions = {
|
|||||||
),
|
),
|
||||||
|
|
||||||
register: (options: AuthRegistrationOptions) =>
|
register: (options: AuthRegistrationOptions) =>
|
||||||
axios
|
axios.post<AuthRegistrationResponse>(`${baseURI}api/auth/register`, options).then(
|
||||||
.post(`${baseURI}api/auth/register`, options)
|
({data}) => {
|
||||||
.then((json) => json.data)
|
AuthStore.handleRegisterSuccess(data);
|
||||||
.then(
|
},
|
||||||
(data) => {
|
(error: AxiosError) => {
|
||||||
AuthStore.handleRegisterSuccess(data);
|
throw error;
|
||||||
},
|
},
|
||||||
(error: AxiosError) => {
|
),
|
||||||
throw error;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
verify: () =>
|
verify: () =>
|
||||||
axios
|
axios
|
||||||
.get(`${baseURI}api/auth/verify?${Date.now()}`)
|
.get<AuthVerificationResponse>(`${baseURI}api/auth/verify?${Date.now()}`)
|
||||||
.then(
|
.then(
|
||||||
(res: AxiosResponse) => {
|
({data}) => {
|
||||||
if (res.data.configs != null) {
|
if (data.configs != null) {
|
||||||
ConfigStore.handlePreloadConfigs(res.data.configs);
|
ConfigStore.handlePreloadConfigs(data.configs);
|
||||||
}
|
}
|
||||||
return res.data;
|
return data;
|
||||||
},
|
},
|
||||||
(error: AxiosError) => {
|
(error: AxiosError<AuthVerificationResponse>) => {
|
||||||
if (error.response?.data?.configs != null) {
|
if (error.response?.data?.configs != null) {
|
||||||
ConfigStore.handlePreloadConfigs(error.response.data.configs);
|
ConfigStore.handlePreloadConfigs(error.response.data.configs);
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then(
|
.then((data) => {
|
||||||
(data: AuthVerificationResponse) => {
|
AuthStore.handleAuthVerificationSuccess(data);
|
||||||
AuthStore.handleAuthVerificationSuccess(data);
|
|
||||||
|
|
||||||
return Promise.all([ClientActions.fetchSettings(), SettingActions.fetchSettings()]).then(() => data);
|
return Promise.all([ClientActions.fetchSettings(), SettingActions.fetchSettings()]).then(() => data);
|
||||||
},
|
})
|
||||||
(error) => {
|
.catch((error) => {
|
||||||
AuthStore.handleAuthVerificationError();
|
AuthStore.handleAuthVerificationError();
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
},
|
}),
|
||||||
),
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default AuthActions;
|
export default AuthActions;
|
||||||
|
|||||||
@@ -11,46 +11,35 @@ const {baseURI} = ConfigStore;
|
|||||||
|
|
||||||
const ClientActions = {
|
const ClientActions = {
|
||||||
fetchSettings: async (): Promise<void> =>
|
fetchSettings: async (): Promise<void> =>
|
||||||
axios
|
axios.get<ClientSettings>(`${baseURI}api/client/settings`).then(
|
||||||
.get(`${baseURI}api/client/settings`)
|
({data}) => {
|
||||||
.then((json) => json.data)
|
SettingStore.handleClientSettingsFetchSuccess(data);
|
||||||
.then(
|
},
|
||||||
(data) => {
|
() => {
|
||||||
SettingStore.handleClientSettingsFetchSuccess(data);
|
// do nothing.
|
||||||
},
|
},
|
||||||
() => {
|
),
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
saveSettings: async (settings: SetClientSettingsOptions, options?: {alert?: boolean}): Promise<void> => {
|
saveSettings: async (settings: SetClientSettingsOptions, options?: {alert?: boolean}): Promise<void> => {
|
||||||
if (Object.keys(settings).length > 0) {
|
if (Object.keys(settings).length > 0) {
|
||||||
SettingStore.saveClientSettings(settings);
|
SettingStore.saveClientSettings(settings);
|
||||||
|
|
||||||
let err = false;
|
const success = await axios.patch(`${baseURI}api/client/settings`, settings).then(
|
||||||
await axios
|
() => true,
|
||||||
.patch(`${baseURI}api/client/settings`, settings)
|
() => false,
|
||||||
.then((json) => json.data)
|
);
|
||||||
.then(
|
|
||||||
() => {
|
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
err = true;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (options?.alert) {
|
if (options?.alert) {
|
||||||
// TODO: More precise error message.
|
// TODO: More precise error message.
|
||||||
AlertStore.add(
|
AlertStore.add(
|
||||||
err
|
success
|
||||||
? {
|
? {
|
||||||
id: 'general.error.unknown',
|
|
||||||
type: 'error',
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
id: 'alert.settings.saved',
|
id: 'alert.settings.saved',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
id: 'general.error.unknown',
|
||||||
|
type: 'error',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -61,12 +50,9 @@ const ClientActions = {
|
|||||||
ClientActions.saveSettings({[property]: data}),
|
ClientActions.saveSettings({[property]: data}),
|
||||||
|
|
||||||
testConnection: async (): Promise<void> =>
|
testConnection: async (): Promise<void> =>
|
||||||
axios
|
axios.get(`${baseURI}api/client/connection-test`).then(() => {
|
||||||
.get(`${baseURI}api/client/connection-test`)
|
// do nothing.
|
||||||
.then((json) => json.data)
|
}),
|
||||||
.then(() => {
|
|
||||||
// do nothing.
|
|
||||||
}),
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default ClientActions;
|
export default ClientActions;
|
||||||
|
|||||||
@@ -3,58 +3,40 @@ import axios from 'axios';
|
|||||||
import ConfigStore from '@client/stores/ConfigStore';
|
import ConfigStore from '@client/stores/ConfigStore';
|
||||||
import FeedStore from '@client/stores/FeedStore';
|
import FeedStore from '@client/stores/FeedStore';
|
||||||
|
|
||||||
|
import type {Feed, Item, Rule} from '@shared/types/Feed';
|
||||||
import type {AddFeedOptions, AddRuleOptions, ModifyFeedOptions} from '@shared/types/api/feed-monitor';
|
import type {AddFeedOptions, AddRuleOptions, ModifyFeedOptions} from '@shared/types/api/feed-monitor';
|
||||||
|
|
||||||
const {baseURI} = ConfigStore;
|
const {baseURI} = ConfigStore;
|
||||||
|
|
||||||
const FeedActions = {
|
const FeedActions = {
|
||||||
addFeed: (options: AddFeedOptions) =>
|
addFeed: (options: AddFeedOptions) =>
|
||||||
axios
|
axios.put(`${baseURI}api/feed-monitor/feeds`, options).then(() => FeedActions.fetchFeedMonitors()),
|
||||||
.put(`${baseURI}api/feed-monitor/feeds`, options)
|
|
||||||
.then((json) => json.data)
|
|
||||||
.then(() => {
|
|
||||||
FeedActions.fetchFeedMonitors();
|
|
||||||
}),
|
|
||||||
|
|
||||||
modifyFeed: (id: string, options: ModifyFeedOptions) =>
|
modifyFeed: (id: string, options: ModifyFeedOptions) =>
|
||||||
axios
|
axios.patch(`${baseURI}api/feed-monitor/feeds/${id}`, options).then(() => FeedActions.fetchFeedMonitors()),
|
||||||
.patch(`${baseURI}api/feed-monitor/feeds/${id}`, options)
|
|
||||||
.then((json) => json.data)
|
|
||||||
.then(() => {
|
|
||||||
FeedActions.fetchFeedMonitors();
|
|
||||||
}),
|
|
||||||
|
|
||||||
addRule: (options: AddRuleOptions) =>
|
addRule: (options: AddRuleOptions) =>
|
||||||
axios
|
axios.put(`${baseURI}api/feed-monitor/rules`, options).then(() => FeedActions.fetchFeedMonitors()),
|
||||||
.put(`${baseURI}api/feed-monitor/rules`, options)
|
|
||||||
.then((json) => json.data)
|
|
||||||
.then(() => {
|
|
||||||
FeedActions.fetchFeedMonitors();
|
|
||||||
}),
|
|
||||||
|
|
||||||
fetchFeedMonitors: () =>
|
fetchFeedMonitors: () =>
|
||||||
axios
|
axios.get<{feeds: Array<Feed>; rules: Array<Rule>}>(`${baseURI}api/feed-monitor`).then(
|
||||||
.get(`${baseURI}api/feed-monitor`)
|
({data}) => {
|
||||||
.then((json) => json.data)
|
FeedStore.handleFeedMonitorsFetchSuccess(data);
|
||||||
.then(
|
},
|
||||||
(data) => {
|
() => {
|
||||||
FeedStore.handleFeedMonitorsFetchSuccess(data);
|
// do nothing.
|
||||||
},
|
},
|
||||||
() => {
|
),
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
fetchItems: ({id, search}: {id: string; search: string}) =>
|
fetchItems: ({id, search}: {id: string; search: string}) =>
|
||||||
axios
|
axios
|
||||||
.get(`${baseURI}api/feed-monitor/feeds/${id}/items`, {
|
.get<Item[]>(`${baseURI}api/feed-monitor/feeds/${id}/items`, {
|
||||||
params: {
|
params: {
|
||||||
search,
|
search,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((json) => json.data)
|
|
||||||
.then(
|
.then(
|
||||||
(data) => {
|
({data}) => {
|
||||||
FeedStore.handleItemsFetchSuccess(data);
|
FeedStore.handleItemsFetchSuccess(data);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
@@ -63,17 +45,12 @@ const FeedActions = {
|
|||||||
),
|
),
|
||||||
|
|
||||||
removeFeedMonitor: (id: string) =>
|
removeFeedMonitor: (id: string) =>
|
||||||
axios
|
axios.delete(`${baseURI}api/feed-monitor/${id}`).then(
|
||||||
.delete(`${baseURI}api/feed-monitor/${id}`)
|
() => FeedActions.fetchFeedMonitors(),
|
||||||
.then((json) => json.data)
|
() => {
|
||||||
.then(
|
// do nothing.
|
||||||
() => {
|
},
|
||||||
FeedActions.fetchFeedMonitors();
|
),
|
||||||
},
|
|
||||||
() => {
|
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
),
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default FeedActions;
|
export default FeedActions;
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ import TorrentStore from '@client/stores/TorrentStore';
|
|||||||
import TransferDataStore from '@client/stores/TransferDataStore';
|
import TransferDataStore from '@client/stores/TransferDataStore';
|
||||||
import UIStore from '@client/stores/UIStore';
|
import UIStore from '@client/stores/UIStore';
|
||||||
|
|
||||||
|
import type {DirectoryListResponse} from '@shared/types/api';
|
||||||
import type {HistorySnapshot} from '@shared/constants/historySnapshotTypes';
|
import type {HistorySnapshot} from '@shared/constants/historySnapshotTypes';
|
||||||
import type {NotificationFetchOptions} from '@shared/types/Notification';
|
import type {NotificationFetchOptions, NotificationState} from '@shared/types/Notification';
|
||||||
import type {ServerEvents} from '@shared/types/ServerEvents';
|
import type {ServerEvents} from '@shared/types/ServerEvents';
|
||||||
|
|
||||||
interface ActivityStreamOptions {
|
interface ActivityStreamOptions {
|
||||||
@@ -70,17 +71,14 @@ const ServerEventHandlers: Record<keyof ServerEvents, (event: unknown) => void>
|
|||||||
const FloodActions = {
|
const FloodActions = {
|
||||||
clearNotifications: () => {
|
clearNotifications: () => {
|
||||||
NotificationStore.clearAll();
|
NotificationStore.clearAll();
|
||||||
return axios
|
return axios.delete(`${baseURI}api/notifications`).then(
|
||||||
.delete(`${baseURI}api/notifications`)
|
() => {
|
||||||
.then((json) => json.data)
|
// do nothing.
|
||||||
.then(
|
},
|
||||||
() => {
|
() => {
|
||||||
// do nothing.
|
// do nothing.
|
||||||
},
|
},
|
||||||
() => {
|
);
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
closeActivityStream() {
|
closeActivityStream() {
|
||||||
@@ -101,19 +99,18 @@ const FloodActions = {
|
|||||||
|
|
||||||
fetchDirectoryList: (path: string) =>
|
fetchDirectoryList: (path: string) =>
|
||||||
axios
|
axios
|
||||||
.get(`${baseURI}api/directory-list`, {
|
.get<DirectoryListResponse>(`${baseURI}api/directory-list`, {
|
||||||
params: {path},
|
params: {path},
|
||||||
})
|
})
|
||||||
.then((json) => json.data),
|
.then((res) => res.data),
|
||||||
|
|
||||||
fetchNotifications: (options: NotificationFetchOptions) =>
|
fetchNotifications: (options: NotificationFetchOptions) =>
|
||||||
axios
|
axios
|
||||||
.get(`${baseURI}api/notifications`, {
|
.get<NotificationState>(`${baseURI}api/notifications`, {
|
||||||
params: options,
|
params: options,
|
||||||
})
|
})
|
||||||
.then((json) => json.data)
|
|
||||||
.then(
|
.then(
|
||||||
(data) => {
|
({data}) => {
|
||||||
NotificationStore.handleNotificationsFetchSuccess(data);
|
NotificationStore.handleNotificationsFetchSuccess(data);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -11,46 +11,35 @@ const {baseURI} = ConfigStore;
|
|||||||
|
|
||||||
const SettingActions = {
|
const SettingActions = {
|
||||||
fetchSettings: async (): Promise<void> =>
|
fetchSettings: async (): Promise<void> =>
|
||||||
axios
|
axios.get<FloodSettings>(`${baseURI}api/settings`).then(
|
||||||
.get(`${baseURI}api/settings`)
|
({data}) => {
|
||||||
.then((json) => json.data)
|
SettingStore.handleSettingsFetchSuccess(data);
|
||||||
.then(
|
},
|
||||||
(data) => {
|
() => {
|
||||||
SettingStore.handleSettingsFetchSuccess(data);
|
// do nothing.
|
||||||
},
|
},
|
||||||
() => {
|
),
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
saveSettings: async (settings: SetFloodSettingsOptions, options?: {alert?: boolean}): Promise<void> => {
|
saveSettings: async (settings: SetFloodSettingsOptions, options?: {alert?: boolean}): Promise<void> => {
|
||||||
if (Object.keys(settings).length > 0) {
|
if (Object.keys(settings).length > 0) {
|
||||||
SettingStore.saveFloodSettings(settings);
|
SettingStore.saveFloodSettings(settings);
|
||||||
|
|
||||||
let err = false;
|
const success = await axios.patch(`${baseURI}api/settings`, settings).then(
|
||||||
await axios
|
() => true,
|
||||||
.patch(`${baseURI}api/settings`, settings)
|
() => false,
|
||||||
.then((json) => json.data)
|
);
|
||||||
.then(
|
|
||||||
() => {
|
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
err = true;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (options?.alert) {
|
if (options?.alert) {
|
||||||
// TODO: More precise error message.
|
// TODO: More precise error message.
|
||||||
AlertStore.add(
|
AlertStore.add(
|
||||||
err
|
success
|
||||||
? {
|
? {
|
||||||
id: 'general.error.unknown',
|
|
||||||
type: 'error',
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
id: 'alert.settings.saved',
|
id: 'alert.settings.saved',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
id: 'general.error.unknown',
|
||||||
|
type: 'error',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import type {
|
|||||||
ReannounceTorrentsOptions,
|
ReannounceTorrentsOptions,
|
||||||
SetTorrentsTagsOptions,
|
SetTorrentsTagsOptions,
|
||||||
} from '@shared/schema/api/torrents';
|
} from '@shared/schema/api/torrents';
|
||||||
import type {
|
import {
|
||||||
CheckTorrentsOptions,
|
CheckTorrentsOptions,
|
||||||
CreateTorrentOptions,
|
CreateTorrentOptions,
|
||||||
DeleteTorrentsOptions,
|
DeleteTorrentsOptions,
|
||||||
@@ -23,6 +23,8 @@ import type {
|
|||||||
SetTorrentsTrackersOptions,
|
SetTorrentsTrackersOptions,
|
||||||
StartTorrentsOptions,
|
StartTorrentsOptions,
|
||||||
StopTorrentsOptions,
|
StopTorrentsOptions,
|
||||||
|
TorrentAddResponse,
|
||||||
|
TorrentMediainfoResponse,
|
||||||
} from '@shared/types/api/torrents';
|
} from '@shared/types/api/torrents';
|
||||||
import type {TorrentContent} from '@shared/types/TorrentContent';
|
import type {TorrentContent} from '@shared/types/TorrentContent';
|
||||||
import type {TorrentPeer} from '@shared/types/TorrentPeer';
|
import type {TorrentPeer} from '@shared/types/TorrentPeer';
|
||||||
@@ -56,43 +58,37 @@ const emitFailedToAddTorrentAlert = (count: number) => {
|
|||||||
|
|
||||||
const TorrentActions = {
|
const TorrentActions = {
|
||||||
addTorrentsByUrls: (options: AddTorrentByURLOptions): Promise<void> =>
|
addTorrentsByUrls: (options: AddTorrentByURLOptions): Promise<void> =>
|
||||||
axios
|
axios.post<TorrentAddResponse>(`${baseURI}api/torrents/add-urls`, options).then(
|
||||||
.post(`${baseURI}api/torrents/add-urls`, options)
|
({data}) => {
|
||||||
.then((json) => json.data)
|
if (data.length) {
|
||||||
.then(
|
emitTorrentAddedAlert(data.length);
|
||||||
(response) => {
|
} else {
|
||||||
if (response.length) {
|
emitRequestSentAlert(options.urls.length);
|
||||||
emitTorrentAddedAlert(response.length);
|
}
|
||||||
} else {
|
},
|
||||||
emitRequestSentAlert(options.urls.length);
|
() => {
|
||||||
}
|
emitFailedToAddTorrentAlert(options.urls.length);
|
||||||
},
|
},
|
||||||
() => {
|
),
|
||||||
emitFailedToAddTorrentAlert(options.urls.length);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
addTorrentsByFiles: (options: AddTorrentByFileOptions): Promise<void> =>
|
addTorrentsByFiles: (options: AddTorrentByFileOptions): Promise<void> =>
|
||||||
axios
|
axios.post<TorrentAddResponse>(`${baseURI}api/torrents/add-files`, options).then(
|
||||||
.post(`${baseURI}api/torrents/add-files`, options)
|
({data}) => {
|
||||||
.then((json) => json.data)
|
if (data.length) {
|
||||||
.then(
|
emitTorrentAddedAlert(data.length);
|
||||||
(response) => {
|
} else {
|
||||||
if (response.length) {
|
emitRequestSentAlert(options.files.length);
|
||||||
emitTorrentAddedAlert(response.length);
|
}
|
||||||
} else {
|
},
|
||||||
emitRequestSentAlert(options.files.length);
|
() => {
|
||||||
}
|
emitFailedToAddTorrentAlert(options.files.length);
|
||||||
},
|
},
|
||||||
() => {
|
),
|
||||||
emitFailedToAddTorrentAlert(options.files.length);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
createTorrent: (options: CreateTorrentOptions): Promise<void> =>
|
createTorrent: (options: CreateTorrentOptions): Promise<void> =>
|
||||||
axios.post(`${baseURI}api/torrents/create`, options, {responseType: 'blob'}).then(
|
axios.post<Blob>(`${baseURI}api/torrents/create`, options, {responseType: 'blob'}).then(
|
||||||
(response) => {
|
({data}) => {
|
||||||
download(response.data, (options.name || `${Date.now()}`).concat('.torrent'));
|
download(data, (options.name || `${Date.now()}`).concat('.torrent'));
|
||||||
emitTorrentAddedAlert(1);
|
emitTorrentAddedAlert(1);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
@@ -101,68 +97,53 @@ const TorrentActions = {
|
|||||||
),
|
),
|
||||||
|
|
||||||
deleteTorrents: (options: DeleteTorrentsOptions): Promise<void> =>
|
deleteTorrents: (options: DeleteTorrentsOptions): Promise<void> =>
|
||||||
axios
|
axios.post(`${baseURI}api/torrents/delete`, options).then(
|
||||||
.post(`${baseURI}api/torrents/delete`, options)
|
() =>
|
||||||
.then((json) => json.data)
|
AlertStore.add({
|
||||||
.then(
|
id: 'alert.torrent.remove',
|
||||||
() => {
|
type: 'success',
|
||||||
AlertStore.add({
|
count: options.hashes.length,
|
||||||
id: 'alert.torrent.remove',
|
}),
|
||||||
type: 'success',
|
() =>
|
||||||
count: options.hashes.length,
|
AlertStore.add({
|
||||||
});
|
id: 'alert.torrent.remove.failed',
|
||||||
},
|
type: 'error',
|
||||||
() => {
|
count: options.hashes.length,
|
||||||
AlertStore.add({
|
}),
|
||||||
id: 'alert.torrent.remove.failed',
|
),
|
||||||
type: 'error',
|
|
||||||
count: options.hashes.length,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
checkHash: (options: CheckTorrentsOptions): Promise<void> =>
|
checkHash: (options: CheckTorrentsOptions): Promise<void> =>
|
||||||
axios
|
axios.post(`${baseURI}api/torrents/check-hash`, options).then(
|
||||||
.post(`${baseURI}api/torrents/check-hash`, options)
|
() => {
|
||||||
.then((json) => json.data)
|
// do nothing.
|
||||||
.then(
|
},
|
||||||
() => {
|
() => {
|
||||||
// do nothing.
|
// do nothing.
|
||||||
},
|
},
|
||||||
() => {
|
),
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
fetchMediainfo: (hash: TorrentProperties['hash'], cancelToken?: CancelToken): Promise<{output: string}> =>
|
fetchMediainfo: (hash: TorrentProperties['hash'], cancelToken?: CancelToken): Promise<{output: string}> =>
|
||||||
axios.get(`${baseURI}api/torrents/${hash}/mediainfo`, {cancelToken}).then<{output: string}>((json) => json.data),
|
axios
|
||||||
|
.get<TorrentMediainfoResponse>(`${baseURI}api/torrents/${hash}/mediainfo`, {cancelToken})
|
||||||
|
.then<{output: string}>((res) => res.data),
|
||||||
|
|
||||||
fetchTorrentContents: (hash: TorrentProperties['hash']): Promise<Array<TorrentContent> | null> =>
|
fetchTorrentContents: (hash: TorrentProperties['hash']): Promise<Array<TorrentContent> | null> =>
|
||||||
axios
|
axios.get<Array<TorrentContent>>(`${baseURI}api/torrents/${hash}/contents`).then(
|
||||||
.get(`${baseURI}api/torrents/${hash}/contents`)
|
(res) => res.data,
|
||||||
.then<Array<TorrentContent>>((json) => json.data)
|
() => null,
|
||||||
.then(
|
),
|
||||||
(contents) => contents,
|
|
||||||
() => null,
|
|
||||||
),
|
|
||||||
|
|
||||||
fetchTorrentPeers: (hash: TorrentProperties['hash']): Promise<Array<TorrentPeer> | null> =>
|
fetchTorrentPeers: (hash: TorrentProperties['hash']): Promise<Array<TorrentPeer> | null> =>
|
||||||
axios
|
axios.get<Array<TorrentPeer>>(`${baseURI}api/torrents/${hash}/peers`).then(
|
||||||
.get(`${baseURI}api/torrents/${hash}/peers`)
|
(res) => res.data,
|
||||||
.then<Array<TorrentPeer>>((json) => json.data)
|
() => null,
|
||||||
.then(
|
),
|
||||||
(peers) => peers,
|
|
||||||
() => null,
|
|
||||||
),
|
|
||||||
|
|
||||||
fetchTorrentTrackers: (hash: TorrentProperties['hash']): Promise<Array<TorrentTracker> | null> =>
|
fetchTorrentTrackers: (hash: TorrentProperties['hash']): Promise<Array<TorrentTracker> | null> =>
|
||||||
axios
|
axios.get<Array<TorrentTracker>>(`${baseURI}api/torrents/${hash}/trackers`).then(
|
||||||
.get(`${baseURI}api/torrents/${hash}/trackers`)
|
(res) => res.data,
|
||||||
.then<Array<TorrentTracker>>((json) => json.data)
|
() => null,
|
||||||
.then(
|
),
|
||||||
(trackers) => trackers,
|
|
||||||
() => null,
|
|
||||||
),
|
|
||||||
|
|
||||||
getTorrentContentsDataPermalink: (hash: TorrentProperties['hash'], indices: number[]): Promise<string> =>
|
getTorrentContentsDataPermalink: (hash: TorrentProperties['hash'], indices: number[]): Promise<string> =>
|
||||||
axios
|
axios
|
||||||
@@ -175,150 +156,116 @@ const TorrentActions = {
|
|||||||
),
|
),
|
||||||
|
|
||||||
moveTorrents: (options: MoveTorrentsOptions): Promise<void> =>
|
moveTorrents: (options: MoveTorrentsOptions): Promise<void> =>
|
||||||
axios
|
axios.post(`${baseURI}api/torrents/move`, options).then(
|
||||||
.post(`${baseURI}api/torrents/move`, options)
|
() =>
|
||||||
.then((json) => json.data)
|
AlertStore.add({
|
||||||
.then(
|
id: 'alert.torrent.move',
|
||||||
() => {
|
type: 'success',
|
||||||
AlertStore.add({
|
count: options.hashes.length,
|
||||||
id: 'alert.torrent.move',
|
}),
|
||||||
type: 'success',
|
() =>
|
||||||
count: options.hashes.length,
|
AlertStore.add({
|
||||||
});
|
id: 'alert.torrent.move.failed',
|
||||||
},
|
type: 'error',
|
||||||
() => {
|
count: options.hashes.length,
|
||||||
AlertStore.add({
|
}),
|
||||||
id: 'alert.torrent.move.failed',
|
),
|
||||||
type: 'error',
|
|
||||||
count: options.hashes.length,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
reannounce: (options: ReannounceTorrentsOptions): Promise<void> =>
|
reannounce: (options: ReannounceTorrentsOptions): Promise<void> =>
|
||||||
axios
|
axios.post(`${baseURI}api/torrents/reannounce`, options).then(
|
||||||
.post(`${baseURI}api/torrents/reannounce`, options)
|
() => {
|
||||||
.then((json) => json.data)
|
// do nothing.
|
||||||
.then(
|
},
|
||||||
() => {
|
() => {
|
||||||
// do nothing.
|
// do nothing.
|
||||||
},
|
},
|
||||||
() => {
|
),
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
startTorrents: async (options: StartTorrentsOptions): Promise<void> => {
|
startTorrents: async (options: StartTorrentsOptions): Promise<void> => {
|
||||||
if (options.hashes.length > 0) {
|
if (options.hashes.length > 0) {
|
||||||
return axios
|
return axios.post(`${baseURI}api/torrents/start`, options).then(
|
||||||
.post(`${baseURI}api/torrents/start`, options)
|
() => {
|
||||||
.then((json) => json.data)
|
// do nothing.
|
||||||
.then(
|
},
|
||||||
() => {
|
() => {
|
||||||
// do nothing.
|
// do nothing.
|
||||||
},
|
},
|
||||||
() => {
|
);
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
|
|
||||||
stopTorrents: async (options: StopTorrentsOptions): Promise<void> => {
|
stopTorrents: async (options: StopTorrentsOptions): Promise<void> => {
|
||||||
if (options.hashes.length > 0) {
|
if (options.hashes.length > 0) {
|
||||||
return axios
|
return axios.post(`${baseURI}api/torrents/stop`, options).then(
|
||||||
.post(`${baseURI}api/torrents/stop`, options)
|
() => {
|
||||||
.then((json) => json.data)
|
// do nothing.
|
||||||
.then(
|
},
|
||||||
() => {
|
() => {
|
||||||
// do nothing.
|
// do nothing.
|
||||||
},
|
},
|
||||||
() => {
|
);
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
|
|
||||||
setInitialSeeding: (options: SetTorrentsInitialSeedingOptions): Promise<void> =>
|
setInitialSeeding: (options: SetTorrentsInitialSeedingOptions): Promise<void> =>
|
||||||
axios
|
axios.patch(`${baseURI}api/torrents/initial-seeding`, options).then(
|
||||||
.patch(`${baseURI}api/torrents/initial-seeding`, options)
|
() => {
|
||||||
.then((json) => json.data)
|
// do nothing.
|
||||||
.then(
|
},
|
||||||
() => {
|
() => {
|
||||||
// do nothing.
|
// do nothing.
|
||||||
},
|
},
|
||||||
() => {
|
),
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
setPriority: (options: SetTorrentsPriorityOptions): Promise<void> =>
|
setPriority: (options: SetTorrentsPriorityOptions): Promise<void> =>
|
||||||
axios
|
axios.patch(`${baseURI}api/torrents/priority`, options).then(
|
||||||
.patch(`${baseURI}api/torrents/priority`, options)
|
() => {
|
||||||
.then((json) => json.data)
|
// do nothing.
|
||||||
.then(
|
},
|
||||||
() => {
|
() => {
|
||||||
// do nothing.
|
// do nothing.
|
||||||
},
|
},
|
||||||
() => {
|
),
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
setSequential: (options: SetTorrentsSequentialOptions): Promise<void> =>
|
setSequential: (options: SetTorrentsSequentialOptions): Promise<void> =>
|
||||||
axios
|
axios.patch(`${baseURI}api/torrents/sequential`, options).then(
|
||||||
.patch(`${baseURI}api/torrents/sequential`, options)
|
() => {
|
||||||
.then((json) => json.data)
|
// do nothing.
|
||||||
.then(
|
},
|
||||||
() => {
|
() => {
|
||||||
// do nothing.
|
// do nothing.
|
||||||
},
|
},
|
||||||
() => {
|
),
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
setFilePriority: (hash: TorrentProperties['hash'], options: SetTorrentContentsPropertiesOptions): Promise<void> =>
|
setFilePriority: (hash: TorrentProperties['hash'], options: SetTorrentContentsPropertiesOptions): Promise<void> =>
|
||||||
axios
|
axios.patch(`${baseURI}api/torrents/${hash}/contents`, options).then(
|
||||||
.patch(`${baseURI}api/torrents/${hash}/contents`, options)
|
() => {
|
||||||
.then((json) => json.data)
|
// do nothing.
|
||||||
.then(
|
},
|
||||||
() => {
|
() => {
|
||||||
// do nothing.
|
// do nothing.
|
||||||
},
|
},
|
||||||
() => {
|
),
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
setTags: (options: SetTorrentsTagsOptions): Promise<void> =>
|
setTags: (options: SetTorrentsTagsOptions): Promise<void> =>
|
||||||
axios
|
axios.patch(`${baseURI}api/torrents/tags`, options).then(
|
||||||
.patch(`${baseURI}api/torrents/tags`, options)
|
() => UIStore.handleSetTaxonomySuccess(),
|
||||||
.then((json) => json.data)
|
() => {
|
||||||
.then(
|
// do nothing.
|
||||||
() => {
|
},
|
||||||
UIStore.handleSetTaxonomySuccess();
|
),
|
||||||
},
|
|
||||||
() => {
|
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
setTrackers: (options: SetTorrentsTrackersOptions): Promise<void> =>
|
setTrackers: (options: SetTorrentsTrackersOptions): Promise<void> =>
|
||||||
axios
|
axios.patch(`${baseURI}api/torrents/trackers`, options).then(
|
||||||
.patch(`${baseURI}api/torrents/trackers`, options)
|
() => {
|
||||||
.then((json) => json.data)
|
// do nothing.
|
||||||
.then(
|
},
|
||||||
() => {
|
() => {
|
||||||
// do nothing.
|
// do nothing.
|
||||||
},
|
},
|
||||||
() => {
|
),
|
||||||
// do nothing.
|
|
||||||
},
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TorrentActions;
|
export default TorrentActions;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import {forwardRef, MutableRefObject, ReactNode, useEffect, useRef, useState} from 'react';
|
import {forwardRef, MutableRefObject, ReactElement, ReactNode, useEffect, useRef, useState} from 'react';
|
||||||
import {Trans, useLingui} from '@lingui/react';
|
import {Trans, useLingui} from '@lingui/react';
|
||||||
import {useEnsuredForwardedRef} from 'react-use';
|
import {useEnsuredForwardedRef} from 'react-use';
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ const FilesystemBrowserTextbox = forwardRef<HTMLInputElement, FilesystemBrowserT
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggles: React.ReactNodeArray = [];
|
const toggles: Array<ReactElement> = [];
|
||||||
if (showBasePathToggle) {
|
if (showBasePathToggle) {
|
||||||
toggles.push(
|
toggles.push(
|
||||||
<Checkbox grow={false} id="isBasePath" key="isBasePath">
|
<Checkbox grow={false} id="isBasePath" key="isBasePath">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import {FC, ReactNode, ReactNodeArray, useEffect, useRef, useState} from 'react';
|
import {FC, ReactElement, ReactNode, useEffect, useRef, useState} from 'react';
|
||||||
import {sort} from 'fast-sort';
|
import {sort} from 'fast-sort';
|
||||||
import {Trans} from '@lingui/react';
|
import {Trans} from '@lingui/react';
|
||||||
import {useKeyPressEvent} from 'react-use';
|
import {useKeyPressEvent} from 'react-use';
|
||||||
@@ -114,7 +114,7 @@ const TagSelect: FC<TagSelectProps> = ({defaultValue, placeholder, id, label, on
|
|||||||
...sort(Object.keys(TorrentFilterStore.taxonomy.tagCounts)).asc(),
|
...sort(Object.keys(TorrentFilterStore.taxonomy.tagCounts)).asc(),
|
||||||
...selectedTags,
|
...selectedTags,
|
||||||
]),
|
]),
|
||||||
].reduce((accumulator: ReactNodeArray, tag) => {
|
].reduce((accumulator: Array<ReactElement>, tag) => {
|
||||||
if (tag === '') {
|
if (tag === '') {
|
||||||
return accumulator;
|
return accumulator;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import {FC, ReactNode, ReactNodeArray} from 'react';
|
import {FC, ReactNode} from 'react';
|
||||||
|
|
||||||
export interface Tab {
|
export interface Tab {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -18,41 +18,43 @@ interface ModalTabsProps {
|
|||||||
const ModalTabs: FC<ModalTabsProps> = (props: ModalTabsProps) => {
|
const ModalTabs: FC<ModalTabsProps> = (props: ModalTabsProps) => {
|
||||||
const {activeTabId, tabs = {}, onTabChange} = props;
|
const {activeTabId, tabs = {}, onTabChange} = props;
|
||||||
|
|
||||||
const tabNodes: ReactNodeArray = Object.keys(tabs).map((tabId) => {
|
return (
|
||||||
const currentTab = tabs[tabId];
|
<ul className="modal__tabs">
|
||||||
|
{Object.keys(tabs).map((tabId) => {
|
||||||
|
const currentTab = tabs[tabId];
|
||||||
|
|
||||||
currentTab.id = tabId;
|
currentTab.id = tabId;
|
||||||
|
|
||||||
const classes = classnames('modal__tab', {
|
const classes = classnames('modal__tab', {
|
||||||
'is-active': tabId === activeTabId,
|
'is-active': tabId === activeTabId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={classes} key={tabId}>
|
<li className={classes} key={tabId}>
|
||||||
<button
|
<button
|
||||||
css={{
|
css={{
|
||||||
':focus': {
|
':focus': {
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
WebkitTapHighlightColor: 'transparent',
|
WebkitTapHighlightColor: 'transparent',
|
||||||
},
|
},
|
||||||
':focus-visible': {
|
':focus-visible': {
|
||||||
outline: 'dashed',
|
outline: 'dashed',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (onTabChange) {
|
if (onTabChange) {
|
||||||
onTabChange(currentTab);
|
onTabChange(currentTab);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currentTab.label}
|
{currentTab.label}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
});
|
})}
|
||||||
|
</ul>
|
||||||
return <ul className="modal__tabs">{tabNodes}</ul>;
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ModalTabs.defaultProps = {
|
ModalTabs.defaultProps = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {FC, ReactNodeArray, useRef, useState} from 'react';
|
import {FC, ReactElement, useRef, useState} from 'react';
|
||||||
import {Trans, useLingui} from '@lingui/react';
|
import {Trans, useLingui} from '@lingui/react';
|
||||||
|
|
||||||
import {Button, Form, FormError, FormRow, FormRowItem} from '@client/ui';
|
import {Button, Form, FormError, FormRow, FormRowItem} from '@client/ui';
|
||||||
@@ -164,7 +164,7 @@ const DownloadRulesTab: FC = () => {
|
|||||||
<ModalFormSectionHeader>
|
<ModalFormSectionHeader>
|
||||||
<Trans id="feeds.existing.rules" />
|
<Trans id="feeds.existing.rules" />
|
||||||
</ModalFormSectionHeader>
|
</ModalFormSectionHeader>
|
||||||
{Object.keys(errors).reduce((memo: ReactNodeArray, key) => {
|
{Object.keys(errors).reduce((memo: Array<ReactElement>, key) => {
|
||||||
if (errors[key as ValidatedField] != null) {
|
if (errors[key as ValidatedField] != null) {
|
||||||
memo.push(
|
memo.push(
|
||||||
<FormRow key={`error-${key}`}>
|
<FormRow key={`error-${key}`}>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {FC, ReactNodeArray} from 'react';
|
import {FC, ReactElement} from 'react';
|
||||||
import {observer} from 'mobx-react';
|
import {observer} from 'mobx-react';
|
||||||
import {Trans} from '@lingui/react';
|
import {Trans} from '@lingui/react';
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ interface FeedItemsProps {
|
|||||||
const FeedItems: FC<FeedItemsProps> = observer(({selectedFeedID}: FeedItemsProps) => {
|
const FeedItems: FC<FeedItemsProps> = observer(({selectedFeedID}: FeedItemsProps) => {
|
||||||
const {items} = FeedStore;
|
const {items} = FeedStore;
|
||||||
|
|
||||||
const itemElements: ReactNodeArray = [];
|
const itemElements: Array<ReactElement> = [];
|
||||||
if (selectedFeedID) {
|
if (selectedFeedID) {
|
||||||
const titleOccurrences: Record<string, number> = {};
|
const titleOccurrences: Record<string, number> = {};
|
||||||
items.forEach((item, index) => {
|
items.forEach((item, index) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {FC, ReactNodeArray, useRef, useState} from 'react';
|
import {FC, ReactElement, useRef, useState} from 'react';
|
||||||
import {Trans, useLingui} from '@lingui/react';
|
import {Trans, useLingui} from '@lingui/react';
|
||||||
|
|
||||||
import {Button, Form, FormError, FormRow, FormRowItem} from '@client/ui';
|
import {Button, Form, FormError, FormRow, FormRowItem} from '@client/ui';
|
||||||
@@ -137,7 +137,7 @@ const FeedsTab: FC = () => {
|
|||||||
<ModalFormSectionHeader>
|
<ModalFormSectionHeader>
|
||||||
<Trans id="feeds.existing.feeds" />
|
<Trans id="feeds.existing.feeds" />
|
||||||
</ModalFormSectionHeader>
|
</ModalFormSectionHeader>
|
||||||
{Object.keys(errors).reduce((memo: ReactNodeArray, key) => {
|
{Object.keys(errors).reduce((memo: Array<ReactElement>, key) => {
|
||||||
if (errors[key as ValidatedField] != null) {
|
if (errors[key as ValidatedField] != null) {
|
||||||
memo.push(
|
memo.push(
|
||||||
<FormRow key={`error-${key}`}>
|
<FormRow key={`error-${key}`}>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import {forwardRef, MutableRefObject, ReactNodeArray, useRef, useState} from 'react';
|
import {forwardRef, MutableRefObject, ReactElement, useRef, useState} from 'react';
|
||||||
import {observer} from 'mobx-react';
|
import {observer} from 'mobx-react';
|
||||||
import {Trans, useLingui} from '@lingui/react';
|
import {Trans, useLingui} from '@lingui/react';
|
||||||
import {useEnsuredForwardedRef} from 'react-use';
|
import {useEnsuredForwardedRef} from 'react-use';
|
||||||
@@ -76,7 +76,7 @@ const TableHeading = observer(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="table__row table__row--heading" role="row" ref={tableHeading}>
|
<div className="table__row table__row--heading" role="row" ref={tableHeading}>
|
||||||
{SettingStore.floodSettings.torrentListColumns.reduce((accumulator: ReactNodeArray, {id, visible}) => {
|
{SettingStore.floodSettings.torrentListColumns.reduce((accumulator: Array<ReactElement>, {id, visible}) => {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
return accumulator;
|
return accumulator;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {CSSProperties, forwardRef, KeyboardEvent, MouseEvent, ReactNodeArray, TouchEvent} from 'react';
|
import {CSSProperties, forwardRef, KeyboardEvent, MouseEvent, ReactElement, TouchEvent} from 'react';
|
||||||
import {observer} from 'mobx-react';
|
import {observer} from 'mobx-react';
|
||||||
|
|
||||||
import SettingStore from '../../stores/SettingStore';
|
import SettingStore from '../../stores/SettingStore';
|
||||||
@@ -34,7 +34,7 @@ const TorrentListRowCondensed = observer(
|
|||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const torrentListColumns = SettingStore.floodSettings.torrentListColumns.reduce(
|
const torrentListColumns = SettingStore.floodSettings.torrentListColumns.reduce(
|
||||||
(accumulator: ReactNodeArray, {id, visible}) => {
|
(accumulator: Array<ReactElement>, {id, visible}) => {
|
||||||
if (TorrentListColumns[id] == null) {
|
if (TorrentListColumns[id] == null) {
|
||||||
return accumulator;
|
return accumulator;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {CSSProperties, FC, forwardRef, KeyboardEvent, MouseEvent, ReactNodeArray, TouchEvent} from 'react';
|
import {CSSProperties, FC, forwardRef, KeyboardEvent, MouseEvent, ReactElement, TouchEvent} from 'react';
|
||||||
import {observer} from 'mobx-react';
|
import {observer} from 'mobx-react';
|
||||||
import {useLingui} from '@lingui/react';
|
import {useLingui} from '@lingui/react';
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ const TorrentListRowExpanded = observer(
|
|||||||
) => {
|
) => {
|
||||||
const columns = SettingStore.floodSettings.torrentListColumns;
|
const columns = SettingStore.floodSettings.torrentListColumns;
|
||||||
|
|
||||||
const primarySection: ReactNodeArray = [
|
const primarySection: Array<ReactElement> = [
|
||||||
<TorrentListCell
|
<TorrentListCell
|
||||||
key="name"
|
key="name"
|
||||||
hash={hash}
|
hash={hash}
|
||||||
@@ -62,12 +62,12 @@ const TorrentListRowExpanded = observer(
|
|||||||
className="torrent__details__section torrent__details__section--primary"
|
className="torrent__details__section torrent__details__section--primary"
|
||||||
/>,
|
/>,
|
||||||
];
|
];
|
||||||
const secondarySection: ReactNodeArray = [
|
const secondarySection: Array<ReactElement> = [
|
||||||
<TorrentListCell key="eta" hash={hash} column="eta" showIcon />,
|
<TorrentListCell key="eta" hash={hash} column="eta" showIcon />,
|
||||||
<TorrentListCell key="downRate" hash={hash} column="downRate" showIcon />,
|
<TorrentListCell key="downRate" hash={hash} column="downRate" showIcon />,
|
||||||
<TorrentListCell key="upRate" hash={hash} column="upRate" showIcon />,
|
<TorrentListCell key="upRate" hash={hash} column="upRate" showIcon />,
|
||||||
];
|
];
|
||||||
const tertiarySection: ReactNodeArray = [
|
const tertiarySection: Array<ReactElement> = [
|
||||||
<TorrentListCell
|
<TorrentListCell
|
||||||
key="percentComplete"
|
key="percentComplete"
|
||||||
hash={hash}
|
hash={hash}
|
||||||
@@ -76,7 +76,7 @@ const TorrentListRowExpanded = observer(
|
|||||||
showIcon
|
showIcon
|
||||||
/>,
|
/>,
|
||||||
];
|
];
|
||||||
const quaternarySection: ReactNodeArray = [
|
const quaternarySection: Array<ReactElement> = [
|
||||||
<TorrentListCell
|
<TorrentListCell
|
||||||
key="percentBar"
|
key="percentBar"
|
||||||
hash={hash}
|
hash={hash}
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import FloodActions from '@client/actions/FloodActions';
|
|||||||
|
|
||||||
import {AccessLevel} from '@shared/schema/constants/Auth';
|
import {AccessLevel} from '@shared/schema/constants/Auth';
|
||||||
|
|
||||||
import type {AuthAuthenticationResponse, AuthVerificationResponse} from '@shared/schema/api/auth';
|
import type {
|
||||||
|
AuthAuthenticationResponse,
|
||||||
|
AuthRegistrationResponse,
|
||||||
|
AuthVerificationResponse,
|
||||||
|
} from '@shared/schema/api/auth';
|
||||||
import type {Credentials} from '@shared/schema/Auth';
|
import type {Credentials} from '@shared/schema/Auth';
|
||||||
|
|
||||||
class AuthStore {
|
class AuthStore {
|
||||||
@@ -26,7 +30,7 @@ class AuthStore {
|
|||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCreateUserSuccess({username}: {username: Credentials['username']}): void {
|
handleCreateUserSuccess({username}: AuthRegistrationResponse): void {
|
||||||
this.optimisticUsers.push({username});
|
this.optimisticUsers.push({username});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,9 +41,9 @@ class AuthStore {
|
|||||||
this.users = nextUserList;
|
this.users = nextUserList;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLoginSuccess(response: AuthAuthenticationResponse): void {
|
handleLoginSuccess({username, level}: AuthAuthenticationResponse): void {
|
||||||
this.currentUser.username = response.username;
|
this.currentUser.username = username;
|
||||||
this.currentUser.isAdmin = response.level === AccessLevel.ADMINISTRATOR;
|
this.currentUser.isAdmin = level === AccessLevel.ADMINISTRATOR;
|
||||||
this.currentUser.isInitialUser = false;
|
this.currentUser.isInitialUser = false;
|
||||||
this.isAuthenticating = true;
|
this.isAuthenticating = true;
|
||||||
this.isAuthenticated = true;
|
this.isAuthenticated = true;
|
||||||
@@ -50,21 +54,23 @@ class AuthStore {
|
|||||||
this.isAuthenticating = true;
|
this.isAuthenticating = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRegisterSuccess(response: AuthAuthenticationResponse): void {
|
handleRegisterSuccess({username, level}: AuthRegistrationResponse): void {
|
||||||
this.currentUser.username = response.username;
|
this.currentUser.username = username;
|
||||||
this.currentUser.isAdmin = response.level === AccessLevel.ADMINISTRATOR;
|
this.currentUser.isAdmin = level === AccessLevel.ADMINISTRATOR;
|
||||||
this.currentUser.isInitialUser = false;
|
this.currentUser.isInitialUser = false;
|
||||||
FloodActions.restartActivityStream();
|
FloodActions.restartActivityStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAuthVerificationSuccess(response: AuthVerificationResponse): void {
|
handleAuthVerificationSuccess(response: AuthVerificationResponse): void {
|
||||||
if (response.initialUser === true) {
|
if (response.initialUser === true) {
|
||||||
this.currentUser.isInitialUser = response.initialUser;
|
this.currentUser.isInitialUser = true;
|
||||||
} else {
|
} else {
|
||||||
|
const {username, level} = response;
|
||||||
|
|
||||||
this.currentUser = {
|
this.currentUser = {
|
||||||
username: response.username,
|
username: username,
|
||||||
isAdmin: response.level === AccessLevel.ADMINISTRATOR,
|
isAdmin: level === AccessLevel.ADMINISTRATOR,
|
||||||
isInitialUser: response.initialUser,
|
isInitialUser: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {ButtonHTMLAttributes, Children, cloneElement, FC, ReactElement, ReactNode, ReactNodeArray, Ref} from 'react';
|
import {ButtonHTMLAttributes, Children, cloneElement, FC, ReactElement, ReactNode, Ref} from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import {LoadingRing} from '@client/ui/icons';
|
import {LoadingRing} from '@client/ui/icons';
|
||||||
@@ -42,8 +42,8 @@ const Button: FC<ButtonProps> = ({
|
|||||||
grow,
|
grow,
|
||||||
onClick,
|
onClick,
|
||||||
}: ButtonProps) => {
|
}: ButtonProps) => {
|
||||||
const addonNodes: ReactNodeArray = [];
|
const addonNodes: Array<ReactElement> = [];
|
||||||
const childNodes: ReactNodeArray = [];
|
const childNodes: Array<ReactElement> = [];
|
||||||
|
|
||||||
Children.toArray(children).forEach((child) => {
|
Children.toArray(children).forEach((child) => {
|
||||||
const childAsElement = child as ReactElement;
|
const childAsElement = child as ReactElement;
|
||||||
@@ -56,7 +56,7 @@ const Button: FC<ButtonProps> = ({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
childNodes.push(child);
|
childNodes.push(childAsElement);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
3423
package-lock.json
generated
3423
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
72
package.json
72
package.json
@@ -66,7 +66,7 @@
|
|||||||
"test:client": "FLOOD_OPTION_port=4200 start-server-and-test start 4200 'cypress run'"
|
"test:client": "FLOOD_OPTION_port=4200 start-server-and-test start 4200 'cypress run'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"geoip-country": "^4.0.89"
|
"geoip-country": "^4.0.91"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.15.8",
|
"@babel/core": "^7.15.8",
|
||||||
@@ -78,8 +78,8 @@
|
|||||||
"@babel/preset-react": "^7.14.5",
|
"@babel/preset-react": "^7.14.5",
|
||||||
"@babel/preset-typescript": "^7.15.0",
|
"@babel/preset-typescript": "^7.15.0",
|
||||||
"@emotion/babel-plugin": "^11.3.0",
|
"@emotion/babel-plugin": "^11.3.0",
|
||||||
"@emotion/css": "^11.1.3",
|
"@emotion/css": "^11.5.0",
|
||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.5.0",
|
||||||
"@lingui/loader": "^3.12.1",
|
"@lingui/loader": "^3.12.1",
|
||||||
"@lingui/react": "^3.12.1",
|
"@lingui/react": "^3.12.1",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.1",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.1",
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
"@types/content-disposition": "^0.5.4",
|
"@types/content-disposition": "^0.5.4",
|
||||||
"@types/cookie-parser": "^1.4.2",
|
"@types/cookie-parser": "^1.4.2",
|
||||||
"@types/create-torrent": "^5.0.0",
|
"@types/create-torrent": "^5.0.0",
|
||||||
"@types/d3": "^7.0.0",
|
"@types/d3": "^7.1.0",
|
||||||
"@types/debug": "^4.1.7",
|
"@types/debug": "^4.1.7",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/express-rate-limit": "^5.1.3",
|
"@types/express-rate-limit": "^5.1.3",
|
||||||
@@ -98,29 +98,28 @@
|
|||||||
"@types/http-errors": "^1.8.1",
|
"@types/http-errors": "^1.8.1",
|
||||||
"@types/jest": "^27.0.2",
|
"@types/jest": "^27.0.2",
|
||||||
"@types/jsonwebtoken": "^8.5.5",
|
"@types/jsonwebtoken": "^8.5.5",
|
||||||
"@types/lodash": "^4.14.175",
|
"@types/lodash": "^4.14.176",
|
||||||
"@types/morgan": "^1.9.3",
|
"@types/morgan": "^1.9.3",
|
||||||
"@types/node": "^12.20.28",
|
"@types/node": "^12.20.34",
|
||||||
"@types/overlayscrollbars": "^1.12.1",
|
"@types/overlayscrollbars": "^1.12.1",
|
||||||
"@types/parse-torrent": "^5.8.4",
|
"@types/parse-torrent": "^5.8.4",
|
||||||
"@types/passport": "^1.0.7",
|
"@types/passport": "^1.0.7",
|
||||||
"@types/passport-jwt": "^3.0.6",
|
"@types/passport-jwt": "^3.0.6",
|
||||||
"@types/react": "^17.0.27",
|
"@types/react": "^17.0.31",
|
||||||
"@types/react-dom": "^17.0.9",
|
"@types/react-dom": "^17.0.10",
|
||||||
"@types/react-measure": "^2.0.7",
|
"@types/react-measure": "^2.0.8",
|
||||||
"@types/react-router-dom": "^5.3.1",
|
"@types/react-router-dom": "^5.3.1",
|
||||||
"@types/react-transition-group": "^4.4.3",
|
"@types/react-transition-group": "^4.4.4",
|
||||||
"@types/react-window": "^1.8.5",
|
"@types/react-window": "^1.8.5",
|
||||||
"@types/spdy": "^3.4.5",
|
"@types/spdy": "^3.4.5",
|
||||||
"@types/supertest": "^2.0.11",
|
"@types/supertest": "^2.0.11",
|
||||||
"@types/tar-fs": "^2.0.1",
|
"@types/tar-fs": "^2.0.1",
|
||||||
"@types/tar-stream": "^2.2.1",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||||
"@typescript-eslint/parser": "^4.33.0",
|
"@typescript-eslint/parser": "^4.33.0",
|
||||||
"@vercel/ncc": "^0.31.1",
|
"@vercel/ncc": "^0.31.1",
|
||||||
"autoprefixer": "^10.3.7",
|
"autoprefixer": "^10.3.7",
|
||||||
"axios": "^0.21.4",
|
"axios": "^0.23.0",
|
||||||
"babel-loader": "^8.2.2",
|
"babel-loader": "^8.2.3",
|
||||||
"bencode": "^2.0.2",
|
"bencode": "^2.0.2",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
||||||
@@ -130,7 +129,7 @@
|
|||||||
"content-disposition": "^0.5.3",
|
"content-disposition": "^0.5.3",
|
||||||
"cookie-parser": "^1.4.5",
|
"cookie-parser": "^1.4.5",
|
||||||
"create-torrent": "^5.0.1",
|
"create-torrent": "^5.0.1",
|
||||||
"css-loader": "^6.3.0",
|
"css-loader": "^6.4.0",
|
||||||
"css-minimizer-webpack-plugin": "^3.1.1",
|
"css-minimizer-webpack-plugin": "^3.1.1",
|
||||||
"d3-array": "^3.1.1",
|
"d3-array": "^3.1.1",
|
||||||
"d3-scale": "^4.0.2",
|
"d3-scale": "^4.0.2",
|
||||||
@@ -141,16 +140,11 @@
|
|||||||
"eslint-config-airbnb": "^18.2.1",
|
"eslint-config-airbnb": "^18.2.1",
|
||||||
"eslint-config-airbnb-typescript": "^14.0.1",
|
"eslint-config-airbnb-typescript": "^14.0.1",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-config-react-app": "^6.0.0",
|
"eslint-config-react-app": "^7.0.0-next.91",
|
||||||
"eslint-import-resolver-webpack": "^0.13.1",
|
"eslint-import-resolver-webpack": "^0.13.2",
|
||||||
"eslint-plugin-import": "^2.24.2",
|
|
||||||
"eslint-plugin-jest": "^24.5.2",
|
|
||||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
|
||||||
"eslint-plugin-react": "^7.26.1",
|
|
||||||
"eslint-plugin-react-hooks": "^4.2.0",
|
|
||||||
"eslint-webpack-plugin": "^3.0.1",
|
"eslint-webpack-plugin": "^3.0.1",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-rate-limit": "^5.4.1",
|
"express-rate-limit": "^5.5.0",
|
||||||
"fast-json-patch": "^3.1.0",
|
"fast-json-patch": "^3.1.0",
|
||||||
"fast-sort": "^3.0.3",
|
"fast-sort": "^3.0.3",
|
||||||
"feedsub": "^0.7.7",
|
"feedsub": "^0.7.7",
|
||||||
@@ -160,15 +154,15 @@
|
|||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^10.0.0",
|
||||||
"get-user-locale": "^1.4.0",
|
"get-user-locale": "^1.4.0",
|
||||||
"hash-wasm": "^4.9.0",
|
"hash-wasm": "^4.9.0",
|
||||||
"html-webpack-plugin": "^5.3.2",
|
"html-webpack-plugin": "^5.4.0",
|
||||||
"http-errors": "^1.8.0",
|
"http-errors": "^1.8.0",
|
||||||
"jest": "^27.2.5",
|
"jest": "^27.3.1",
|
||||||
"js-file-download": "^0.4.12",
|
"js-file-download": "^0.4.12",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mini-css-extract-plugin": "^2.4.2",
|
"mini-css-extract-plugin": "^2.4.3",
|
||||||
"mobx": "^6.3.3",
|
"mobx": "^6.3.5",
|
||||||
"mobx-react": "^7.2.0",
|
"mobx-react": "^7.2.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"nedb-promises": "^5.0.1",
|
"nedb-promises": "^5.0.1",
|
||||||
"overlayscrollbars": "^1.13.1",
|
"overlayscrollbars": "^1.13.1",
|
||||||
@@ -177,8 +171,8 @@
|
|||||||
"passport": "^0.5.0",
|
"passport": "^0.5.0",
|
||||||
"passport-jwt": "^4.0.0",
|
"passport-jwt": "^4.0.0",
|
||||||
"polished": "^4.1.3",
|
"polished": "^4.1.3",
|
||||||
"postcss": "^8.3.9",
|
"postcss": "^8.3.11",
|
||||||
"postcss-loader": "^6.1.1",
|
"postcss-loader": "^6.2.0",
|
||||||
"prettier": "^2.4.1",
|
"prettier": "^2.4.1",
|
||||||
"promise": "^8.1.0",
|
"promise": "^8.1.0",
|
||||||
"query-string": "^6.14.1",
|
"query-string": "^6.14.1",
|
||||||
@@ -200,29 +194,27 @@
|
|||||||
"react-window": "^1.8.6",
|
"react-window": "^1.8.6",
|
||||||
"ress": "^4.0.0",
|
"ress": "^4.0.0",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sass": "^1.42.1",
|
"sass": "^1.43.3",
|
||||||
"sass-loader": "^12.1.0",
|
"sass-loader": "^12.2.0",
|
||||||
"saxen": "^8.1.2",
|
"saxen": "^8.1.2",
|
||||||
"source-map-loader": "^3.0.0",
|
"source-map-loader": "^3.0.0",
|
||||||
"spdy": "^4.0.2",
|
"spdy": "^4.0.2",
|
||||||
"style-loader": "^3.3.0",
|
"style-loader": "^3.3.1",
|
||||||
"supertest": "^6.1.6",
|
"supertest": "^6.1.6",
|
||||||
"tar-fs": "^2.1.1",
|
"tar-fs": "^2.1.1",
|
||||||
"tar-stream": "^2.2.0",
|
|
||||||
"terser-webpack-plugin": "^5.2.4",
|
"terser-webpack-plugin": "^5.2.4",
|
||||||
"tldts": "^5.7.47",
|
"tldts": "^5.7.49",
|
||||||
"ts-jest": "^27.0.5",
|
"ts-jest": "^27.0.7",
|
||||||
"ts-node-dev": "^1.1.8",
|
"ts-node-dev": "^1.1.8",
|
||||||
"tsconfig-paths": "^3.11.0",
|
"tsconfig-paths": "^3.11.0",
|
||||||
"typed-emitter": "^1.3.1",
|
"typed-emitter": "^1.4.0",
|
||||||
"typescript": "~4.4.3",
|
"typescript": "~4.4.4",
|
||||||
"url-loader": "^4.1.1",
|
|
||||||
"use-query-params": "^1.2.3",
|
"use-query-params": "^1.2.3",
|
||||||
"webpack": "^5.58.1",
|
"webpack": "^5.59.1",
|
||||||
"webpack-dev-server": "^4.3.1",
|
"webpack-dev-server": "^4.3.1",
|
||||||
"webpackbar": "^5.0.0-3",
|
"webpackbar": "^5.0.0-3",
|
||||||
"yargs": "^17.2.1",
|
"yargs": "^17.2.1",
|
||||||
"zod": "^3.9.8"
|
"zod": "^3.11.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0",
|
"node": ">=12.0.0",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {contentTokenSchema} from '@shared/schema/api/torrents';
|
|||||||
import type {FloodSettings} from '@shared/types/FloodSettings';
|
import type {FloodSettings} from '@shared/types/FloodSettings';
|
||||||
import type {HistorySnapshot} from '@shared/constants/historySnapshotTypes';
|
import type {HistorySnapshot} from '@shared/constants/historySnapshotTypes';
|
||||||
import type {NotificationFetchOptions, NotificationState} from '@shared/types/Notification';
|
import type {NotificationFetchOptions, NotificationState} from '@shared/types/Notification';
|
||||||
import type {SetFloodSettingsOptions} from '@shared/types/api/index';
|
import type {DirectoryListQuery, DirectoryListResponse, SetFloodSettingsOptions} from '@shared/types/api/index';
|
||||||
|
|
||||||
import {accessDeniedError, isAllowedPath, sanitizePath} from '../../util/fileUtil';
|
import {accessDeniedError, isAllowedPath, sanitizePath} from '../../util/fileUtil';
|
||||||
import appendUserServices from '../../middleware/appendUserServices';
|
import appendUserServices from '../../middleware/appendUserServices';
|
||||||
@@ -100,9 +100,9 @@ router.get('/activity-stream', eventStream, clientActivityStream);
|
|||||||
* @return {Error} 422 - invalid argument - application/json
|
* @return {Error} 422 - invalid argument - application/json
|
||||||
* @return {Error} 500 - other errors - application/json
|
* @return {Error} 500 - other errors - application/json
|
||||||
*/
|
*/
|
||||||
router.get<unknown, unknown, unknown, {path: string}>(
|
router.get<unknown, unknown, unknown, DirectoryListQuery>(
|
||||||
'/directory-list',
|
'/directory-list',
|
||||||
async (req, res): Promise<Response<unknown>> => {
|
async (req, res): Promise<Response<DirectoryListResponse>> => {
|
||||||
const {path: inputPath} = req.query;
|
const {path: inputPath} = req.query;
|
||||||
|
|
||||||
if (typeof inputPath !== 'string' || !inputPath) {
|
if (typeof inputPath !== 'string' || !inputPath) {
|
||||||
|
|||||||
@@ -907,13 +907,9 @@ router.get<{hash: string}>(
|
|||||||
'mediainfo',
|
'mediainfo',
|
||||||
torrentContentPaths,
|
torrentContentPaths,
|
||||||
{maxBuffer: 1024 * 2000, timeout: 1000 * 10},
|
{maxBuffer: 1024 * 2000, timeout: 1000 * 10},
|
||||||
(error, stdout, stderr) => {
|
(error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return res.status(500).json({message: error.message});
|
return res.status(500).json({code: error.code, message: error.message});
|
||||||
}
|
|
||||||
|
|
||||||
if (stderr) {
|
|
||||||
return res.status(500).json({message: stderr});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json({output: stdout});
|
return res.status(200).json({output: stdout});
|
||||||
|
|||||||
@@ -18,49 +18,50 @@ import {
|
|||||||
TransmissionTorrentsSetLocationArguments,
|
TransmissionTorrentsSetLocationArguments,
|
||||||
} from './types/TransmissionTorrentsMethods';
|
} from './types/TransmissionTorrentsMethods';
|
||||||
|
|
||||||
|
type TransmissionRPCResponse<T = undefined> = {
|
||||||
|
result: 'success';
|
||||||
|
arguments: T;
|
||||||
|
} & {
|
||||||
|
result: string;
|
||||||
|
};
|
||||||
|
|
||||||
class ClientRequestManager {
|
class ClientRequestManager {
|
||||||
private rpcURL: string;
|
private rpcURL: string;
|
||||||
private authHeader: string;
|
private authHeader: string;
|
||||||
private sessionID?: Promise<string | undefined>;
|
private sessionID?: Promise<string | undefined>;
|
||||||
|
|
||||||
async fetchSessionID(url = this.rpcURL, authHeader = this.authHeader): Promise<string | undefined> {
|
async fetchSessionID(url = this.rpcURL, authHeader = this.authHeader): Promise<string | undefined> {
|
||||||
return axios
|
let id: string | undefined = undefined;
|
||||||
|
|
||||||
|
await axios
|
||||||
.get(url, {
|
.get(url, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: authHeader,
|
Authorization: authHeader,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then<string | undefined>(
|
.catch((err: AxiosError) => {
|
||||||
() => {
|
if (err.response?.status !== 409) {
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
(err: AxiosError) => {
|
|
||||||
if (err.response?.status === 409) {
|
|
||||||
return err.response?.headers['x-transmission-session-id'];
|
|
||||||
}
|
|
||||||
throw err;
|
throw err;
|
||||||
},
|
}
|
||||||
);
|
id = err.response?.headers['x-transmission-session-id'];
|
||||||
|
});
|
||||||
|
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateSessionID(url = this.rpcURL, authHeader = this.authHeader): Promise<void> {
|
async updateSessionID(url = this.rpcURL, authHeader = this.authHeader): Promise<void> {
|
||||||
let authFailed = false;
|
let authFailed = false;
|
||||||
|
|
||||||
this.sessionID = new Promise((resolve) => {
|
this.sessionID = this.fetchSessionID(url, authHeader).catch(() => {
|
||||||
this.fetchSessionID(url, authHeader).then(
|
authFailed = true;
|
||||||
(sessionID) => {
|
return undefined;
|
||||||
resolve(sessionID);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
authFailed = true;
|
|
||||||
resolve(undefined);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.sessionID;
|
await this.sessionID;
|
||||||
|
|
||||||
return authFailed ? Promise.reject(new Error()) : Promise.resolve();
|
if (authFailed) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRequestHeaders(): Promise<Record<string, string>> {
|
async getRequestHeaders(): Promise<Record<string, string>> {
|
||||||
@@ -74,18 +75,18 @@ class ClientRequestManager {
|
|||||||
|
|
||||||
async getSessionStats(): Promise<TransmissionSessionStats> {
|
async getSessionStats(): Promise<TransmissionSessionStats> {
|
||||||
return axios
|
return axios
|
||||||
.post(
|
.post<TransmissionRPCResponse<TransmissionSessionStats>>(
|
||||||
this.rpcURL,
|
this.rpcURL,
|
||||||
{method: 'session-stats'},
|
{method: 'session-stats'},
|
||||||
{
|
{
|
||||||
headers: await this.getRequestHeaders(),
|
headers: await this.getRequestHeaders(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then(({data}) => {
|
||||||
if (res.data.result !== 'success') {
|
if (data.result !== 'success') {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
return res.data.arguments;
|
return data.arguments;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,32 +96,32 @@ class ClientRequestManager {
|
|||||||
const sessionGetArguments: TransmissionSessionGetArguments = {fields};
|
const sessionGetArguments: TransmissionSessionGetArguments = {fields};
|
||||||
|
|
||||||
return axios
|
return axios
|
||||||
.post(
|
.post<TransmissionRPCResponse<Pick<TransmissionSessionProperties, T[number]>>>(
|
||||||
this.rpcURL,
|
this.rpcURL,
|
||||||
{method: 'session-get', arguments: sessionGetArguments},
|
{method: 'session-get', arguments: sessionGetArguments},
|
||||||
{
|
{
|
||||||
headers: await this.getRequestHeaders(),
|
headers: await this.getRequestHeaders(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then(({data}) => {
|
||||||
if (res.data.result !== 'success') {
|
if (data.result !== 'success') {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
return res.data.arguments;
|
return data.arguments;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async setSessionProperties(properties: TransmissionSessionSetArguments): Promise<void> {
|
async setSessionProperties(properties: TransmissionSessionSetArguments): Promise<void> {
|
||||||
return axios
|
return axios
|
||||||
.post(
|
.post<TransmissionRPCResponse>(
|
||||||
this.rpcURL,
|
this.rpcURL,
|
||||||
{method: 'session-set', arguments: properties},
|
{method: 'session-set', arguments: properties},
|
||||||
{
|
{
|
||||||
headers: await this.getRequestHeaders(),
|
headers: await this.getRequestHeaders(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then(({data}) => {
|
||||||
if (res.data.result !== 'success') {
|
if (data.result !== 'success') {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -137,18 +138,18 @@ class ClientRequestManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return axios
|
return axios
|
||||||
.post(
|
.post<TransmissionRPCResponse<{torrents: Array<Pick<TransmissionTorrentProperties, T[number]>>}>>(
|
||||||
this.rpcURL,
|
this.rpcURL,
|
||||||
{method: 'torrent-get', arguments: torrentsGetArguments},
|
{method: 'torrent-get', arguments: torrentsGetArguments},
|
||||||
{
|
{
|
||||||
headers: await this.getRequestHeaders(),
|
headers: await this.getRequestHeaders(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then(({data}) => {
|
||||||
if (res.data.result !== 'success' || res.data.arguments.torrents == null) {
|
if (data.result !== 'success' || data.arguments.torrents == null) {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
return res.data.arguments.torrents;
|
return data.arguments.torrents;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,32 +157,34 @@ class ClientRequestManager {
|
|||||||
args: TransmissionTorrentAddArguments,
|
args: TransmissionTorrentAddArguments,
|
||||||
): Promise<Pick<TransmissionTorrentProperties, 'id' | 'name' | 'hashString'>> {
|
): Promise<Pick<TransmissionTorrentProperties, 'id' | 'name' | 'hashString'>> {
|
||||||
return axios
|
return axios
|
||||||
.post(
|
.post<
|
||||||
|
TransmissionRPCResponse<{'torrent-added'?: Pick<TransmissionTorrentProperties, 'id' | 'name' | 'hashString'>}>
|
||||||
|
>(
|
||||||
this.rpcURL,
|
this.rpcURL,
|
||||||
{method: 'torrent-add', arguments: args},
|
{method: 'torrent-add', arguments: args},
|
||||||
{
|
{
|
||||||
headers: await this.getRequestHeaders(),
|
headers: await this.getRequestHeaders(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then(({data}) => {
|
||||||
if (res.data.result !== 'success') {
|
if (data.result !== 'success' || !data.arguments['torrent-added']) {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
return res.data.arguments['torrent-added'];
|
return data.arguments['torrent-added'];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async reannounceTorrents(ids: TransmissionTorrentIDs): Promise<void> {
|
async reannounceTorrents(ids: TransmissionTorrentIDs): Promise<void> {
|
||||||
return axios
|
return axios
|
||||||
.post(
|
.post<TransmissionRPCResponse>(
|
||||||
this.rpcURL,
|
this.rpcURL,
|
||||||
{method: 'torrent-reannounce', arguments: {ids}},
|
{method: 'torrent-reannounce', arguments: {ids}},
|
||||||
{
|
{
|
||||||
headers: await this.getRequestHeaders(),
|
headers: await this.getRequestHeaders(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then(({data}) => {
|
||||||
if (res.data.result !== 'success') {
|
if (data.result !== 'success') {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -189,15 +192,15 @@ class ClientRequestManager {
|
|||||||
|
|
||||||
async setTorrentsProperties(args: TransmissionTorrentsSetArguments): Promise<void> {
|
async setTorrentsProperties(args: TransmissionTorrentsSetArguments): Promise<void> {
|
||||||
return axios
|
return axios
|
||||||
.post(
|
.post<TransmissionRPCResponse>(
|
||||||
this.rpcURL,
|
this.rpcURL,
|
||||||
{method: 'torrent-set', arguments: args},
|
{method: 'torrent-set', arguments: args},
|
||||||
{
|
{
|
||||||
headers: await this.getRequestHeaders(),
|
headers: await this.getRequestHeaders(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then(({data}) => {
|
||||||
if (res.data.result !== 'success') {
|
if (data.result !== 'success') {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -205,15 +208,15 @@ class ClientRequestManager {
|
|||||||
|
|
||||||
async startTorrents(ids: TransmissionTorrentIDs): Promise<void> {
|
async startTorrents(ids: TransmissionTorrentIDs): Promise<void> {
|
||||||
return axios
|
return axios
|
||||||
.post(
|
.post<TransmissionRPCResponse>(
|
||||||
this.rpcURL,
|
this.rpcURL,
|
||||||
{method: 'torrent-start-now', arguments: {ids}},
|
{method: 'torrent-start-now', arguments: {ids}},
|
||||||
{
|
{
|
||||||
headers: await this.getRequestHeaders(),
|
headers: await this.getRequestHeaders(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then(({data}) => {
|
||||||
if (res.data.result !== 'success') {
|
if (data.result !== 'success') {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -221,15 +224,15 @@ class ClientRequestManager {
|
|||||||
|
|
||||||
async stopTorrents(ids: TransmissionTorrentIDs): Promise<void> {
|
async stopTorrents(ids: TransmissionTorrentIDs): Promise<void> {
|
||||||
return axios
|
return axios
|
||||||
.post(
|
.post<TransmissionRPCResponse>(
|
||||||
this.rpcURL,
|
this.rpcURL,
|
||||||
{method: 'torrent-stop', arguments: {ids}},
|
{method: 'torrent-stop', arguments: {ids}},
|
||||||
{
|
{
|
||||||
headers: await this.getRequestHeaders(),
|
headers: await this.getRequestHeaders(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then(({data}) => {
|
||||||
if (res.data.result !== 'success') {
|
if (data.result !== 'success') {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -237,15 +240,15 @@ class ClientRequestManager {
|
|||||||
|
|
||||||
async verifyTorrents(ids: TransmissionTorrentIDs): Promise<void> {
|
async verifyTorrents(ids: TransmissionTorrentIDs): Promise<void> {
|
||||||
return axios
|
return axios
|
||||||
.post(
|
.post<TransmissionRPCResponse>(
|
||||||
this.rpcURL,
|
this.rpcURL,
|
||||||
{method: 'torrent-verify', arguments: {ids}},
|
{method: 'torrent-verify', arguments: {ids}},
|
||||||
{
|
{
|
||||||
headers: await this.getRequestHeaders(),
|
headers: await this.getRequestHeaders(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then(({data}) => {
|
||||||
if (res.data.result !== 'success') {
|
if (data.result !== 'success') {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -258,15 +261,15 @@ class ClientRequestManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return axios
|
return axios
|
||||||
.post(
|
.post<TransmissionRPCResponse>(
|
||||||
this.rpcURL,
|
this.rpcURL,
|
||||||
{method: 'torrent-remove', arguments: removeTorrentsArguments},
|
{method: 'torrent-remove', arguments: removeTorrentsArguments},
|
||||||
{
|
{
|
||||||
headers: await this.getRequestHeaders(),
|
headers: await this.getRequestHeaders(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then(({data}) => {
|
||||||
if (res.data.result !== 'success') {
|
if (data.result !== 'success') {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -280,7 +283,7 @@ class ClientRequestManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return axios
|
return axios
|
||||||
.post(
|
.post<TransmissionRPCResponse>(
|
||||||
this.rpcURL,
|
this.rpcURL,
|
||||||
{
|
{
|
||||||
method: 'torrent-set-location',
|
method: 'torrent-set-location',
|
||||||
@@ -290,8 +293,8 @@ class ClientRequestManager {
|
|||||||
headers: await this.getRequestHeaders(),
|
headers: await this.getRequestHeaders(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then(({data}) => {
|
||||||
if (res.data.result !== 'success') {
|
if (data.result !== 'success') {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const EMPTY_SERVER_STATE = {
|
|||||||
class ClientRequestManager {
|
class ClientRequestManager {
|
||||||
private connectionSettings: QBittorrentConnectionSettings;
|
private connectionSettings: QBittorrentConnectionSettings;
|
||||||
private apiBase: string;
|
private apiBase: string;
|
||||||
private authCookie?: Promise<string | undefined>;
|
private authCookie: Promise<string | undefined> = Promise.resolve(undefined);
|
||||||
private isMainDataPending = false;
|
private isMainDataPending = false;
|
||||||
|
|
||||||
private syncRids: {
|
private syncRids: {
|
||||||
@@ -66,7 +66,7 @@ class ClientRequestManager {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const cookies: Array<string> = res.headers['set-cookie'];
|
const cookies = res.headers['set-cookie'];
|
||||||
|
|
||||||
if (Array.isArray(cookies)) {
|
if (Array.isArray(cookies)) {
|
||||||
return cookies.filter((cookie) => cookie.includes('SID='))[0];
|
return cookies.filter((cookie) => cookie.includes('SID='))[0];
|
||||||
@@ -79,35 +79,37 @@ class ClientRequestManager {
|
|||||||
async updateAuthCookie(connectionSettings?: QBittorrentConnectionSettings): Promise<void> {
|
async updateAuthCookie(connectionSettings?: QBittorrentConnectionSettings): Promise<void> {
|
||||||
let authFailed = false;
|
let authFailed = false;
|
||||||
|
|
||||||
this.authCookie = new Promise((resolve) => {
|
this.authCookie = this.authenticate(connectionSettings).catch(() => {
|
||||||
this.authenticate(connectionSettings).then(
|
authFailed = true;
|
||||||
(authCookie) => {
|
return undefined;
|
||||||
resolve(authCookie);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
authFailed = true;
|
|
||||||
resolve(undefined);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.authCookie;
|
await this.authCookie;
|
||||||
|
|
||||||
return authFailed ? Promise.reject(new Error()) : Promise.resolve();
|
if (authFailed) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRequestHeaders(): Promise<Record<string, string>> {
|
||||||
|
const Cookie = await this.authCookie;
|
||||||
|
return {
|
||||||
|
...(Cookie == null ? {} : {Cookie}),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAppPreferences(): Promise<QBittorrentAppPreferences> {
|
async getAppPreferences(): Promise<QBittorrentAppPreferences> {
|
||||||
return axios
|
return axios
|
||||||
.get(`${this.apiBase}/app/preferences`, {
|
.get<QBittorrentAppPreferences>(`${this.apiBase}/app/preferences`, {
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then((json) => json.data);
|
.then((res) => res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAppPreferences(preferences: Partial<QBittorrentAppPreferences>): Promise<void> {
|
async setAppPreferences(preferences: Partial<QBittorrentAppPreferences>): Promise<void> {
|
||||||
return axios
|
return axios
|
||||||
.post(`${this.apiBase}/app/setPreferences`, `json=${JSON.stringify(preferences)}`, {
|
.post(`${this.apiBase}/app/setPreferences`, `json=${JSON.stringify(preferences)}`, {
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
@@ -116,55 +118,55 @@ class ClientRequestManager {
|
|||||||
|
|
||||||
async getTorrentInfos(): Promise<QBittorrentTorrentInfos> {
|
async getTorrentInfos(): Promise<QBittorrentTorrentInfos> {
|
||||||
return axios
|
return axios
|
||||||
.get(`${this.apiBase}/torrents/info`, {
|
.get<QBittorrentTorrentInfos>(`${this.apiBase}/torrents/info`, {
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then((json) => json.data);
|
.then((res) => res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTorrentContents(hash: string): Promise<QBittorrentTorrentContents> {
|
async getTorrentContents(hash: string): Promise<QBittorrentTorrentContents> {
|
||||||
return axios
|
return axios
|
||||||
.get(`${this.apiBase}/torrents/files`, {
|
.get<QBittorrentTorrentContents>(`${this.apiBase}/torrents/files`, {
|
||||||
params: {
|
params: {
|
||||||
hash: hash.toLowerCase(),
|
hash: hash.toLowerCase(),
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then((json) => json.data);
|
.then((res) => res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTorrentProperties(hash: string): Promise<QBittorrentTorrentProperties> {
|
async getTorrentProperties(hash: string): Promise<QBittorrentTorrentProperties> {
|
||||||
return axios
|
return axios
|
||||||
.get(`${this.apiBase}/torrents/properties`, {
|
.get<QBittorrentTorrentProperties>(`${this.apiBase}/torrents/properties`, {
|
||||||
params: {
|
params: {
|
||||||
hash: hash.toLowerCase(),
|
hash: hash.toLowerCase(),
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then((json) => json.data);
|
.then((res) => res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTorrentTrackers(hash: string): Promise<QBittorrentTorrentTrackers> {
|
async getTorrentTrackers(hash: string): Promise<QBittorrentTorrentTrackers> {
|
||||||
return axios
|
return axios
|
||||||
.get(`${this.apiBase}/torrents/trackers`, {
|
.get<QBittorrentTorrentTrackers>(`${this.apiBase}/torrents/trackers`, {
|
||||||
params: {
|
params: {
|
||||||
hash: hash.toLowerCase(),
|
hash: hash.toLowerCase(),
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then((json) => json.data);
|
.then((res) => res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTransferInfo(): Promise<QBittorrentTransferInfo> {
|
async getTransferInfo(): Promise<QBittorrentTransferInfo> {
|
||||||
return axios
|
return axios
|
||||||
.get(`${this.apiBase}/transfer/info`, {
|
.get<QBittorrentTransferInfo>(`${this.apiBase}/transfer/info`, {
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then((json) => json.data);
|
.then((res) => res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async syncMainData(): Promise<QBittorrentMainData> {
|
async syncMainData(): Promise<QBittorrentMainData> {
|
||||||
const Cookie = await this.authCookie;
|
const headers = await this.getRequestHeaders();
|
||||||
|
|
||||||
if (this.isMainDataPending == false) {
|
if (this.isMainDataPending == false) {
|
||||||
this.isMainDataPending = true;
|
this.isMainDataPending = true;
|
||||||
@@ -174,7 +176,7 @@ class ClientRequestManager {
|
|||||||
params: {
|
params: {
|
||||||
rid,
|
rid,
|
||||||
},
|
},
|
||||||
headers: {Cookie},
|
headers,
|
||||||
})
|
})
|
||||||
.then(({data}) => {
|
.then(({data}) => {
|
||||||
const {
|
const {
|
||||||
@@ -268,9 +270,9 @@ class ClientRequestManager {
|
|||||||
hash: hash.toLowerCase(),
|
hash: hash.toLowerCase(),
|
||||||
rid: 0,
|
rid: 0,
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(({data}) => data.peers as QBittorrentTorrentPeers);
|
.then(({data}) => data.peers);
|
||||||
}
|
}
|
||||||
|
|
||||||
async torrentsPause(hashes: Array<string>): Promise<void> {
|
async torrentsPause(hashes: Array<string>): Promise<void> {
|
||||||
@@ -279,7 +281,7 @@ class ClientRequestManager {
|
|||||||
params: {
|
params: {
|
||||||
hashes: hashes.join('|').toLowerCase(),
|
hashes: hashes.join('|').toLowerCase(),
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
@@ -292,7 +294,7 @@ class ClientRequestManager {
|
|||||||
params: {
|
params: {
|
||||||
hashes: hashes.join('|').toLowerCase(),
|
hashes: hashes.join('|').toLowerCase(),
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
@@ -306,7 +308,7 @@ class ClientRequestManager {
|
|||||||
hashes: hashes.join('|').toLowerCase(),
|
hashes: hashes.join('|').toLowerCase(),
|
||||||
deleteFiles: deleteFiles ? 'true' : 'false',
|
deleteFiles: deleteFiles ? 'true' : 'false',
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
@@ -319,7 +321,7 @@ class ClientRequestManager {
|
|||||||
params: {
|
params: {
|
||||||
hashes: hashes.join('|').toLowerCase(),
|
hashes: hashes.join('|').toLowerCase(),
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
@@ -333,7 +335,7 @@ class ClientRequestManager {
|
|||||||
hashes: hashes.join('|').toLowerCase(),
|
hashes: hashes.join('|').toLowerCase(),
|
||||||
location,
|
location,
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
@@ -346,7 +348,7 @@ class ClientRequestManager {
|
|||||||
params: {
|
params: {
|
||||||
hashes: hashes.join('|').toLowerCase(),
|
hashes: hashes.join('|').toLowerCase(),
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
@@ -359,7 +361,7 @@ class ClientRequestManager {
|
|||||||
params: {
|
params: {
|
||||||
hashes: hashes.join('|').toLowerCase(),
|
hashes: hashes.join('|').toLowerCase(),
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
@@ -426,7 +428,7 @@ class ClientRequestManager {
|
|||||||
hashes: hashes.join('|').toLowerCase(),
|
hashes: hashes.join('|').toLowerCase(),
|
||||||
tags: tags.join(','),
|
tags: tags.join(','),
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
@@ -440,7 +442,7 @@ class ClientRequestManager {
|
|||||||
hashes: hashes.join('|').toLowerCase(),
|
hashes: hashes.join('|').toLowerCase(),
|
||||||
tags: tags?.join(','),
|
tags: tags?.join(','),
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
@@ -455,7 +457,7 @@ class ClientRequestManager {
|
|||||||
hash: hash.toLowerCase(),
|
hash: hash.toLowerCase(),
|
||||||
urls: urls.join('\n'),
|
urls: urls.join('\n'),
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
@@ -470,7 +472,7 @@ class ClientRequestManager {
|
|||||||
params: {
|
params: {
|
||||||
hashes: hashes.join('|').toLowerCase(),
|
hashes: hashes.join('|').toLowerCase(),
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
@@ -486,7 +488,7 @@ class ClientRequestManager {
|
|||||||
hash: hash.toLowerCase(),
|
hash: hash.toLowerCase(),
|
||||||
urls: urls.join('|'),
|
urls: urls.join('|'),
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
@@ -502,7 +504,7 @@ class ClientRequestManager {
|
|||||||
hashes: hashes.join('|').toLowerCase(),
|
hashes: hashes.join('|').toLowerCase(),
|
||||||
value: value ? 'true' : 'false',
|
value: value ? 'true' : 'false',
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
@@ -517,7 +519,7 @@ class ClientRequestManager {
|
|||||||
params: {
|
params: {
|
||||||
hashes: hashes.join('|').toLowerCase(),
|
hashes: hashes.join('|').toLowerCase(),
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
@@ -533,7 +535,7 @@ class ClientRequestManager {
|
|||||||
id: ids.join('|'),
|
id: ids.join('|'),
|
||||||
priority,
|
priority,
|
||||||
},
|
},
|
||||||
headers: {Cookie: await this.authCookie},
|
headers: await this.getRequestHeaders(),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// returns nothing
|
// returns nothing
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export type QBittorrentMainData = Required<
|
|||||||
|
|
||||||
export interface QBittorrentSyncTorrentPeers {
|
export interface QBittorrentSyncTorrentPeers {
|
||||||
rid: number;
|
rid: number;
|
||||||
peers?: {
|
peers: {
|
||||||
[ip_and_port: string]: QBittorrentTorrentPeer;
|
[ip_and_port: string]: QBittorrentTorrentPeer;
|
||||||
};
|
};
|
||||||
peers_removed?: string[];
|
peers_removed?: string[];
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import axios, {AxiosError, AxiosResponse} from 'axios';
|
import axios, {AxiosError} from 'axios';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ export const fetchUrls = async (
|
|||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
}).then(
|
}).then(
|
||||||
(res: AxiosResponse) => res.data,
|
(res) => res.data,
|
||||||
(e: AxiosError) => console.error(e),
|
(e: AxiosError) => console.error(e),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ export interface AuthAuthenticationResponse {
|
|||||||
export const authRegistrationSchema = credentialsSchema;
|
export const authRegistrationSchema = credentialsSchema;
|
||||||
export type AuthRegistrationOptions = Required<zodInfer<typeof authRegistrationSchema>>;
|
export type AuthRegistrationOptions = Required<zodInfer<typeof authRegistrationSchema>>;
|
||||||
|
|
||||||
|
// POST /api/auth/register - success response
|
||||||
|
export interface AuthRegistrationResponse {
|
||||||
|
username: string;
|
||||||
|
level: AccessLevel;
|
||||||
|
}
|
||||||
|
|
||||||
// PATCH /api/auth/users/{username}
|
// PATCH /api/auth/users/{username}
|
||||||
export const authUpdateUserSchema = credentialsSchema.partial();
|
export const authUpdateUserSchema = credentialsSchema.partial();
|
||||||
export type AuthUpdateUserOptions = zodInfer<typeof authUpdateUserSchema>;
|
export type AuthUpdateUserOptions = zodInfer<typeof authUpdateUserSchema>;
|
||||||
|
|||||||
@@ -2,3 +2,16 @@ import type {FloodSettings} from '../FloodSettings';
|
|||||||
|
|
||||||
// PATCH /api/settings
|
// PATCH /api/settings
|
||||||
export type SetFloodSettingsOptions = Partial<FloodSettings>;
|
export type SetFloodSettingsOptions = Partial<FloodSettings>;
|
||||||
|
|
||||||
|
// GET /api/directory-list
|
||||||
|
export interface DirectoryListQuery {
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /api/directory-list - success response
|
||||||
|
export interface DirectoryListResponse {
|
||||||
|
path: string;
|
||||||
|
separator: string;
|
||||||
|
directories: string[];
|
||||||
|
files: string[];
|
||||||
|
}
|
||||||
|
|||||||
@@ -104,3 +104,13 @@ export interface SetTorrentContentsPropertiesOptions {
|
|||||||
// Number representing priority
|
// Number representing priority
|
||||||
priority: TorrentContentPriority;
|
priority: TorrentContentPriority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET /api/torrents/{hash}/mediainfo - success response
|
||||||
|
export interface TorrentMediainfoResponse {
|
||||||
|
output: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /api/torrents/add-urls - success response
|
||||||
|
// POST /api/torrents/add-files - success response
|
||||||
|
// hashes of torrents added
|
||||||
|
export type TorrentAddResponse = string[];
|
||||||
|
|||||||
Reference in New Issue
Block a user