From ba84212e68dc9e68769684c0c3e3ece3bd645b40 Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 13 Nov 2024 02:54:18 +0000 Subject: [PATCH 01/41] docs: added missing sub_filters in nginx subpath (#1052) * Update reverse-proxy.mdx Fix posters not showing up when using sub-folder (at least with NPM) * Update docs/extending-jellyseerr/reverse-proxy.mdx Added `avatarproxy` support Co-authored-by: Gauthier --------- Co-authored-by: Gauthier --- docs/extending-jellyseerr/reverse-proxy.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/extending-jellyseerr/reverse-proxy.mdx b/docs/extending-jellyseerr/reverse-proxy.mdx index c78ae915..505389ac 100644 --- a/docs/extending-jellyseerr/reverse-proxy.mdx +++ b/docs/extending-jellyseerr/reverse-proxy.mdx @@ -95,6 +95,8 @@ location ^~ /jellyseerr { sub_filter '/api/v1' '/$app/api/v1'; sub_filter '/login/plex/loading' '/$app/login/plex/loading'; sub_filter '/images/' '/$app/images/'; + sub_filter '/imageproxy/' '/$app/imageproxy/'; + sub_filter '/avatarproxy/' '/$app/avatarproxy/'; sub_filter '/android-' '/$app/android-'; sub_filter '/apple-' '/$app/apple-'; sub_filter '/favicon' '/$app/favicon'; From 5c24e79b1dddc3c8421e57e67302fa3dc064f87f Mon Sep 17 00:00:00 2001 From: Guillaume ARNOUX Date: Fri, 15 Nov 2024 18:38:23 +0100 Subject: [PATCH 02/41] feat(notifications): improve discord notifications (#1102) * feat: improve discord notifications Added a field in the general notification settings to allow a role to be mentioned in the webhook message via discord notification agent * feat: add discord role id notification - locales --- cypress/config/settings.cypress.json | 1 + .../using-jellyseerr/notifications/discord.md | 4 +++ overseerr-api.yml | 2 ++ server/lib/notifications/agents/discord.ts | 4 +++ server/lib/settings/index.ts | 2 ++ .../Notifications/NotificationsDiscord.tsx | 28 +++++++++++++++++++ src/i18n/locale/en.json | 5 +++- 7 files changed, 45 insertions(+), 1 deletion(-) diff --git a/cypress/config/settings.cypress.json b/cypress/config/settings.cypress.json index 45e38a29..ff763ac7 100644 --- a/cypress/config/settings.cypress.json +++ b/cypress/config/settings.cypress.json @@ -75,6 +75,7 @@ "types": 0, "options": { "webhookUrl": "", + "webhookRoleId": "", "enableMentions": true } }, diff --git a/docs/using-jellyseerr/notifications/discord.md b/docs/using-jellyseerr/notifications/discord.md index 016de30e..b39e283a 100644 --- a/docs/using-jellyseerr/notifications/discord.md +++ b/docs/using-jellyseerr/notifications/discord.md @@ -18,6 +18,10 @@ Users can optionally opt-in to being mentioned in Discord notifications by confi You can find the webhook URL in the Discord application, at **Server Settings → Integrations → Webhooks**. +### Notification Role ID (optional) + +If a role ID is specified, it will be included in the webhook message. See [Discord role ID](https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID). + ### Bot Username (optional) If you would like to override the name you configured for your bot in Discord, you may set this value to whatever you like! diff --git a/overseerr-api.yml b/overseerr-api.yml index 9e2505f4..dfbbfd08 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -1273,6 +1273,8 @@ components: type: string webhookUrl: type: string + webhookRoleId: + type: string enableMentions: type: boolean SlackSettings: diff --git a/server/lib/notifications/agents/discord.ts b/server/lib/notifications/agents/discord.ts index e949e3e1..8eb1d99d 100644 --- a/server/lib/notifications/agents/discord.ts +++ b/server/lib/notifications/agents/discord.ts @@ -291,6 +291,10 @@ class DiscordAgent } } + if (settings.options.webhookRoleId) { + userMentions.push(`<@&${settings.options.webhookRoleId}>`); + } + const response = await fetch(settings.options.webhookUrl, { method: 'POST', headers: { diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 29447f53..425fc138 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -170,6 +170,7 @@ export interface NotificationAgentDiscord extends NotificationAgentConfig { botUsername?: string; botAvatarUrl?: string; webhookUrl: string; + webhookRoleId?: string; enableMentions: boolean; }; } @@ -394,6 +395,7 @@ class Settings { types: 0, options: { webhookUrl: '', + webhookRoleId: '', enableMentions: true, }, }, diff --git a/src/components/Settings/Notifications/NotificationsDiscord.tsx b/src/components/Settings/Notifications/NotificationsDiscord.tsx index b62263fb..82ac6840 100644 --- a/src/components/Settings/Notifications/NotificationsDiscord.tsx +++ b/src/components/Settings/Notifications/NotificationsDiscord.tsx @@ -19,12 +19,16 @@ const messages = defineMessages('components.Settings.Notifications', { webhookUrl: 'Webhook URL', webhookUrlTip: 'Create a webhook integration in your server', + webhookRoleId: 'Notification Role ID', + webhookRoleIdTip: + 'The role ID to mention in the webhook message. Leave empty to disable mentions', discordsettingssaved: 'Discord notification settings saved successfully!', discordsettingsfailed: 'Discord notification settings failed to save.', toastDiscordTestSending: 'Sending Discord test notification…', toastDiscordTestSuccess: 'Discord test notification sent!', toastDiscordTestFailed: 'Discord test notification failed to send.', validationUrl: 'You must provide a valid URL', + validationWebhookRoleId: 'You must provide a valid Discord Role ID', validationTypes: 'You must select at least one notification type', enableMentions: 'Enable Mentions', }); @@ -53,6 +57,12 @@ const NotificationsDiscord = () => { otherwise: Yup.string().nullable(), }) .url(intl.formatMessage(messages.validationUrl)), + webhookRoleId: Yup.string() + .nullable() + .matches( + /^\d{17,19}$/, + intl.formatMessage(messages.validationWebhookRoleId) + ), }); if (!data && !error) { @@ -67,6 +77,7 @@ const NotificationsDiscord = () => { botUsername: data?.options.botUsername, botAvatarUrl: data?.options.botAvatarUrl, webhookUrl: data.options.webhookUrl, + webhookRoleId: data?.options.webhookRoleId, enableMentions: data?.options.enableMentions, }} validationSchema={NotificationsDiscordSchema} @@ -84,6 +95,7 @@ const NotificationsDiscord = () => { botUsername: values.botUsername, botAvatarUrl: values.botAvatarUrl, webhookUrl: values.webhookUrl, + webhookRoleId: values.webhookRoleId, enableMentions: values.enableMentions, }, }), @@ -141,6 +153,7 @@ const NotificationsDiscord = () => { botUsername: values.botUsername, botAvatarUrl: values.botAvatarUrl, webhookUrl: values.webhookUrl, + webhookRoleId: values.webhookRoleId, enableMentions: values.enableMentions, }, }), @@ -254,6 +267,21 @@ const NotificationsDiscord = () => { )} +
+ +
+
+ +
+ {errors.webhookRoleId && + touched.webhookRoleId && + typeof errors.webhookRoleId === 'string' && ( +
{errors.webhookRoleId}
+ )} +
+
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 272c96bf..599106e9 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -503,6 +503,7 @@ "components.RequestList.requests": "Requests", "components.RequestList.showallrequests": "Show All Requests", "components.RequestList.sortAdded": "Most Recent", + "components.RequestList.sortDirection": "Toggle Sort Direction", "components.RequestList.sortModified": "Last Modified", "components.RequestModal.AdvancedRequester.advancedoptions": "Advanced", "components.RequestModal.AdvancedRequester.animenote": "* This series is an anime.", @@ -1099,7 +1100,7 @@ "components.Setup.finishing": "Finishing…", "components.Setup.servertype": "Choose Server Type", "components.Setup.setup": "Setup", - "components.Setup.signin": "Sign in to your account", + "components.Setup.signin": "Sign In", "components.Setup.signinMessage": "Get started by signing in", "components.Setup.signinWithEmby": "Enter your Emby details", "components.Setup.signinWithJellyfin": "Enter your Jellyfin details", From 347a24a97b354725c4ccb3b5a07793b96ff60b80 Mon Sep 17 00:00:00 2001 From: Ben Haney <31331498+benhaney@users.noreply.github.com> Date: Fri, 20 Dec 2024 04:23:14 -0600 Subject: [PATCH 27/41] fix: handle non-existent rottentomatoes rating for movies (#1169) This fixes a bug where some movies don't have any rottentomatoes ratings, which causes ratings from other services to not show --- server/api/rating/rottentomatoes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/api/rating/rottentomatoes.ts b/server/api/rating/rottentomatoes.ts index f4fbe12b..170cbb64 100644 --- a/server/api/rating/rottentomatoes.ts +++ b/server/api/rating/rottentomatoes.ts @@ -128,7 +128,7 @@ class RottenTomatoes extends ExternalAPI { movie = contentResults.hits.find((movie) => movie.title === name); } - if (!movie) { + if (!movie?.rottenTomatoes) { return null; } From 1da2f258a70e15aae33eff4112edcbd6ab776989 Mon Sep 17 00:00:00 2001 From: GkhnGRBZ <127258824+GkhnGRBZ@users.noreply.github.com> Date: Sat, 21 Dec 2024 00:37:46 +0300 Subject: [PATCH 28/41] Turkish language added (#1165) * Add files via upload * Add files via upload --- src/context/LanguageContext.tsx | 5 +++++ src/pages/_app.tsx | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/context/LanguageContext.tsx b/src/context/LanguageContext.tsx index 10a658db..cb4338aa 100644 --- a/src/context/LanguageContext.tsx +++ b/src/context/LanguageContext.tsx @@ -31,6 +31,7 @@ export type AvailableLocale = | 'sq' | 'sr' | 'sv' + | 'tr' | 'uk' | 'zh-CN' | 'zh-TW'; @@ -149,6 +150,10 @@ export const availableLanguages: AvailableLanguageObject = { code: 'sr', display: 'српски језик', }, + tr: { + code: 'tr', + display: 'Türkçe', + }, ar: { code: 'ar', display: 'العربية', diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index d12b9191..d0fbbfa9 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -85,6 +85,8 @@ const loadLocaleData = (locale: AvailableLocale): Promise => { return import('../i18n/locale/sr.json'); case 'sv': return import('../i18n/locale/sv.json'); + case 'tr': + return import('../i18n/locale/tr.json'); case 'uk': return import('../i18n/locale/uk.json'); case 'zh-CN': From d76d79441142ccc6fe2357549f39a1fba3546ff9 Mon Sep 17 00:00:00 2001 From: astro <18621898+bytebone@users.noreply.github.com> Date: Sat, 21 Dec 2024 07:54:55 +0100 Subject: [PATCH 29/41] feat(notifications): added telegram thread id's (#1145) * feat(notifications): added telegram thread id's * undid unwanted formatting * chore: remove manual translations * style: conformed formatting * fix: add missing migration * fix: corrected erroneous migration --- cypress/config/settings.cypress.json | 1 + overseerr-api.yml | 5 +++ server/entity/UserSettings.ts | 3 ++ .../interfaces/api/userSettingsInterfaces.ts | 1 + server/lib/notifications/agents/telegram.ts | 6 +++ server/lib/settings/index.ts | 2 + ...734287582736-AddTelegramMessageThreadId.ts | 33 ++++++++++++++ server/routes/user/usersettings.ts | 5 +++ .../Notifications/NotificationsTelegram.tsx | 39 ++++++++++++++++ .../UserNotificationsTelegram.tsx | 45 +++++++++++++++++++ src/i18n/locale/en.json | 6 +++ 11 files changed, 146 insertions(+) create mode 100644 server/migration/1734287582736-AddTelegramMessageThreadId.ts diff --git a/cypress/config/settings.cypress.json b/cypress/config/settings.cypress.json index 69c8db42..f45bcbc0 100644 --- a/cypress/config/settings.cypress.json +++ b/cypress/config/settings.cypress.json @@ -100,6 +100,7 @@ "options": { "botAPI": "", "chatId": "", + "messageThreadId": "", "sendSilently": false } }, diff --git a/overseerr-api.yml b/overseerr-api.yml index dc59b7af..ac76f6a7 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -1338,6 +1338,8 @@ components: type: string chatId: type: string + messageThreadId: + type: string sendSilently: type: boolean PushbulletSettings: @@ -1821,6 +1823,9 @@ components: telegramChatId: type: string nullable: true + telegramMessageThreadId: + type: string + nullable: true telegramSendSilently: type: boolean nullable: true diff --git a/server/entity/UserSettings.ts b/server/entity/UserSettings.ts index d5a7555a..82671fe3 100644 --- a/server/entity/UserSettings.ts +++ b/server/entity/UserSettings.ts @@ -60,6 +60,9 @@ export class UserSettings { @Column({ nullable: true }) public telegramChatId?: string; + @Column({ nullable: true }) + public telegramMessageThreadId?: string; + @Column({ nullable: true }) public telegramSendSilently?: boolean; diff --git a/server/interfaces/api/userSettingsInterfaces.ts b/server/interfaces/api/userSettingsInterfaces.ts index 43c567c7..32776461 100644 --- a/server/interfaces/api/userSettingsInterfaces.ts +++ b/server/interfaces/api/userSettingsInterfaces.ts @@ -34,6 +34,7 @@ export interface UserSettingsNotificationsResponse { telegramEnabled?: boolean; telegramBotUsername?: string; telegramChatId?: string; + telegramMessageThreadId?: string; telegramSendSilently?: boolean; webPushEnabled?: boolean; notificationTypes: Partial; diff --git a/server/lib/notifications/agents/telegram.ts b/server/lib/notifications/agents/telegram.ts index a66f9710..db12b494 100644 --- a/server/lib/notifications/agents/telegram.ts +++ b/server/lib/notifications/agents/telegram.ts @@ -17,6 +17,7 @@ interface TelegramMessagePayload { text: string; parse_mode: string; chat_id: string; + message_thread_id: string; disable_notification: boolean; } @@ -25,6 +26,7 @@ interface TelegramPhotoPayload { caption: string; parse_mode: string; chat_id: string; + message_thread_id: string; disable_notification: boolean; } @@ -182,6 +184,7 @@ class TelegramAgent body: JSON.stringify({ ...notificationPayload, chat_id: settings.options.chatId, + message_thread_id: settings.options.messageThreadId, disable_notification: !!settings.options.sendSilently, } as TelegramMessagePayload | TelegramPhotoPayload), }); @@ -233,6 +236,8 @@ class TelegramAgent body: JSON.stringify({ ...notificationPayload, chat_id: payload.notifyUser.settings.telegramChatId, + message_thread_id: + payload.notifyUser.settings.telegramMessageThreadId, disable_notification: !!payload.notifyUser.settings.telegramSendSilently, } as TelegramMessagePayload | TelegramPhotoPayload), @@ -296,6 +301,7 @@ class TelegramAgent body: JSON.stringify({ ...notificationPayload, chat_id: user.settings.telegramChatId, + message_thread_id: user.settings.telegramMessageThreadId, disable_notification: !!user.settings?.telegramSendSilently, } as TelegramMessagePayload | TelegramPhotoPayload), }); diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index f14e0eb6..f1c73022 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -213,6 +213,7 @@ export interface NotificationAgentTelegram extends NotificationAgentConfig { botUsername?: string; botAPI: string; chatId: string; + messageThreadId: string; sendSilently: boolean; }; } @@ -423,6 +424,7 @@ class Settings { options: { botAPI: '', chatId: '', + messageThreadId: '', sendSilently: false, }, }, diff --git a/server/migration/1734287582736-AddTelegramMessageThreadId.ts b/server/migration/1734287582736-AddTelegramMessageThreadId.ts new file mode 100644 index 00000000..94a76b99 --- /dev/null +++ b/server/migration/1734287582736-AddTelegramMessageThreadId.ts @@ -0,0 +1,33 @@ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTelegramMessageThreadId1734287582736 + implements MigrationInterface +{ + name = 'AddTelegramMessageThreadId1734287582736'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "temporary_user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "locale" varchar NOT NULL DEFAULT (''), "discoverRegion" varchar, "streamingRegion" varchar, "originalLanguage" varchar, "pgpKey" varchar, "discordId" varchar, "pushbulletAccessToken" varchar, "pushoverApplicationToken" varchar, "pushoverUserKey" varchar, "pushoverSound" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "watchlistSyncMovies" boolean, "watchlistSyncTv" boolean, "notificationTypes" text, "userId" integer, "telegramMessageThreadId" varchar, CONSTRAINT "REL_986a2b6d3c05eb4091bb8066f7" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "temporary_user_settings"("id", "locale", "discoverRegion", "streamingRegion", "originalLanguage", "pgpKey", "discordId", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "pushoverSound", "telegramChatId", "telegramSendSilently", "watchlistSyncMovies", "watchlistSyncTv", "notificationTypes", "userId") SELECT "id", "locale", "discoverRegion", "streamingRegion", "originalLanguage", "pgpKey", "discordId", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "pushoverSound", "telegramChatId", "telegramSendSilently", "watchlistSyncMovies", "watchlistSyncTv", "notificationTypes", "userId" FROM "user_settings"` + ); + await queryRunner.query(`DROP TABLE "user_settings"`); + await queryRunner.query( + `ALTER TABLE "temporary_user_settings" RENAME TO "user_settings"` + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "user_settings" RENAME TO "temporary_user_settings"` + ); + await queryRunner.query( + `CREATE TABLE "user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "locale" varchar NOT NULL DEFAULT (''), "discoverRegion" varchar, "streamingRegion" varchar, "originalLanguage" varchar, "pgpKey" varchar, "discordId" varchar, "pushbulletAccessToken" varchar, "pushoverApplicationToken" varchar, "pushoverUserKey" varchar, "pushoverSound" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "watchlistSyncMovies" boolean, "watchlistSyncTv" boolean, "notificationTypes" text, "userId" integer, CONSTRAINT "REL_986a2b6d3c05eb4091bb8066f7" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "user_settings"("id", "locale", "discoverRegion", "streamingRegion", "originalLanguage", "pgpKey", "discordId", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "pushoverSound", "telegramChatId", "telegramSendSilently", "watchlistSyncMovies", "watchlistSyncTv", "notificationTypes", "userId") SELECT "id", "locale", "discoverRegion", "streamingRegion", "originalLanguage", "pgpKey", "discordId", "pushbulletAccessToken", "pushoverApplicationToken", "pushoverUserKey", "pushoverSound", "telegramChatId", "telegramSendSilently", "watchlistSyncMovies", "watchlistSyncTv", "notificationTypes", "userId" FROM "temporary_user_settings"` + ); + await queryRunner.query(`DROP TABLE "temporary_user_settings"`); + } +} diff --git a/server/routes/user/usersettings.ts b/server/routes/user/usersettings.ts index e4c16b1e..24ca976b 100644 --- a/server/routes/user/usersettings.ts +++ b/server/routes/user/usersettings.ts @@ -323,6 +323,7 @@ userSettingsRoutes.get<{ id: string }, UserSettingsNotificationsResponse>( telegramEnabled: settings.telegram.enabled, telegramBotUsername: settings.telegram.options.botUsername, telegramChatId: user.settings?.telegramChatId, + telegramMessageThreadId: user.settings?.telegramMessageThreadId, telegramSendSilently: user.settings?.telegramSendSilently, webPushEnabled: settings.webpush.enabled, notificationTypes: user.settings?.notificationTypes ?? {}, @@ -365,6 +366,7 @@ userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>( pushoverApplicationToken: req.body.pushoverApplicationToken, pushoverUserKey: req.body.pushoverUserKey, telegramChatId: req.body.telegramChatId, + telegramMessageThreadId: req.body.telegramMessageThreadId, telegramSendSilently: req.body.telegramSendSilently, notificationTypes: req.body.notificationTypes, }); @@ -377,6 +379,8 @@ userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>( user.settings.pushoverUserKey = req.body.pushoverUserKey; user.settings.pushoverSound = req.body.pushoverSound; user.settings.telegramChatId = req.body.telegramChatId; + user.settings.telegramMessageThreadId = + req.body.telegramMessageThreadId; user.settings.telegramSendSilently = req.body.telegramSendSilently; user.settings.notificationTypes = Object.assign( {}, @@ -395,6 +399,7 @@ userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>( pushoverUserKey: user.settings.pushoverUserKey, pushoverSound: user.settings.pushoverSound, telegramChatId: user.settings.telegramChatId, + telegramMessageThreadId: user.settings.telegramMessageThreadId, telegramSendSilently: user.settings.telegramSendSilently, notificationTypes: user.settings.notificationTypes, }); diff --git a/src/components/Settings/Notifications/NotificationsTelegram.tsx b/src/components/Settings/Notifications/NotificationsTelegram.tsx index 53ee4787..6636c6b4 100644 --- a/src/components/Settings/Notifications/NotificationsTelegram.tsx +++ b/src/components/Settings/Notifications/NotificationsTelegram.tsx @@ -23,8 +23,13 @@ const messages = defineMessages('components.Settings.Notifications', { chatId: 'Chat ID', chatIdTip: 'Start a chat with your bot, add @get_id_bot, and issue the /my_id command', + messageThreadId: 'Thread/Topic ID', + messageThreadIdTip: + "If your group-chat has topics enabled, you can specify a thread/topic's ID here", validationBotAPIRequired: 'You must provide a bot authorization token', validationChatIdRequired: 'You must provide a valid chat ID', + validationMessageThreadId: + 'The thread/topic ID must be a positive whole number', telegramsettingssaved: 'Telegram notification settings saved successfully!', telegramsettingsfailed: 'Telegram notification settings failed to save.', toastTelegramTestSending: 'Sending Telegram test notification…', @@ -64,6 +69,15 @@ const NotificationsTelegram = () => { /^-?\d+$/, intl.formatMessage(messages.validationChatIdRequired) ), + messageThreadId: Yup.string() + .when(['types'], { + is: (enabled: boolean, types: number) => enabled && !!types, + then: Yup.string() + .nullable() + .required(intl.formatMessage(messages.validationMessageThreadId)), + otherwise: Yup.string().nullable(), + }) + .matches(/^\d+$/, intl.formatMessage(messages.validationMessageThreadId)), }); if (!data && !error) { @@ -78,6 +92,7 @@ const NotificationsTelegram = () => { botUsername: data?.options.botUsername, botAPI: data?.options.botAPI, chatId: data?.options.chatId, + messageThreadId: data?.options.messageThreadId, sendSilently: data?.options.sendSilently, }} validationSchema={NotificationsTelegramSchema} @@ -94,6 +109,7 @@ const NotificationsTelegram = () => { options: { botAPI: values.botAPI, chatId: values.chatId, + messageThreadId: values.messageThreadId, sendSilently: values.sendSilently, botUsername: values.botUsername, }, @@ -151,6 +167,7 @@ const NotificationsTelegram = () => { options: { botAPI: values.botAPI, chatId: values.chatId, + messageThreadId: values.messageThreadId, sendSilently: values.sendSilently, botUsername: values.botUsername, }, @@ -286,6 +303,28 @@ const NotificationsTelegram = () => { )} +
+ +
+
+ +
+ {errors.messageThreadId && + touched.messageThreadId && + typeof errors.messageThreadId === 'string' && ( +
{errors.messageThreadId}
+ )} +
+
+
+ +
+
+ +
+ {errors.telegramMessageThreadId && + touched.telegramMessageThreadId && + typeof errors.telegramMessageThreadId === 'string' && ( +
+ {errors.telegramMessageThreadId} +
+ )} +
+
+

+ {intl.formatMessage(messages.overrideRules)} +

+
    + {rules && ( + + )} +
  • +
    + +
    +
  • +
); }} diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 447d3167..9453d39e 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -589,6 +589,7 @@ "components.Selector.searchKeywords": "Search keywords…", "components.Selector.searchStatus": "Select status...", "components.Selector.searchStudios": "Search studios…", + "components.Selector.searchUsers": "Select users…", "components.Selector.showless": "Show Less", "components.Selector.showmore": "Show More", "components.Selector.starttyping": "Starting typing to search.", @@ -735,7 +736,37 @@ "components.Settings.Notifications.webhookRoleIdTip": "The role ID to mention in the webhook message. Leave empty to disable mentions", "components.Settings.Notifications.webhookUrl": "Webhook URL", "components.Settings.Notifications.webhookUrlTip": "Create a webhook integration in your server", + "components.Settings.OverrideRuleModal.conditions": "Conditions", + "components.Settings.OverrideRuleModal.conditionsDescription": "Specifies conditions before applying parameter changes. Each field must be validated for the rules to be applied (AND operation). A field is considered verified if any of its properties match (OR operation).", + "components.Settings.OverrideRuleModal.create": "Create rule", + "components.Settings.OverrideRuleModal.createrule": "New Override Rule", + "components.Settings.OverrideRuleModal.editrule": "Edit Override Rule", + "components.Settings.OverrideRuleModal.genres": "Genres", + "components.Settings.OverrideRuleModal.keywords": "Keywords", + "components.Settings.OverrideRuleModal.languages": "Languages", + "components.Settings.OverrideRuleModal.notagoptions": "No tags.", + "components.Settings.OverrideRuleModal.qualityprofile": "Quality Profile", + "components.Settings.OverrideRuleModal.rootfolder": "Root Folder", + "components.Settings.OverrideRuleModal.ruleCreated": "Override rule created successfully!", + "components.Settings.OverrideRuleModal.ruleUpdated": "Override rule updated successfully!", + "components.Settings.OverrideRuleModal.selectQualityProfile": "Select quality profile", + "components.Settings.OverrideRuleModal.selectRootFolder": "Select root folder", + "components.Settings.OverrideRuleModal.selecttags": "Select tags", + "components.Settings.OverrideRuleModal.settings": "Settings", + "components.Settings.OverrideRuleModal.settingsDescription": "Specifies which settings will be changed when the above conditions are met.", + "components.Settings.OverrideRuleModal.tags": "Tags", + "components.Settings.OverrideRuleModal.users": "Users", + "components.Settings.OverrideRuleTile.conditions": "Conditions", + "components.Settings.OverrideRuleTile.genre": "Genre", + "components.Settings.OverrideRuleTile.keywords": "Keywords", + "components.Settings.OverrideRuleTile.language": "Language", + "components.Settings.OverrideRuleTile.qualityprofile": "Quality Profile", + "components.Settings.OverrideRuleTile.rootfolder": "Root Folder", + "components.Settings.OverrideRuleTile.settings": "Settings", + "components.Settings.OverrideRuleTile.tags": "Tags", + "components.Settings.OverrideRuleTile.users": "Users", "components.Settings.RadarrModal.add": "Add Server", + "components.Settings.RadarrModal.addrule": "New Override Rule", "components.Settings.RadarrModal.announced": "Announced", "components.Settings.RadarrModal.apiKey": "API Key", "components.Settings.RadarrModal.baseUrl": "URL Base", @@ -754,6 +785,7 @@ "components.Settings.RadarrModal.loadingrootfolders": "Loading root folders…", "components.Settings.RadarrModal.minimumAvailability": "Minimum Availability", "components.Settings.RadarrModal.notagoptions": "No tags.", + "components.Settings.RadarrModal.overrideRules": "Override Rules", "components.Settings.RadarrModal.port": "Port", "components.Settings.RadarrModal.qualityprofile": "Quality Profile", "components.Settings.RadarrModal.released": "Released", @@ -929,6 +961,7 @@ "components.Settings.SettingsUsers.userSettingsDescription": "Configure global and default user settings.", "components.Settings.SettingsUsers.users": "Users", "components.Settings.SonarrModal.add": "Add Server", + "components.Settings.SonarrModal.addrule": "New Override Rule", "components.Settings.SonarrModal.animeSeriesType": "Anime Series Type", "components.Settings.SonarrModal.animeTags": "Anime Tags", "components.Settings.SonarrModal.animelanguageprofile": "Anime Language Profile", @@ -951,6 +984,7 @@ "components.Settings.SonarrModal.loadingprofiles": "Loading quality profiles…", "components.Settings.SonarrModal.loadingrootfolders": "Loading root folders…", "components.Settings.SonarrModal.notagoptions": "No tags.", + "components.Settings.SonarrModal.overrideRules": "Override Rules", "components.Settings.SonarrModal.port": "Port", "components.Settings.SonarrModal.qualityprofile": "Quality Profile", "components.Settings.SonarrModal.rootfolder": "Root Folder", From 66948b420feecd85bd6bbdb2371b2ee32675baef Mon Sep 17 00:00:00 2001 From: Gauthier Date: Sat, 28 Dec 2024 23:22:26 +0100 Subject: [PATCH 39/41] refactor(i18n): add better types to our custom defineMessages (#1192) --- .../Discover/DiscoverTvUpcoming.tsx | 4 ++- src/i18n/locale/en.json | 1 + src/utils/defineMessages.ts | 32 ++++++++++++------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/components/Discover/DiscoverTvUpcoming.tsx b/src/components/Discover/DiscoverTvUpcoming.tsx index 182cd1a5..a6a3be17 100644 --- a/src/components/Discover/DiscoverTvUpcoming.tsx +++ b/src/components/Discover/DiscoverTvUpcoming.tsx @@ -7,7 +7,9 @@ import defineMessages from '@app/utils/defineMessages'; import type { TvResult } from '@server/models/Search'; import { useIntl } from 'react-intl'; -const messages = defineMessages('components.DiscoverTvUpcoming', {}); +const messages = defineMessages('components.DiscoverTvUpcoming', { + upcomingtv: 'Upcoming Series', +}); const DiscoverTvUpcoming = () => { const intl = useIntl(); diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 9453d39e..3fce7abd 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -100,6 +100,7 @@ "components.Discover.StudioSlider.studios": "Studios", "components.Discover.TvGenreList.seriesgenres": "Series Genres", "components.Discover.TvGenreSlider.tvgenres": "Series Genres", + "components.DiscoverTvUpcoming.upcomingtv": "Upcoming Series", "components.Discover.createnewslider": "Create New Slider", "components.Discover.customizediscover": "Customize Discover", "components.Discover.discover": "Discover", diff --git a/src/utils/defineMessages.ts b/src/utils/defineMessages.ts index 69a04a7d..bb8fadb4 100644 --- a/src/utils/defineMessages.ts +++ b/src/utils/defineMessages.ts @@ -1,18 +1,26 @@ import { defineMessages as intlDefineMessages } from 'react-intl'; -export default function defineMessages( +type Messages> = { + [K in keyof T]: { + id: string; + defaultMessage: T[K]; + }; +}; + +export default function defineMessages>( prefix: string, - messages: Record -) { - const modifiedMessages: Record< - string, - { id: string; defaultMessage: string } - > = {}; - for (const key of Object.keys(messages)) { - modifiedMessages[key] = { - id: prefix + '.' + key, + messages: T +): Messages { + const keys: (keyof T)[] = Object.keys(messages); + const modifiedMessagesEntries = keys.map((key) => [ + key, + { + id: `${prefix}.${key as string}`, defaultMessage: messages[key], - }; - } + }, + ]); + const modifiedMessages: Messages = Object.fromEntries( + modifiedMessagesEntries + ); return intlDefineMessages(modifiedMessages); } From b6e2e6ce615cb94cea8d2335140fe245a0ca2d8a Mon Sep 17 00:00:00 2001 From: Gauthier Date: Sun, 29 Dec 2024 22:03:49 +0100 Subject: [PATCH 40/41] feat: add a setting for special episodes (#1193) * feat: add a setting for special episodes This PR adds a separate setting for special episodes and disables them by default, to avoid unwanted library status updates. * refactor(settings): re-order setting for allow specials request --------- Co-authored-by: fallenbagel <98979876+Fallenbagel@users.noreply.github.com> --- cypress/config/settings.cypress.json | 1 + overseerr-api.yml | 3 + server/entity/MediaRequest.ts | 7 ++- server/interfaces/api/settingsInterfaces.ts | 1 + server/lib/scanners/plex/index.ts | 7 ++- server/lib/scanners/sonarr/index.ts | 7 ++- server/lib/settings/index.ts | 4 ++ src/components/RequestCard/index.tsx | 8 ++- .../RequestList/RequestItem/index.tsx | 8 ++- .../RequestModal/TvRequestModal.tsx | 17 +++-- .../Settings/SettingsMain/index.tsx | 62 +++++++++++++------ src/components/TvDetails/index.tsx | 9 ++- src/context/SettingsContext.tsx | 1 + src/i18n/locale/en.json | 1 + src/pages/_app.tsx | 1 + 15 files changed, 108 insertions(+), 29 deletions(-) diff --git a/cypress/config/settings.cypress.json b/cypress/config/settings.cypress.json index f45bcbc0..e3d31cc1 100644 --- a/cypress/config/settings.cypress.json +++ b/cypress/config/settings.cypress.json @@ -22,6 +22,7 @@ "trustProxy": false, "mediaServerType": 1, "partialRequestsEnabled": true, + "enableSpecialEpisodes": false, "locale": "en" }, "plex": { diff --git a/overseerr-api.yml b/overseerr-api.yml index 6a387a6b..06a7523d 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -188,6 +188,9 @@ components: defaultPermissions: type: number example: 32 + enableSpecialEpisodes: + type: boolean + example: false PlexLibrary: type: object properties: diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index 61b82c0e..6cc808c3 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -58,6 +58,7 @@ export class MediaRequest { const mediaRepository = getRepository(Media); const requestRepository = getRepository(MediaRequest); const userRepository = getRepository(User); + const settings = getSettings(); let requestUser = user; @@ -258,7 +259,11 @@ export class MediaRequest { >; const requestedSeasons = requestBody.seasons === 'all' - ? tmdbMediaShow.seasons.map((season) => season.season_number) + ? settings.main.enableSpecialEpisodes + ? tmdbMediaShow.seasons.map((season) => season.season_number) + : tmdbMediaShow.seasons + .map((season) => season.season_number) + .filter((sn) => sn > 0) : (requestBody.seasons as number[]); let existingSeasons: number[] = []; diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts index 29a81d5e..017eef85 100644 --- a/server/interfaces/api/settingsInterfaces.ts +++ b/server/interfaces/api/settingsInterfaces.ts @@ -37,6 +37,7 @@ export interface PublicSettingsResponse { originalLanguage: string; mediaServerType: number; partialRequestsEnabled: boolean; + enableSpecialEpisodes: boolean; cacheImages: boolean; vapidPublic: string; enablePushRegistration: boolean; diff --git a/server/lib/scanners/plex/index.ts b/server/lib/scanners/plex/index.ts index e4af7a1f..9dee904a 100644 --- a/server/lib/scanners/plex/index.ts +++ b/server/lib/scanners/plex/index.ts @@ -277,8 +277,13 @@ class PlexScanner const seasons = tvShow.seasons; const processableSeasons: ProcessableSeason[] = []; + const settings = getSettings(); - for (const season of seasons) { + const filteredSeasons = settings.main.enableSpecialEpisodes + ? seasons + : seasons.filter((sn) => sn.season_number !== 0); + + for (const season of filteredSeasons) { const matchedPlexSeason = metadata.Children?.Metadata.find( (md) => Number(md.index) === season.season_number ); diff --git a/server/lib/scanners/sonarr/index.ts b/server/lib/scanners/sonarr/index.ts index 5d28e014..88f6a324 100644 --- a/server/lib/scanners/sonarr/index.ts +++ b/server/lib/scanners/sonarr/index.ts @@ -102,9 +102,12 @@ class SonarrScanner } const tmdbId = tvShow.id; + const settings = getSettings(); - const filteredSeasons = sonarrSeries.seasons.filter((sn) => - tvShow.seasons.find((s) => s.season_number === sn.seasonNumber) + const filteredSeasons = sonarrSeries.seasons.filter( + (sn) => + tvShow.seasons.find((s) => s.season_number === sn.seasonNumber) && + (!settings.main.partialRequestsEnabled ? sn.seasonNumber !== 0 : true) ); for (const season of filteredSeasons) { diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 4613486f..cd8ebb97 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -131,6 +131,7 @@ export interface MainSettings { trustProxy: boolean; mediaServerType: number; partialRequestsEnabled: boolean; + enableSpecialEpisodes: boolean; locale: string; proxy: ProxySettings; } @@ -154,6 +155,7 @@ interface FullPublicSettings extends PublicSettings { jellyfinForgotPasswordUrl?: string; jellyfinServerName?: string; partialRequestsEnabled: boolean; + enableSpecialEpisodes: boolean; cacheImages: boolean; vapidPublic: string; enablePushRegistration: boolean; @@ -343,6 +345,7 @@ class Settings { trustProxy: false, mediaServerType: MediaServerType.NOT_CONFIGURED, partialRequestsEnabled: true, + enableSpecialEpisodes: false, locale: 'en', proxy: { enabled: false, @@ -587,6 +590,7 @@ class Settings { originalLanguage: this.data.main.originalLanguage, mediaServerType: this.main.mediaServerType, partialRequestsEnabled: this.data.main.partialRequestsEnabled, + enableSpecialEpisodes: this.data.main.enableSpecialEpisodes, cacheImages: this.data.main.cacheImages, vapidPublic: this.vapidPublic, enablePushRegistration: this.data.notifications.agents.webpush.enabled, diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx index e737c733..7f08044e 100644 --- a/src/components/RequestCard/index.tsx +++ b/src/components/RequestCard/index.tsx @@ -5,6 +5,7 @@ import Tooltip from '@app/components/Common/Tooltip'; import RequestModal from '@app/components/RequestModal'; import StatusBadge from '@app/components/StatusBadge'; import useDeepLinks from '@app/hooks/useDeepLinks'; +import useSettings from '@app/hooks/useSettings'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; import defineMessages from '@app/utils/defineMessages'; @@ -219,6 +220,7 @@ interface RequestCardProps { } const RequestCard = ({ request, onTitleData }: RequestCardProps) => { + const settings = useSettings(); const { ref, inView } = useInView({ triggerOnce: true, }); @@ -411,7 +413,11 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => { {intl.formatMessage(messages.seasons, { seasonCount: - title.seasons.length === request.seasons.length + (settings.currentSettings.enableSpecialEpisodes + ? title.seasons.length + : title.seasons.filter( + (season) => season.seasonNumber !== 0 + ).length) === request.seasons.length ? 0 : request.seasons.length, })} diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index 7f64039c..0f8a5a24 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -5,6 +5,7 @@ import ConfirmButton from '@app/components/Common/ConfirmButton'; import RequestModal from '@app/components/RequestModal'; import StatusBadge from '@app/components/StatusBadge'; import useDeepLinks from '@app/hooks/useDeepLinks'; +import useSettings from '@app/hooks/useSettings'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; import defineMessages from '@app/utils/defineMessages'; @@ -294,6 +295,7 @@ interface RequestItemProps { } const RequestItem = ({ request, revalidateList }: RequestItemProps) => { + const settings = useSettings(); const { ref, inView } = useInView({ triggerOnce: true, }); @@ -481,7 +483,11 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { {intl.formatMessage(messages.seasons, { seasonCount: - title.seasons.length === request.seasons.length + (settings.currentSettings.enableSpecialEpisodes + ? title.seasons.length + : title.seasons.filter( + (season) => season.seasonNumber !== 0 + ).length) === request.seasons.length ? 0 : request.seasons.length, })} diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx index 10c9c7db..18579d64 100644 --- a/src/components/RequestModal/TvRequestModal.tsx +++ b/src/components/RequestModal/TvRequestModal.tsx @@ -253,9 +253,13 @@ const TvRequestModal = ({ }; const getAllSeasons = (): number[] => { - return (data?.seasons ?? []) - .filter((season) => season.episodeCount !== 0) - .map((season) => season.seasonNumber); + let allSeasons = (data?.seasons ?? []).filter( + (season) => season.episodeCount !== 0 + ); + if (!settings.currentSettings.partialRequestsEnabled) { + allSeasons = allSeasons.filter((season) => season.seasonNumber !== 0); + } + return allSeasons.map((season) => season.seasonNumber); }; const getAllRequestedSeasons = (): number[] => { @@ -577,7 +581,12 @@ const TvRequestModal = ({ {data?.seasons - .filter((season) => season.episodeCount !== 0) + .filter( + (season) => + (!settings.currentSettings.enableSpecialEpisodes + ? season.seasonNumber !== 0 + : true) && season.episodeCount !== 0 + ) .map((season) => { const seasonRequest = getSeasonRequest( season.seasonNumber diff --git a/src/components/Settings/SettingsMain/index.tsx b/src/components/Settings/SettingsMain/index.tsx index e2c50cc1..d5f116c1 100644 --- a/src/components/Settings/SettingsMain/index.tsx +++ b/src/components/Settings/SettingsMain/index.tsx @@ -56,6 +56,7 @@ const messages = defineMessages('components.Settings.SettingsMain', { validationApplicationUrl: 'You must provide a valid URL', validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash', partialRequestsEnabled: 'Allow Partial Series Requests', + enableSpecialEpisodes: 'Allow Special Episodes Requests', locale: 'Display Language', proxyEnabled: 'HTTP(S) Proxy', proxyHostname: 'Proxy Hostname', @@ -158,6 +159,7 @@ const SettingsMain = () => { originalLanguage: data?.originalLanguage, streamingRegion: data?.streamingRegion, partialRequestsEnabled: data?.partialRequestsEnabled, + enableSpecialEpisodes: data?.enableSpecialEpisodes, trustProxy: data?.trustProxy, cacheImages: data?.cacheImages, proxyEnabled: data?.proxy?.enabled, @@ -188,6 +190,7 @@ const SettingsMain = () => { streamingRegion: values.streamingRegion, originalLanguage: values.originalLanguage, partialRequestsEnabled: values.partialRequestsEnabled, + enableSpecialEpisodes: values.enableSpecialEpisodes, trustProxy: values.trustProxy, cacheImages: values.cacheImages, proxy: { @@ -498,6 +501,47 @@ const SettingsMain = () => { /> +
+ +
+ { + setFieldValue( + 'enableSpecialEpisodes', + !values.enableSpecialEpisodes + ); + }} + /> +
+
+
+
+ + + +
+
)} -
-
- - - -
-
); }} diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 4d50f1b8..77028595 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -301,7 +301,9 @@ const TvDetails = ({ tv }: TvDetailsProps) => { }; const showHasSpecials = data.seasons.some( - (season) => season.seasonNumber === 0 + (season) => + season.seasonNumber === 0 && + settings.currentSettings.partialRequestsEnabled ); const isComplete = @@ -799,6 +801,11 @@ const TvDetails = ({ tv }: TvDetailsProps) => { {data.seasons .slice() .reverse() + .filter( + (season) => + settings.currentSettings.enableSpecialEpisodes || + season.seasonNumber !== 0 + ) .map((season) => { const show4k = settings.currentSettings.series4kEnabled && diff --git a/src/context/SettingsContext.tsx b/src/context/SettingsContext.tsx index 5579940a..6a286d8a 100644 --- a/src/context/SettingsContext.tsx +++ b/src/context/SettingsContext.tsx @@ -21,6 +21,7 @@ const defaultSettings = { originalLanguage: '', mediaServerType: MediaServerType.NOT_CONFIGURED, partialRequestsEnabled: true, + enableSpecialEpisodes: false, cacheImages: false, vapidPublic: '', enablePushRegistration: false, diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 3fce7abd..ded40f9f 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -919,6 +919,7 @@ "components.Settings.SettingsMain.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)", "components.Settings.SettingsMain.discoverRegion": "Discover Region", "components.Settings.SettingsMain.discoverRegionTip": "Filter content by regional availability", + "components.Settings.SettingsMain.enableSpecialEpisodes": "Allow Special Episodes Requests", "components.Settings.SettingsMain.general": "General", "components.Settings.SettingsMain.generalsettings": "General Settings", "components.Settings.SettingsMain.generalsettingsDescription": "Configure global and default settings for Jellyseerr.", diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index d0fbbfa9..facb3a44 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -199,6 +199,7 @@ CoreApp.getInitialProps = async (initialProps) => { originalLanguage: '', mediaServerType: MediaServerType.NOT_CONFIGURED, partialRequestsEnabled: true, + enableSpecialEpisodes: false, cacheImages: false, vapidPublic: '', enablePushRegistration: false, From 5fc4ae57c01fa90e61f6389346bc1449f2ee7773 Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Mon, 30 Dec 2024 05:09:23 +0800 Subject: [PATCH 41/41] style(http-proxy): fix margin for responsive design (#1194) --- .../Settings/SettingsMain/index.tsx | 256 +++++++++--------- 1 file changed, 131 insertions(+), 125 deletions(-) diff --git a/src/components/Settings/SettingsMain/index.tsx b/src/components/Settings/SettingsMain/index.tsx index d5f116c1..8020b9fe 100644 --- a/src/components/Settings/SettingsMain/index.tsx +++ b/src/components/Settings/SettingsMain/index.tsx @@ -563,151 +563,157 @@ const SettingsMain = () => { {values.proxyEnabled && ( <> -
-