├── .github └── FUNDING.yml ├── .gitignore ├── NOTICE ├── PRIVACY_POLICY.md ├── README.md ├── _locales ├── en │ └── messages.json ├── fr │ └── messages.json ├── ja │ └── messages.json └── zh_CN │ └── messages.json ├── content.js ├── embed.js ├── icons ├── chrome-webstore-icon.png ├── icon.svg ├── icon128.png ├── icon16.png ├── icon256.png ├── icon32.png ├── icon48.png ├── icon512.png ├── icon64.png ├── icon96.png ├── toolbar-icon16.png ├── toolbar-icon19.png ├── toolbar-icon32.png ├── toolbar-icon38.png ├── toolbar-icon48.png └── toolbar-icon72.png ├── jsconfig.json ├── manifest.mv2.json ├── manifest.mv3.json ├── options-icon.png ├── options.css ├── options.html ├── options.js ├── package.json ├── page.js ├── safari ├── .gitignore ├── Control Panel for YouTube.xcodeproj │ └── project.pbxproj ├── Shared (App) │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── appicon1024-fullbleed.png │ │ │ ├── appicon1024.png │ │ │ ├── appicon128.png │ │ │ ├── appicon16.png │ │ │ ├── appicon256.png │ │ │ ├── appicon32.png │ │ │ ├── appicon512.png │ │ │ └── appicon64.png │ │ ├── Contents.json │ │ └── LargeIcon.imageset │ │ │ └── Contents.json │ ├── Base.lproj │ │ └── Main.html │ ├── Resources │ │ ├── Ad.png │ │ ├── Icon.png │ │ ├── Script.js │ │ └── Style.css │ └── ViewController.swift ├── Shared (Extension) │ ├── Resources │ │ └── manifest.json │ └── SafariWebExtensionHandler.swift ├── iOS (App) │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── SceneDelegate.swift ├── iOS (Extension) │ └── Info.plist ├── macOS (App) │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── Main.storyboard │ └── Control Panel for YouTube.entitlements └── macOS (Extension) │ ├── Control Panel for YouTube.entitlements │ └── Info.plist ├── scripts ├── build.js ├── copy.js ├── create-browser-action.js ├── create-store-description.mjs ├── lib.js ├── locales │ ├── .prettierrc │ ├── base-locales.json │ ├── create-locales.js │ └── mobile.sh ├── mac-appicon-assets.sh ├── release.js └── update-locale-messages.js └── types.d.ts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: jbscript 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | TODO.md 3 | browser_action.html 4 | manifest.json 5 | node_modules/ 6 | scripts/locales/locales.js 7 | scripts/locales/mobile/* 8 | scripts/translations.json 9 | web-ext-artifacts/ 10 | wip/ -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | content.js includes the dedent() function from https://github.com/victornpb/tiny-dedent 2 | 3 | MIT License 4 | 5 | Copyright (c) 2021 Victor 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | --- 26 | 27 | content.js includes CSS for an animated pie timer from https://kittygiraudel.com/2021/04/11/css-pie-timer-revisited/ 28 | 29 | The MIT License (MIT) 30 | 31 | Copyright (c) 2010–2021 Kitty Giraudel 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy 34 | of this software and associated documentation files (the "Software"), to deal 35 | in the Software without restriction, including without limitation the rights 36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is 38 | furnished to do so, subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in all 41 | copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 49 | SOFTWARE. -------------------------------------------------------------------------------- /PRIVACY_POLICY.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | No data or personal information is collected by Control Panel for YouTube. 4 | 5 | ### Contact 6 | 7 | If you have any questions or suggestions regarding this privacy policy, please email jonathan.buchanan+extensions@gmail.com. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Control Panel for YouTube](https://soitis.dev/control-panel-for-youtube) 2 | 3 | [![](icons/icon128.png)](https://soitis.dev/control-panel-for-youtube) 4 | 5 | **Control Panel for YouTube is a browser extension which gives you more control over your YouTube experience by adding missing options and UI improvements** 6 | 7 | > [!IMPORTANT] 8 | > This is the support repository for Control Panel for YouTube - for installation links, information about the extension, and FAQs, please visit the [Control Panel for YouTube website](https://soitis.dev/control-panel-for-youtube). 9 | 10 | Check the latest updates and availability for your browser on the [releases page](https://github.com/insin/control-panel-for-youtube/releases). 11 | 12 | Follow [@soitis.dev](https://bsky.app/profile/soitis.dev) on Bluesky for extension news and other announcements. 13 | 14 | ## Support 15 | 16 | To report a bug [create a new Issue](https://github.com/insin/control-panel-for-youtube/issues/new) here on GitHub. 17 | 18 | Please include: 19 | 20 | - The version of Control Panel for YouTube you're using 21 | - The browser and operating system you're using it on 22 | - Relevant URLs if applicable (e.g. a specific YouTube video a feature isn't working on) 23 | - Relevant screenshots if applicable 24 | 25 | If you don't have a GitHub account, you can use the [Browser Extension Feedback & Support form](https://soitis.dev/extensions/feedback) on our website instead, or email [extensions@soitis.dev](mailto:extensions@soitis.dev). 26 | 27 | ## New translations 28 | 29 | If you would like to translate Control Panel for YouTube's options into your language, please create a copy of the English [messages.json](./_locales/en/messages.json) file, translate the `"message"` strings into your language, then create a new Issue or Pull Request with the translated JSON. 30 | 31 | Certain features also depend on translations in the `locales` object in [content.js](./content.js#L104), which uses the language code you can find in YouTube's `` attribute - these _must_ match the text used in the relevant parts of YouTube's UI. 32 | 33 | ## Thanks 34 | 35 | - [@Catastravia](https://github.com/Catastravia) for [improving the Japanese translation](https://github.com/insin/control-panel-for-youtube/issues/22) 36 | - [@movcoa](https://github.com/movcoa) for [providing a Chinese translation](https://github.com/insin/control-panel-for-youtube/issues/67) 37 | - [@tohu-sand](https://github.com/tohu-sand) for [improving the Japanese translation](https://github.com/insin/control-panel-for-youtube/pull/68) 38 | - [@maxchrr](https://github.com/maxchrr) for [providing a French translation](https://github.com/insin/control-panel-for-youtube/pull/86) 39 | 40 | ## Attribution 41 | 42 | Icon adapted from "Floor hatch icon" by [Delapouite](https://delapouite.com/) from [game-icons.net](https://game-icons.net), [CC 3.0 BY](https://creativecommons.org/licenses/by/3.0/) 43 | -------------------------------------------------------------------------------- /_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "addTakeSnapshot": { 3 | "message": "Add Take snapshot to video menu" 4 | }, 5 | "alwaysShowShortsProgressBar": { 6 | "message": "Always show progress bar" 7 | }, 8 | "alwaysUseOriginalAudio": { 9 | "message": "Always use original audio" 10 | }, 11 | "alwaysUseTheaterMode": { 12 | "message": "Always use theater mode" 13 | }, 14 | "anyPercent": { 15 | "message": "any %" 16 | }, 17 | "auto": { 18 | "message": "Auto" 19 | }, 20 | "desktopOnly": { 21 | "message": "Desktop only" 22 | }, 23 | "disableAutoplay": { 24 | "message": "Disable Autoplay" 25 | }, 26 | "disableHomeFeed": { 27 | "message": "Disable Home feed" 28 | }, 29 | "disableHomeFeedNote": { 30 | "message": "Only when logged in - redirects to Subscriptions" 31 | }, 32 | "downloadTranscript": { 33 | "message": "Make Transcript downloadable" 34 | }, 35 | "embeddedVideos": { 36 | "message": "Embedded videos" 37 | }, 38 | "enabled": { 39 | "message": "Enabled" 40 | }, 41 | "experimentalFeatures": { 42 | "message": "Experimental features" 43 | }, 44 | "extensionDescription": { 45 | "message": "Gives you more control over YouTube by adding missing options and UI improvements", 46 | "description": "<= 112 characters" 47 | }, 48 | "extensionName": { 49 | "message": "Control Panel for YouTube" 50 | }, 51 | "features": { 52 | "message": "Features" 53 | }, 54 | "fullSizeTheaterMode": { 55 | "message": "Full size theater mode" 56 | }, 57 | "fullSizeTheaterModeHideHeader": { 58 | "message": "Hide header" 59 | }, 60 | "fullSizeTheaterModeHideScrollbar": { 61 | "message": "Hide scrollbar" 62 | }, 63 | "hiddenChannelsSummary": { 64 | "message": "Hidden channels ($COUNT$)", 65 | "placeholders": { 66 | "count": { 67 | "content": "$1", 68 | "example": "1" 69 | } 70 | } 71 | }, 72 | "hideAI": { 73 | "message": "Hide AI summaries" 74 | }, 75 | "hideChannelBanner": { 76 | "message": "Hide channel banner images" 77 | }, 78 | "hideChannels": { 79 | "message": "Hide channels" 80 | }, 81 | "hideChannelsNote": { 82 | "message": "Adds \"Hide channel\" to video menus" 83 | }, 84 | "hideChat": { 85 | "message": "Hide Chat" 86 | }, 87 | "hideComments": { 88 | "message": "Hide Comments" 89 | }, 90 | "hideEmbedPauseOverlay": { 91 | "message": "Hide pause overlay" 92 | }, 93 | "hideEmbedShareButton": { 94 | "message": "Hide Share button" 95 | }, 96 | "hideEndCards": { 97 | "message": "Hide video end cards" 98 | }, 99 | "hideEndVideos": { 100 | "message": "Hide video endscreen content" 101 | }, 102 | "hideExploreButton": { 103 | "message": "Hide Explore button in Home" 104 | }, 105 | "hideHiddenVideos": { 106 | "message": "Hide hidden videos" 107 | }, 108 | "hideHiddenVideosNote": { 109 | "message": "Hides the \"Undo\" button after 5 seconds" 110 | }, 111 | "hideHomeCategories": { 112 | "message": "Hide categories in Home" 113 | }, 114 | "hideInfoPanels": { 115 | "message": "Hide information panels" 116 | }, 117 | "hideLive": { 118 | "message": "Hide Live videos" 119 | }, 120 | "hideMembersOnly": { 121 | "message": "Hide Members only videos" 122 | }, 123 | "hideMerchEtc": { 124 | "message": "Hide Merch, Offers etc." 125 | }, 126 | "hideMetadata": { 127 | "message": "Hide video metadata" 128 | }, 129 | "hideMiniplayerButton": { 130 | "message": "Hide Miniplayer button" 131 | }, 132 | "hideMixes": { 133 | "message": "Hide Mixes" 134 | }, 135 | "hideMoviesAndTV": { 136 | "message": "Hide Movies and TV" 137 | }, 138 | "hideNextButton": { 139 | "message": "Hide the Next button" 140 | }, 141 | "hideNextButtonNote": { 142 | "message": "When not watching a video in a playlist" 143 | }, 144 | "hideOpenApp": { 145 | "message": "Hide Open App links" 146 | }, 147 | "hidePlaylists": { 148 | "message": "Hide Playlists" 149 | }, 150 | "hidePremiumUpsells": { 151 | "message": "Hide Premium upsells" 152 | }, 153 | "hideRelated": { 154 | "message": "Hide Related videos" 155 | }, 156 | "hideShareThanksClip": { 157 | "message": "Hide Share/Thanks/Clip etc." 158 | }, 159 | "hideShorts": { 160 | "message": "Hide Shorts" 161 | }, 162 | "hideShortsMetadataUntilHover": { 163 | "message": "Hide Shorts metadata until hover" 164 | }, 165 | "hideShortsSuggestedActions": { 166 | "message": "Hide suggested actions" 167 | }, 168 | "hideSponsored": { 169 | "message": "Hide Sponsored videos & promos" 170 | }, 171 | "hideStreamed": { 172 | "message": "Hide Streamed videos" 173 | }, 174 | "hideSubscriptionsChannelList": { 175 | "message": "Hide channel list in Subscriptions" 176 | }, 177 | "hideSubscriptionsLatestBar": { 178 | "message": "Hide \"Latest\" bar in Subscriptions" 179 | }, 180 | "hideSuggestedSections": { 181 | "message": "Hide suggested sections" 182 | }, 183 | "hideUpcoming": { 184 | "message": "Hide Upcoming videos" 185 | }, 186 | "hideVoiceSearch": { 187 | "message": "Hide Search with your voice" 188 | }, 189 | "hideWatched": { 190 | "message": "Hide watched videos" 191 | }, 192 | "hideWatchedThreshold": { 193 | "message": "Watched %" 194 | }, 195 | "homeMaxOption": { 196 | "message": "9 (Home max)" 197 | }, 198 | "inHomeAndSubscriptionsNote": { 199 | "message": "In Home and Subscriptions" 200 | }, 201 | "large": { 202 | "message": "Large" 203 | }, 204 | "medium": { 205 | "message": "Medium" 206 | }, 207 | "minimumGridItemsPerRow": { 208 | "message": "Minimum grid items per row" 209 | }, 210 | "minimumShortsPerRow": { 211 | "message": "Minimum Shorts per row" 212 | }, 213 | "mobileGridView": { 214 | "message": "Use grid view for Subscriptions & Search (portrait only)" 215 | }, 216 | "mobileOnly": { 217 | "message": "Mobile only" 218 | }, 219 | "pauseChannelTrailers": { 220 | "message": "Automatically pause channel trailers" 221 | }, 222 | "qualityFull": { 223 | "message": "Full" 224 | }, 225 | "qualityHigh": { 226 | "message": "High" 227 | }, 228 | "qualityLow": { 229 | "message": "Low" 230 | }, 231 | "qualityMedium": { 232 | "message": "Medium" 233 | }, 234 | "redirectShorts": { 235 | "message": "Redirect Shorts to the normal player" 236 | }, 237 | "removePink": { 238 | "message": "Remove pink gradient from progress bars" 239 | }, 240 | "searchThumbnailSize": { 241 | "message": "Search thumbnail size" 242 | }, 243 | "shorts": { 244 | "message": "Shorts" 245 | }, 246 | "skipAds": { 247 | "message": "Skip ads" 248 | }, 249 | "small": { 250 | "message": "Small" 251 | }, 252 | "snapshotFormat": { 253 | "message": "Snapshot format" 254 | }, 255 | "snapshotQuality": { 256 | "message": "Quality" 257 | }, 258 | "stopShortsLooping": { 259 | "message": "Stop Shorts looping" 260 | }, 261 | "tidyGuideSidebar": { 262 | "message": "Tidy Guide sidebar" 263 | }, 264 | "uiTweaks": { 265 | "message": "UI tweaks" 266 | }, 267 | "videoLists": { 268 | "message": "Video lists" 269 | }, 270 | "videoPages": { 271 | "message": "Video pages" 272 | } 273 | } -------------------------------------------------------------------------------- /_locales/fr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "addTakeSnapshot": { 3 | "message": "Ajouter Capture d'écran au menu de la vidéo" 4 | }, 5 | "alwaysShowShortsProgressBar": { 6 | "message": "Toujours afficher la barre de progression" 7 | }, 8 | "alwaysUseOriginalAudio": { 9 | "message": "Toujours utiliser l'audio d'origine" 10 | }, 11 | "alwaysUseTheaterMode": { 12 | "message": "Toujours utiliser le mode cinéma" 13 | }, 14 | "anyPercent": { 15 | "message": "Quel que soit le pourcentage" 16 | }, 17 | "auto": { 18 | "message": "Automatique" 19 | }, 20 | "desktopOnly": { 21 | "message": "Bureau uniquement" 22 | }, 23 | "disableAutoplay": { 24 | "message": "Désactiver la lecture automatique" 25 | }, 26 | "disableHomeFeed": { 27 | "message": "Désactiver la page d'accueil" 28 | }, 29 | "disableHomeFeedNote": { 30 | "message": "Seulement si connecté(e) - redirige vers les Abonnements" 31 | }, 32 | "downloadTranscript": { 33 | "message": "Rendre la transcription téléchargeable" 34 | }, 35 | "embeddedVideos": { 36 | "message": "Vidéos intégrées" 37 | }, 38 | "enabled": { 39 | "message": "Activé" 40 | }, 41 | "experimentalFeatures": { 42 | "message": "Fonctionnalités expérimentales" 43 | }, 44 | "extensionDescription": { 45 | "message": "Améliore l'interface et rend le contrôle à l'utilisateur sur YouTube en ajoutant des fonctionnalités manquantes", 46 | "description": "<= 112 characters" 47 | }, 48 | "extensionName": { 49 | "message": "Panneau de contrôle pour YouTube" 50 | }, 51 | "features": { 52 | "message": "Fonctionnalités" 53 | }, 54 | "fullSizeTheaterMode": { 55 | "message": "Mode cinéma grand écran" 56 | }, 57 | "fullSizeTheaterModeHideHeader": { 58 | "message": "Masquer l'en-tête" 59 | }, 60 | "fullSizeTheaterModeHideScrollbar": { 61 | "message": "Masquer la barre de défilement" 62 | }, 63 | "hiddenChannelsSummary": { 64 | "message": "Chaîne(s) masquée(s) ($COUNT$)", 65 | "placeholders": { 66 | "count": { 67 | "content": "$1", 68 | "example": "1" 69 | } 70 | } 71 | }, 72 | "hideAI": { 73 | "message": "Masquer les résumés IA" 74 | }, 75 | "hideChannelBanner": { 76 | "message": "Masquer la bannière de la chaîne" 77 | }, 78 | "hideChannels": { 79 | "message": "Masquer les chaînes" 80 | }, 81 | "hideChannelsNote": { 82 | "message": "Ajoute « Masquer la chaîne » au menu de la vidéo" 83 | }, 84 | "hideChat": { 85 | "message": "Masquer le chat" 86 | }, 87 | "hideComments": { 88 | "message": "Masquer les commentaires" 89 | }, 90 | "hideEmbedPauseOverlay": { 91 | "message": "Masquer la superposition de paus" 92 | }, 93 | "hideEmbedShareButton": { 94 | "message": "Masquer le bouton Partager" 95 | }, 96 | "hideEndCards": { 97 | "message": "Masquer les cartes de fin de vidéo" 98 | }, 99 | "hideEndVideos": { 100 | "message": "Masquer le contenu des écrans de fin" 101 | }, 102 | "hideExploreButton": { 103 | "message": "Masquer le bouton « Explorer » sur la page d'accueil" 104 | }, 105 | "hideHiddenVideos": { 106 | "message": "Masquer les vidéos cachées" 107 | }, 108 | "hideHiddenVideosNote": { 109 | "message": "Masque he le bouton « Annuler » après 5 secondes" 110 | }, 111 | "hideHomeCategories": { 112 | "message": "Masquer les catégories de la page d'accueil" 113 | }, 114 | "hideInfoPanels": { 115 | "message": "Masquer les panneaux d'information" 116 | }, 117 | "hideLive": { 118 | "message": "Masquer les vidéos en direct" 119 | }, 120 | "hideMembersOnly": { 121 | "message": "Masquer les vidéos réservées aux membres" 122 | }, 123 | "hideMerchEtc": { 124 | "message": "Masquer Produits dérivés, Offres, etc." 125 | }, 126 | "hideMetadata": { 127 | "message": "Masquer les métadonnées de la vidéo" 128 | }, 129 | "hideMiniplayerButton": { 130 | "message": "Masquer le bouton lecteur réduit" 131 | }, 132 | "hideMixes": { 133 | "message": "Masquer les Mix" 134 | }, 135 | "hideMoviesAndTV": { 136 | "message": "Masquer Films et TV" 137 | }, 138 | "hideNextButton": { 139 | "message": "Masquer le bouton suivant" 140 | }, 141 | "hideNextButtonNote": { 142 | "message": "Quand une vidéo n'est pas dans une playlist" 143 | }, 144 | "hideOpenApp": { 145 | "message": "Masquer les liens « Ouvrir app »" 146 | }, 147 | "hidePlaylists": { 148 | "message": "Masquer les playlists" 149 | }, 150 | "hidePremiumUpsells": { 151 | "message": "Masquer les promotions Premium" 152 | }, 153 | "hideRelated": { 154 | "message": "Masquer les vidéos associées" 155 | }, 156 | "hideShareThanksClip": { 157 | "message": "Masquer Partager/Merci/Clip etc." 158 | }, 159 | "hideShorts": { 160 | "message": "Masquer les Shorts" 161 | }, 162 | "hideShortsMetadataUntilHover": { 163 | "message": "Masquer les métadonnées des Shorts jusqu'au survol" 164 | }, 165 | "hideShortsSuggestedActions": { 166 | "message": "Masquer les actions suggérées" 167 | }, 168 | "hideSponsored": { 169 | "message": "Masquer les vidéos sponsorisées et promotions" 170 | }, 171 | "hideStreamed": { 172 | "message": "Masquer les rediffusions de directs" 173 | }, 174 | "hideSubscriptionsChannelList": { 175 | "message": "Masquer la liste des chaînes dans les abonnements" 176 | }, 177 | "hideSubscriptionsLatestBar": { 178 | "message": "Masquer la barre « Les plus récentes » dans Abonnements" 179 | }, 180 | "hideSuggestedSections": { 181 | "message": "Masquer les sections suggérées" 182 | }, 183 | "hideUpcoming": { 184 | "message": "Masquer les vidéos à venir" 185 | }, 186 | "hideVoiceSearch": { 187 | "message": "Masquer la recherche vocale" 188 | }, 189 | "hideWatched": { 190 | "message": "Masquer les vidéos vues" 191 | }, 192 | "hideWatchedThreshold": { 193 | "message": "Pourcentage visionné" 194 | }, 195 | "homeMaxOption": { 196 | "message": "9 (Accueil max)" 197 | }, 198 | "inHomeAndSubscriptionsNote": { 199 | "message": "Dans Accueil et Abonnements" 200 | }, 201 | "large": { 202 | "message": "Grand" 203 | }, 204 | "medium": { 205 | "message": "Moyen" 206 | }, 207 | "minimumGridItemsPerRow": { 208 | "message": "Nombre minimum d'éléments par ligne de la grille" 209 | }, 210 | "minimumShortsPerRow": { 211 | "message": "Nombre minimum de Shorts par rangée" 212 | }, 213 | "mobileGridView": { 214 | "message": "Utiliser la vue en grille pour Abonnements et Recherche (mode portrait uniquement)" 215 | }, 216 | "mobileOnly": { 217 | "message": "Mobile uniquement" 218 | }, 219 | "pauseChannelTrailers": { 220 | "message": "Mettre en pause automatiquement les bandes-annonces de chaîne" 221 | }, 222 | "qualityFull": { 223 | "message": "Maximum" 224 | }, 225 | "qualityHigh": { 226 | "message": "Haute" 227 | }, 228 | "qualityLow": { 229 | "message": "Basse" 230 | }, 231 | "qualityMedium": { 232 | "message": "Moyenne" 233 | }, 234 | "redirectShorts": { 235 | "message": "Rediriger les Shorts vers le lecteur normal" 236 | }, 237 | "removePink": { 238 | "message": "Supprimer le dégradé rose des barres de progression" 239 | }, 240 | "searchThumbnailSize": { 241 | "message": "Taille des miniatures dans les résultats de recherche" 242 | }, 243 | "shorts": { 244 | "message": "Shorts" 245 | }, 246 | "skipAds": { 247 | "message": "Ignorer les publicités" 248 | }, 249 | "small": { 250 | "message": "Petit" 251 | }, 252 | "snapshotFormat": { 253 | "message": "Format de capture" 254 | }, 255 | "snapshotQuality": { 256 | "message": "Qualité" 257 | }, 258 | "stopShortsLooping": { 259 | "message": "Arrêter la lecture en boucle des Shorts" 260 | }, 261 | "tidyGuideSidebar": { 262 | "message": "Nettoyer la barre latérale du guide" 263 | }, 264 | "uiTweaks": { 265 | "message": "Amélioration de l'interface utilisateur" 266 | }, 267 | "videoLists": { 268 | "message": "Listes de vidéos" 269 | }, 270 | "videoPages": { 271 | "message": "Pages de vidéo" 272 | } 273 | } -------------------------------------------------------------------------------- /_locales/ja/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "addTakeSnapshot": { 3 | "message": "ビデオメニューにスナップショットを追加" 4 | }, 5 | "alwaysShowShortsProgressBar": { 6 | "message": "常に進行状況バーを表示" 7 | }, 8 | "alwaysUseOriginalAudio": { 9 | "message": "常にオリジナル音声を使用する" 10 | }, 11 | "alwaysUseTheaterMode": { 12 | "message": "常にシアターモードを使用する" 13 | }, 14 | "anyPercent": { 15 | "message": "割合によらず" 16 | }, 17 | "auto": { 18 | "message": "自動" 19 | }, 20 | "desktopOnly": { 21 | "message": "デスクトップのみ" 22 | }, 23 | "disableAutoplay": { 24 | "message": "自動再生を無効にする" 25 | }, 26 | "disableHomeFeed": { 27 | "message": "ホームフィードを無効にする" 28 | }, 29 | "disableHomeFeedNote": { 30 | "message": "ログイン時に登録チャンネルへリダイレクト" 31 | }, 32 | "downloadTranscript": { 33 | "message": "文字起こしをダウンロード可能にする" 34 | }, 35 | "embeddedVideos": { 36 | "message": "埋め込み動画" 37 | }, 38 | "enabled": { 39 | "message": "有効" 40 | }, 41 | "experimentalFeatures": { 42 | "message": "実験的な機能" 43 | }, 44 | "extensionDescription": { 45 | "message": "YouTubeに不足しているオプションやUIの改善を追加し、より自由にカスタマイズできるようにします" 46 | }, 47 | "extensionName": { 48 | "message": "Control Panel for YouTube" 49 | }, 50 | "features": { 51 | "message": "機能" 52 | }, 53 | "fullSizeTheaterMode": { 54 | "message": "フルサイズシアターモード" 55 | }, 56 | "fullSizeTheaterModeHideHeader": { 57 | "message": "ヘッダーを非表示にする" 58 | }, 59 | "fullSizeTheaterModeHideScrollbar": { 60 | "message": "スクロールバーを非表示にする" 61 | }, 62 | "hiddenChannelsSummary": { 63 | "message": "隠しチャンネル ($COUNT$)", 64 | "placeholders": { 65 | "count": { 66 | "content": "$1", 67 | "example": "1" 68 | } 69 | } 70 | }, 71 | "hideAI": { 72 | "message": "AIによる概要を非表示" 73 | }, 74 | "hideChannelBanner": { 75 | "message": "チャンネルバナー画像を非表示にする" 76 | }, 77 | "hideChannels": { 78 | "message": "チャンネルを非表示" 79 | }, 80 | "hideChannelsNote": { 81 | "message": "動画メニューに「チャンネルを非表示」オプションを追加" 82 | }, 83 | "hideChat": { 84 | "message": "チャットを非表示" 85 | }, 86 | "hideComments": { 87 | "message": "コメントを非表示" 88 | }, 89 | "hideEmbedPauseOverlay": { 90 | "message": "一時停止オーバーレイを非表示" 91 | }, 92 | "hideEmbedShareButton": { 93 | "message": "共有ボタンを非表示" 94 | }, 95 | "hideEndCards": { 96 | "message": "動画のエンドカードを非表示" 97 | }, 98 | "hideEndVideos": { 99 | "message": "動画の終了画面を非表示" 100 | }, 101 | "hideExploreButton": { 102 | "message": "ホームで検索ボタンを非表示" 103 | }, 104 | "hideHiddenVideos": { 105 | "message": "非表示にされた動画を非表示" 106 | }, 107 | "hideHiddenVideosNote": { 108 | "message": "「元に戻す」ボタンを5秒後に非表示" 109 | }, 110 | "hideHomeCategories": { 111 | "message": "ホームのカテゴリを非表示" 112 | }, 113 | "hideInfoPanels": { 114 | "message": "情報パネルを非表示" 115 | }, 116 | "hideLive": { 117 | "message": "ライブ動画を非表示" 118 | }, 119 | "hideMembersOnly": { 120 | "message": "メンバー限定動画を非表示にする" 121 | }, 122 | "hideMerchEtc": { 123 | "message": "商品、オファーなどを非表示" 124 | }, 125 | "hideMetadata": { 126 | "message": "動画のメタデータを非表示" 127 | }, 128 | "hideMiniplayerButton": { 129 | "message": "ミニプレイヤーボタンを非表示" 130 | }, 131 | "hideMixes": { 132 | "message": "ミックスを非表示" 133 | }, 134 | "hideMoviesAndTV": { 135 | "message": "映画とテレビを非表示" 136 | }, 137 | "hideNextButton": { 138 | "message": "次へボタンを非表示" 139 | }, 140 | "hideNextButtonNote": { 141 | "message": "プレイリスト内の動画を視聴していない場合" 142 | }, 143 | "hideOpenApp": { 144 | "message": "アプリを開くリンクを非表示" 145 | }, 146 | "hidePlaylists": { 147 | "message": "再生リストを非表示にする" 148 | }, 149 | "hidePremiumUpsells": { 150 | "message": "プレミアムのアップセルを非表示にする" 151 | }, 152 | "hideRelated": { 153 | "message": "関連動画を非表示" 154 | }, 155 | "hideShareThanksClip": { 156 | "message": "共有/Thanks/クリップなどを非表示" 157 | }, 158 | "hideShorts": { 159 | "message": "ショート動画を非表示" 160 | }, 161 | "hideShortsMetadataUntilHover": { 162 | "message": "ホバーするまでショートのメタデータを隠す" 163 | }, 164 | "hideShortsSuggestedActions": { 165 | "message": "提案アクションを非表示" 166 | }, 167 | "hideSponsored": { 168 | "message": "スポンサー動画とプロモーションを非表示" 169 | }, 170 | "hideStreamed": { 171 | "message": "配信済みの動画を非表示" 172 | }, 173 | "hideSubscriptionsChannelList": { 174 | "message": "登録チャンネルのチャンネルリストを非表示" 175 | }, 176 | "hideSubscriptionsLatestBar": { 177 | "message": "登録チャンネルの「新しい順」バーを非表示" 178 | }, 179 | "hideSuggestedSections": { 180 | "message": "おすすめセクションを非表示" 181 | }, 182 | "hideUpcoming": { 183 | "message": "公開予定の動画を非表示" 184 | }, 185 | "hideVoiceSearch": { 186 | "message": "音声検索を非表示" 187 | }, 188 | "hideWatched": { 189 | "message": "視聴済みの動画を非表示" 190 | }, 191 | "hideWatchedThreshold": { 192 | "message": "視聴済みの割合" 193 | }, 194 | "homeMaxOption": { 195 | "message": "9 (ホーム最大)" 196 | }, 197 | "inHomeAndSubscriptionsNote": { 198 | "message": "ホームと登録チャンネルで適用" 199 | }, 200 | "large": { 201 | "message": "大" 202 | }, 203 | "medium": { 204 | "message": "中" 205 | }, 206 | "minimumGridItemsPerRow": { 207 | "message": "1行あたりの最小表示数" 208 | }, 209 | "minimumShortsPerRow": { 210 | "message": "1行あたりの最小ショート数" 211 | }, 212 | "mobileGridView": { 213 | "message": "購読と検索にグリッドビューを使用する(縦画面のみ)" 214 | }, 215 | "mobileOnly": { 216 | "message": "モバイルのみ" 217 | }, 218 | "pauseChannelTrailers": { 219 | "message": "チャンネルのトレーラーを自動的に一時停止" 220 | }, 221 | "qualityFull": { 222 | "message": "最高" 223 | }, 224 | "qualityHigh": { 225 | "message": "高" 226 | }, 227 | "qualityLow": { 228 | "message": "低" 229 | }, 230 | "qualityMedium": { 231 | "message": "中" 232 | }, 233 | "redirectShorts": { 234 | "message": "ショート動画を通常のプレイヤーにリダイレクト" 235 | }, 236 | "removePink": { 237 | "message": "進捗バーからピンクのグラデーションを削除" 238 | }, 239 | "searchThumbnailSize": { 240 | "message": "検索サムネイルのサイズ" 241 | }, 242 | "shorts": { 243 | "message": "ショート" 244 | }, 245 | "skipAds": { 246 | "message": "広告をスキップ" 247 | }, 248 | "small": { 249 | "message": "小" 250 | }, 251 | "snapshotFormat": { 252 | "message": "スナップショット形式" 253 | }, 254 | "snapshotQuality": { 255 | "message": "品質" 256 | }, 257 | "stopShortsLooping": { 258 | "message": "ショートのループ再生を停止" 259 | }, 260 | "tidyGuideSidebar": { 261 | "message": "サイドバーを整理する" 262 | }, 263 | "uiTweaks": { 264 | "message": "UIの微調整" 265 | }, 266 | "videoLists": { 267 | "message": "動画リスト" 268 | }, 269 | "videoPages": { 270 | "message": "動画ページ" 271 | } 272 | } -------------------------------------------------------------------------------- /_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "addTakeSnapshot": { 3 | "message": "将截图功能添加到视频菜单" 4 | }, 5 | "alwaysShowShortsProgressBar": { 6 | "message": "始终显示进度条" 7 | }, 8 | "alwaysUseOriginalAudio": { 9 | "message": "始终使用原始音频" 10 | }, 11 | "alwaysUseTheaterMode": { 12 | "message": "始终剧院模式" 13 | }, 14 | "anyPercent": { 15 | "message": "任何 %" 16 | }, 17 | "auto": { 18 | "message": "自动" 19 | }, 20 | "desktopOnly": { 21 | "message": "仅限桌面" 22 | }, 23 | "disableAutoplay": { 24 | "message": "禁用自动播放" 25 | }, 26 | "disableHomeFeed": { 27 | "message": "关闭首页动态" 28 | }, 29 | "disableHomeFeedNote": { 30 | "message": "仅当登录时——重定向到订阅页面" 31 | }, 32 | "downloadTranscript": { 33 | "message": "使转录稿可下载" 34 | }, 35 | "embeddedVideos": { 36 | "message": "嵌入式视频" 37 | }, 38 | "enabled": { 39 | "message": "启用" 40 | }, 41 | "experimentalFeatures": { 42 | "message": "实验性功能" 43 | }, 44 | "extensionDescription": { 45 | "message": "通过添加缺失的选项和界面改进,让您更好地控制 YouTube", 46 | "description": "<= 112 characters" 47 | }, 48 | "extensionName": { 49 | "message": "YouTube 控制面板" 50 | }, 51 | "features": { 52 | "message": "特点" 53 | }, 54 | "fullSizeTheaterMode": { 55 | "message": "全屏影院模式" 56 | }, 57 | "fullSizeTheaterModeHideHeader": { 58 | "message": "隐藏标题栏" 59 | }, 60 | "fullSizeTheaterModeHideScrollbar": { 61 | "message": "隐藏滚动条" 62 | }, 63 | "hiddenChannelsSummary": { 64 | "message": "隐藏频道 ($COUNT$)", 65 | "placeholders": { 66 | "count": { 67 | "content": "$1", 68 | "example": "1" 69 | } 70 | } 71 | }, 72 | "hideAI": { 73 | "message": "隐藏AI摘要" 74 | }, 75 | "hideChannelBanner": { 76 | "message": "隐藏频道横幅图片" 77 | }, 78 | "hideChannels": { 79 | "message": "隐藏频道" 80 | }, 81 | "hideChannelsNote": { 82 | "message": "添加 \"隐藏频道\" 前往视频菜单" 83 | }, 84 | "hideChat": { 85 | "message": "隐藏聊天" 86 | }, 87 | "hideComments": { 88 | "message": "隐藏注释" 89 | }, 90 | "hideEmbedPauseOverlay": { 91 | "message": "隐藏暂停覆盖" 92 | }, 93 | "hideEmbedShareButton": { 94 | "message": "隐藏分享按钮" 95 | }, 96 | "hideEndCards": { 97 | "message": "隐藏视频结束卡" 98 | }, 99 | "hideEndVideos": { 100 | "message": "隐藏视频片尾画面内容" 101 | }, 102 | "hideExploreButton": { 103 | "message": "在主页中隐藏“探索”按钮" 104 | }, 105 | "hideHiddenVideos": { 106 | "message": "隐藏已隐藏的视频" 107 | }, 108 | "hideHiddenVideosNote": { 109 | "message": "隐藏 \"撤消\" 5 秒后按" 110 | }, 111 | "hideHomeCategories": { 112 | "message": "隐藏主页中的类别" 113 | }, 114 | "hideInfoPanels": { 115 | "message": "隐藏信息面板" 116 | }, 117 | "hideLive": { 118 | "message": "隐藏视频直播" 119 | }, 120 | "hideMembersOnly": { 121 | "message": "隐藏会员专属视频" 122 | }, 123 | "hideMerchEtc": { 124 | "message": "隐藏商品、优惠等." 125 | }, 126 | "hideMetadata": { 127 | "message": "隐藏视频元数据" 128 | }, 129 | "hideMiniplayerButton": { 130 | "message": "隐藏迷你播放器按钮" 131 | }, 132 | "hideMixes": { 133 | "message": "隐藏混音" 134 | }, 135 | "hideMoviesAndTV": { 136 | "message": "隐藏电影和电视" 137 | }, 138 | "hideNextButton": { 139 | "message": "隐藏“下一步”按钮" 140 | }, 141 | "hideNextButtonNote": { 142 | "message": "当不在播放列表中观看视频时" 143 | }, 144 | "hideOpenApp": { 145 | "message": "隐藏打开应用程序链接" 146 | }, 147 | "hidePlaylists": { 148 | "message": "隐藏播放列表" 149 | }, 150 | "hidePremiumUpsells": { 151 | "message": "隐藏Premium升级提示" 152 | }, 153 | "hideRelated": { 154 | "message": "隐藏相关视频" 155 | }, 156 | "hideShareThanksClip": { 157 | "message": "隐藏分享/点赞/剪辑等" 158 | }, 159 | "hideShorts": { 160 | "message": "隐藏短视频" 161 | }, 162 | "hideShortsMetadataUntilHover": { 163 | "message": "隐藏Shorts元数据直至悬停" 164 | }, 165 | "hideShortsSuggestedActions": { 166 | "message": "隐藏建议操作" 167 | }, 168 | "hideSponsored": { 169 | "message": "隐藏赞助视频和宣传片" 170 | }, 171 | "hideStreamed": { 172 | "message": "隐藏流媒体视频" 173 | }, 174 | "hideSubscriptionsChannelList": { 175 | "message": "在订阅中隐藏频道列表" 176 | }, 177 | "hideSubscriptionsLatestBar": { 178 | "message": "隐藏 \"最新消息\" 订阅栏" 179 | }, 180 | "hideSuggestedSections": { 181 | "message": "隐藏建议的部分" 182 | }, 183 | "hideUpcoming": { 184 | "message": "隐藏即将播放的视频" 185 | }, 186 | "hideVoiceSearch": { 187 | "message": "隐藏语音搜索" 188 | }, 189 | "hideWatched": { 190 | "message": "隐藏已观看的视频" 191 | }, 192 | "hideWatchedThreshold": { 193 | "message": "观看 %" 194 | }, 195 | "homeMaxOption": { 196 | "message": "9 (首页最大)" 197 | }, 198 | "inHomeAndSubscriptionsNote": { 199 | "message": "在主页和订阅中" 200 | }, 201 | "large": { 202 | "message": "大号" 203 | }, 204 | "medium": { 205 | "message": "中号" 206 | }, 207 | "minimumGridItemsPerRow": { 208 | "message": "每行最小缩略图个数" 209 | }, 210 | "minimumShortsPerRow": { 211 | "message": "每行最少 Shorts 数量" 212 | }, 213 | "mobileGridView": { 214 | "message": "使用缩视图进行订阅和搜索(仅限纵向)" 215 | }, 216 | "mobileOnly": { 217 | "message": "仅限移动设备" 218 | }, 219 | "pauseChannelTrailers": { 220 | "message": "自动暂停频道预告片" 221 | }, 222 | "qualityFull": { 223 | "message": "最高" 224 | }, 225 | "qualityHigh": { 226 | "message": "高" 227 | }, 228 | "qualityLow": { 229 | "message": "低" 230 | }, 231 | "qualityMedium": { 232 | "message": "中" 233 | }, 234 | "redirectShorts": { 235 | "message": "将短视频重定向至普通播放器" 236 | }, 237 | "removePink": { 238 | "message": "从进度条中移除粉色渐变" 239 | }, 240 | "searchThumbnailSize": { 241 | "message": "搜索缩略图大小" 242 | }, 243 | "skipAds": { 244 | "message": "跳过广告" 245 | }, 246 | "small": { 247 | "message": "小号" 248 | }, 249 | "snapshotFormat": { 250 | "message": "截图格式" 251 | }, 252 | "snapshotQuality": { 253 | "message": "质量" 254 | }, 255 | "stopShortsLooping": { 256 | "message": "停止 Shorts 循环播放" 257 | }, 258 | "tidyGuideSidebar": { 259 | "message": "整理侧边栏订阅" 260 | }, 261 | "uiTweaks": { 262 | "message": "界面调整" 263 | }, 264 | "videoLists": { 265 | "message": "视频列表" 266 | }, 267 | "videoPages": { 268 | "message": "视频页面" 269 | } 270 | } -------------------------------------------------------------------------------- /content.js: -------------------------------------------------------------------------------- 1 | /** @type {BroadcastChannel} */ 2 | let channel 3 | /** @type {Set} */ 4 | let configKeys 5 | 6 | window.addEventListener('message', (event) => { 7 | if (event.source !== window) return 8 | if (event.data.type != 'init' || !event.data.channelName || !event.data.configKeys) return 9 | channel = new BroadcastChannel(event.data.channelName) 10 | configKeys = new Set(event.data.configKeys) 11 | channel.addEventListener('message', storeConfigChangesFromPageScript) 12 | chrome.storage.local.get((storedConfig) => { 13 | let siteConfig = Object.fromEntries( 14 | Object.entries(storedConfig).filter(([key]) => configKeys.has(key)) 15 | ) 16 | chrome.storage.local.onChanged.addListener(onStorageChanged) 17 | channel.postMessage({type: 'initial', siteConfig}) 18 | }) 19 | }) 20 | 21 | /** @param {{[key: string]: chrome.storage.StorageChange}} storageChanges */ 22 | function onStorageChanged(storageChanges) { 23 | /** @type {Partial} */ 24 | let siteConfig = Object.fromEntries( 25 | Object.entries(storageChanges) 26 | .filter(([key]) => configKeys.has(key)) 27 | .map(([key, {newValue}]) => [key, newValue]) 28 | ) 29 | 30 | // Ignore storage changes which aren't relevant to the page script 31 | if (Object.keys(siteConfig).length == 0) return 32 | 33 | channel.postMessage({type: 'change', siteConfig}) 34 | } 35 | 36 | /** @param {MessageEvent>} message */ 37 | function storeConfigChangesFromPageScript({data: changes}) { 38 | chrome.storage.local.onChanged.removeListener(onStorageChanged) 39 | chrome.storage.local.set(changes, () => { 40 | chrome.storage.local.onChanged.addListener(onStorageChanged) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /embed.js: -------------------------------------------------------------------------------- 1 | let debug = false 2 | 3 | function log(...args) { 4 | if (debug) { 5 | console.log('🛏️', ...args) 6 | } 7 | } 8 | 9 | //#region Default config 10 | /** @type {import("./types").EmbedConfig} */ 11 | let config = { 12 | debug: false, 13 | enabled: true, 14 | hideEmbedPauseOverlay: true, 15 | hideEmbedShareButton: false, 16 | hideEndCards: false, 17 | hideEndVideos: true, 18 | hideInfoPanels: false, 19 | removePink: false, 20 | } 21 | //#endregion 22 | 23 | //#region Utility functions 24 | function addStyle(css = '') { 25 | let $style = document.createElement('style') 26 | $style.dataset.insertedBy = 'control-panel-for-youtube' 27 | if (css) { 28 | $style.textContent = css 29 | } 30 | document.head.appendChild($style) 31 | return $style 32 | } 33 | 34 | /** 35 | * @param {string} str 36 | * @return {string} 37 | */ 38 | function dedent(str) { 39 | str = str.replace(/^[ \t]*\r?\n/, '') 40 | let indent = /^[ \t]+/m.exec(str) 41 | if (indent) str = str.replace(new RegExp('^' + indent[0], 'gm'), '') 42 | return str.replace(/(\r?\n)[ \t]+$/, '$1') 43 | } 44 | //#endregion 45 | 46 | //#region CSS 47 | const configureCss = (() => { 48 | /** @type {HTMLStyleElement} */ 49 | let $style 50 | 51 | return function configureCss() { 52 | if (!config.enabled) { 53 | log('removing stylesheet') 54 | $style?.remove() 55 | $style = null 56 | return 57 | } 58 | 59 | let cssRules = [] 60 | let hideCssSelectors = [] 61 | 62 | if (config.hideEmbedPauseOverlay) { 63 | hideCssSelectors.push('.ytp-pause-overlay-container') 64 | } 65 | 66 | if (config.hideEmbedShareButton) { 67 | hideCssSelectors.push('.ytp-share-button') 68 | } 69 | 70 | if (config.hideEndCards) { 71 | hideCssSelectors.push('.ytp-ce-element') 72 | } 73 | 74 | if (config.hideEndVideos) { 75 | hideCssSelectors.push('.videowall-endscreen') 76 | } 77 | 78 | if (config.hideInfoPanels) { 79 | hideCssSelectors.push('.ytp-info-panel-preview') 80 | } 81 | 82 | if (config.removePink) { 83 | cssRules.push(` 84 | .ytp-play-progress { 85 | background: #f03 !important; 86 | } 87 | `) 88 | } 89 | 90 | if (hideCssSelectors.length > 0) { 91 | cssRules.push(` 92 | ${hideCssSelectors.join(',\n')} { 93 | display: none !important; 94 | } 95 | `) 96 | } 97 | 98 | let css = cssRules.map(dedent).join('\n') 99 | if ($style == null) { 100 | $style = addStyle(css) 101 | } else { 102 | $style.textContent = css 103 | } 104 | } 105 | })() 106 | //#endregion 107 | 108 | //#region Main 109 | function main() { 110 | if (config.enabled) { 111 | configureCss() 112 | } 113 | } 114 | 115 | /** @param {Partial} changes */ 116 | function configChanged(changes) { 117 | if (!changes.hasOwnProperty('enabled')) { 118 | log('config changed', changes) 119 | configureCss() 120 | return 121 | } 122 | 123 | log(`${changes.enabled ? 'en' : 'dis'}abling extension functionality`) 124 | configureCss() 125 | } 126 | 127 | /** @param {{[key: string]: chrome.storage.StorageChange}} storageChanges */ 128 | function onConfigChange(storageChanges) { 129 | let configChanges = Object.fromEntries( 130 | Object.entries(storageChanges) 131 | .filter(([key]) => config.hasOwnProperty(key)) 132 | .map(([key, {newValue}]) => [key, newValue]) 133 | ) 134 | if (Object.keys(configChanges).length == 0) return 135 | 136 | if ('debug' in configChanges) { 137 | log('disabling debug mode') 138 | debug = configChanges.debug 139 | log('enabled debug mode') 140 | return 141 | } 142 | 143 | Object.assign(config, configChanges) 144 | configChanged(configChanges) 145 | } 146 | 147 | chrome.storage.local.get((storedConfig) => { 148 | Object.assign( 149 | config, 150 | Object.fromEntries( 151 | Object.entries(storedConfig).filter(([key]) => config.hasOwnProperty(key)) 152 | ) 153 | ) 154 | debug = config.debug 155 | log('initial config', config) 156 | chrome.storage.local.onChanged.addListener(onConfigChange) 157 | window.addEventListener('unload', () => { 158 | chrome.storage.local.onChanged.removeListener(onConfigChange) 159 | }, {once: true}) 160 | main() 161 | }) 162 | 163 | // XXX chrome.storage.local.get() callback isn't getting called in Safari - run with default settings 164 | if (navigator.userAgent.includes('Safari/') && !/Chrom(e|ium)\//.test(navigator.userAgent)) { 165 | main() 166 | } 167 | //#endregion -------------------------------------------------------------------------------- /icons/chrome-webstore-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/icons/chrome-webstore-icon.png -------------------------------------------------------------------------------- /icons/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/icons/icon128.png -------------------------------------------------------------------------------- /icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/icons/icon16.png -------------------------------------------------------------------------------- /icons/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/icons/icon256.png -------------------------------------------------------------------------------- /icons/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/icons/icon32.png -------------------------------------------------------------------------------- /icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/icons/icon48.png -------------------------------------------------------------------------------- /icons/icon512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/icons/icon512.png -------------------------------------------------------------------------------- /icons/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/icons/icon64.png -------------------------------------------------------------------------------- /icons/icon96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/icons/icon96.png -------------------------------------------------------------------------------- /icons/toolbar-icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/icons/toolbar-icon16.png -------------------------------------------------------------------------------- /icons/toolbar-icon19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/icons/toolbar-icon19.png -------------------------------------------------------------------------------- /icons/toolbar-icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/icons/toolbar-icon32.png -------------------------------------------------------------------------------- /icons/toolbar-icon38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/icons/toolbar-icon38.png -------------------------------------------------------------------------------- /icons/toolbar-icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/icons/toolbar-icon48.png -------------------------------------------------------------------------------- /icons/toolbar-icon72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/icons/toolbar-icon72.png -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "checkJs": true, 4 | "module": "NodeNext", 5 | "moduleDetection": "force", 6 | "moduleResolution": "nodenext", 7 | "target": "ES2022" 8 | }, 9 | "exclude": [ 10 | "node_modules" 11 | ] 12 | } -------------------------------------------------------------------------------- /manifest.mv2.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "default_locale": "en", 4 | "name": "__MSG_extensionName__", 5 | "description": "__MSG_extensionDescription__", 6 | "homepage_url": "https://soitis.dev/control-panel-for-youtube", 7 | "version": "1.11.2", 8 | "icons": { 9 | "16": "icons/icon16.png", 10 | "32": "icons/icon32.png", 11 | "48": "icons/icon48.png", 12 | "64": "icons/icon64.png", 13 | "96": "icons/icon96.png", 14 | "128": "icons/icon128.png" 15 | }, 16 | "content_scripts": [ 17 | { 18 | "matches": [ 19 | "https://www.youtube.com/*", 20 | "https://m.youtube.com/*" 21 | ], 22 | "exclude_matches": [ 23 | "https://www.youtube.com/embed/*" 24 | ], 25 | "js": [ 26 | "content.js" 27 | ] 28 | }, 29 | { 30 | "world": "MAIN", 31 | "matches": [ 32 | "https://www.youtube.com/*", 33 | "https://m.youtube.com/*" 34 | ], 35 | "exclude_matches": [ 36 | "https://www.youtube.com/embed/*" 37 | ], 38 | "js": [ 39 | "page.js" 40 | ] 41 | }, 42 | { 43 | "matches": [ 44 | "https://www.youtube.com/embed/*", 45 | "https://www.youtube-nocookie.com/embed/*" 46 | ], 47 | "js": [ 48 | "embed.js" 49 | ], 50 | "all_frames": true 51 | } 52 | ], 53 | "options_ui": { 54 | "browser_style": true, 55 | "page": "options.html" 56 | }, 57 | "browser_action": { 58 | "browser_style": true, 59 | "default_title": "__MSG_extensionName__", 60 | "default_popup": "browser_action.html" 61 | }, 62 | "permissions": [ 63 | "storage" 64 | ], 65 | "browser_specific_settings": { 66 | "gecko": { 67 | "id": "control-panel-for-youtube@jbscript.dev", 68 | "strict_min_version": "121.0" 69 | }, 70 | "gecko_android": { 71 | "strict_min_version": "121.0" 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /manifest.mv3.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "default_locale": "en", 4 | "name": "__MSG_extensionName__", 5 | "description": "__MSG_extensionDescription__", 6 | "homepage_url": "https://soitis.dev/control-panel-for-youtube", 7 | "version": "1.11.2", 8 | "icons": { 9 | "16": "icons/icon16.png", 10 | "32": "icons/icon32.png", 11 | "48": "icons/icon48.png", 12 | "64": "icons/icon64.png", 13 | "96": "icons/icon96.png", 14 | "128": "icons/icon128.png" 15 | }, 16 | "content_scripts": [ 17 | { 18 | "matches": [ 19 | "https://www.youtube.com/*", 20 | "https://m.youtube.com/*" 21 | ], 22 | "exclude_matches": [ 23 | "https://www.youtube.com/embed/*" 24 | ], 25 | "js": [ 26 | "content.js" 27 | ] 28 | }, 29 | { 30 | "world": "MAIN", 31 | "matches": [ 32 | "https://www.youtube.com/*", 33 | "https://m.youtube.com/*" 34 | ], 35 | "exclude_matches": [ 36 | "https://www.youtube.com/embed/*" 37 | ], 38 | "js": [ 39 | "page.js" 40 | ] 41 | }, 42 | { 43 | "matches": [ 44 | "https://www.youtube.com/embed/*", 45 | "https://www.youtube-nocookie.com/embed/*" 46 | ], 47 | "js": [ 48 | "embed.js" 49 | ], 50 | "all_frames": true 51 | } 52 | ], 53 | "options_ui": { 54 | "page": "options.html" 55 | }, 56 | "action": { 57 | "default_title": "__MSG_extensionName__", 58 | "default_popup": "browser_action.html" 59 | }, 60 | "permissions": [ 61 | "storage" 62 | ] 63 | } -------------------------------------------------------------------------------- /options-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/options-icon.png -------------------------------------------------------------------------------- /options.css: -------------------------------------------------------------------------------- 1 | :root { 2 | color-scheme: light dark; 3 | --border-color: #f0f0f0; 4 | } 5 | 6 | body { 7 | user-select: none; 8 | padding: 0; 9 | margin: 0; 10 | /* XXX These are defaults for Chrome, do something else instead */ 11 | font-size: 13px; 12 | font-family: system-ui, sans-serif; 13 | } 14 | 15 | body.browserAction { 16 | min-width: 400px; 17 | } 18 | body.iOS.browserAction { 19 | min-width: 0; 20 | } 21 | body.macOS.browserAction { 22 | min-width: 340px; 23 | } 24 | 25 | body.desktop .mobile, 26 | body.mobile .desktop, 27 | body:not(.debug) .debug, 28 | body:not(.fullSizeTheaterMode) .fullSizeTheaterMode, 29 | body:not(.hiddenChannels) #hiddenChannelsDetails, 30 | body:not(.hidingWatched) .hidingWatched, 31 | body:not(.jpegSnapshot) .jpegSnapshot, 32 | body:not(.snapshot) .snapshot { 33 | display: none; 34 | } 35 | 36 | body:not(.iOS.safari) .toggle { 37 | display: none; 38 | } 39 | 40 | label { 41 | display: flex; 42 | justify-content: space-between; 43 | align-items: center; 44 | padding: 4px 12px; 45 | margin: 8px 0; 46 | cursor: pointer; 47 | } 48 | 49 | input[type=checkbox], 50 | select { 51 | cursor: pointer; 52 | } 53 | 54 | input[type=checkbox] { 55 | margin-left: 12px; 56 | flex-shrink: 0; 57 | } 58 | 59 | section.group > label:not(.checkbox) { 60 | cursor: default; 61 | } 62 | 63 | section:not(:first-of-type) { 64 | border-top: 1px solid var(--border-color); 65 | } 66 | 67 | section.group > section { 68 | margin-left: 40px; 69 | } 70 | 71 | section.group > label { 72 | margin-bottom: 12px; 73 | } 74 | 75 | form > section.group:first-of-type > label { 76 | margin-bottom: 8px; 77 | } 78 | 79 | section.group > section > * { 80 | color: rgb(95, 99, 104); 81 | padding-left: 0; 82 | } 83 | 84 | section.group > p { 85 | margin: 0 0 12px 40px; 86 | font-size: 12px; 87 | } 88 | 89 | section.group > section > p { 90 | margin: 0 40px 12px 0; 91 | font-size: 12px; 92 | } 93 | 94 | details { 95 | margin-left: 12px; 96 | } 97 | 98 | summary { 99 | padding-top: 4px; 100 | padding-bottom: 4px; 101 | margin-top: 8px; 102 | margin-bottom: 8px; 103 | cursor: pointer; 104 | } 105 | 106 | .icon { 107 | height: 1.25em; 108 | } 109 | 110 | body.disabled .icon { 111 | filter: grayscale(100%); 112 | } 113 | 114 | body.disabled form > section ~ section { 115 | visibility: hidden; 116 | } 117 | 118 | body.disabled:not(.safari) form > section { 119 | border-bottom: 1px solid var(--border-color); 120 | } 121 | 122 | #version { 123 | text-align: center; 124 | font-size: 75%; 125 | margin-top: 12px; 126 | margin-bottom: 8px; 127 | } 128 | body.disabled #version { 129 | display: none; 130 | } 131 | 132 | /* Firefox overrides */ 133 | @-moz-document url-prefix() { 134 | body { 135 | font-family: inherit; 136 | font-size: 15px; 137 | } 138 | body.browserAction { 139 | min-width: auto; 140 | max-width: 400px; 141 | } 142 | section { 143 | --border-color: #d7d7db; 144 | } 145 | section.group > p, 146 | section.group > section > * { 147 | color: rgb(91, 91, 102); 148 | } 149 | section.group > p, 150 | section.group > section > p { 151 | font-size: 14px; 152 | } 153 | } 154 | 155 | /* Edge overrides */ 156 | body.edge { 157 | font-size: 14px; 158 | } 159 | body.edge section { 160 | --border-color: #B6B6B6; 161 | } 162 | body.edge section.group > label { 163 | font-weight: 600; 164 | } 165 | body.edge section.group > p, 166 | body.edge section.group > section > * { 167 | color: #767676; 168 | } 169 | 170 | /* Safari overrides */ 171 | body.safari { 172 | -webkit-user-select: none; 173 | } 174 | 175 | /* Safari overrides (macOS) */ 176 | body.macOS.safari form { 177 | padding: 4px 0; 178 | } 179 | /* Space option groups */ 180 | body.macOS.safari form > section:not(:first-of-type) { 181 | margin-top: 20px; 182 | } 183 | /* Add colons to option group labels */ 184 | body.macOS.safari section.group > label:not(.checkbox)::after { 185 | content: ":"; 186 | } 187 | body.macOS.safari form > section.group > label { 188 | margin-bottom: 0; 189 | } 190 | /* Indent options to align with the toggle all checkbox if there is one */ 191 | body.macOS.safari section.group > section { 192 | margin-left: 32px; 193 | } 194 | /* Align option group help text with options */ 195 | body.macOS.safari form > section.group > p { 196 | margin: 12px 12px 12px 32px; 197 | } 198 | /* Don't display dividing lines between option groups or options */ 199 | body.macOS.safari section { 200 | border-top: none; 201 | } 202 | /* Indent nested options to align with checkbox labels */ 203 | body.macOS.safari section.group > section > section { 204 | margin-left: 8px; 205 | } 206 | /* Put controls next to their labels... */ 207 | body.macOS.safari label { 208 | justify-content: start; 209 | align-items: center; 210 | padding-top: 0; 211 | padding-bottom: 0; 212 | } 213 | /* Put checkboxes before their labels */ 214 | body.macOS.safari section.checkbox > label, 215 | body.macOS.safari label.checkbox { 216 | flex-direction: row-reverse; 217 | } 218 | body.macOS.safari input[type=checkbox] { 219 | margin-left: 0; 220 | margin-right: 6px; 221 | } 222 | /* Add colons to dropdown labels */ 223 | body.macOS.safari section.select label { 224 | display: block; 225 | } 226 | body.macOS.safari section.select span::after { 227 | content: ": "; 228 | display: inline-block 229 | } 230 | /* Align help text with checkbox labels */ 231 | body.macOS.safari section.checkbox > p { 232 | margin-left: 20px; 233 | margin-right: 12px; 234 | } 235 | body.macOS.safari p { 236 | color: rgb(123, 123, 123) !important; 237 | } 238 | body.macOS.safari section.group > section > * { 239 | color: inherit; 240 | } 241 | 242 | /* Safari overrides (iOS) */ 243 | body.iOS.safari { 244 | background-color: rgb(240, 240, 245); 245 | padding: 18px; 246 | font-size: inherit; 247 | } 248 | body.iOS.safari label { 249 | /* Prevent flash when labels are tapped */ 250 | -webkit-tap-highlight-color: transparent; 251 | padding-left: 0; 252 | padding-right: 18px; 253 | } 254 | /* Option groups should contain their options in a rounded box */ 255 | body.iOS.safari form > section { 256 | background-color: white; 257 | border-radius: 10px; 258 | padding-left: 18px; 259 | padding-bottom: 1px; 260 | padding-top: 1px; 261 | border-top: none !important; 262 | } 263 | body.iOS.safari form > section:not(:first-of-type) { 264 | margin-top: 34px; 265 | } 266 | /* All options should have a dividing line */ 267 | body.iOS.safari section.group section { 268 | border-top: 1px solid var(--border-color); 269 | } 270 | /* Options should not be indented by default */ 271 | body.iOS.safari section.group > section { 272 | margin-left: 0; 273 | } 274 | /* Groups which have a toggle for all their options should indent their options */ 275 | body.iOS.safari section.group > label.checkbox ~ section, 276 | /* Nested groups should indent their options */ 277 | body.iOS.safari section.group > section.group > section { 278 | margin-left: 18px; 279 | } 280 | /* Labelled option groups should display their label above the box */ 281 | body.iOS.safari form > section.labelled { 282 | position: relative; 283 | margin-top: 46px; 284 | } 285 | body.iOS.safari form > section:first-child.labelled { 286 | margin-top: 12px; 287 | } 288 | body.iOS.safari form > section.labelled > label { 289 | position: absolute; 290 | top: -32px; 291 | text-transform: uppercase; 292 | font-size: 75%; 293 | color: rgb(133, 133, 135); 294 | } 295 | body.iOS.safari form > section.labelled > label + section, 296 | body.iOS.safari form > section.labelled > label + section.desktop + section { 297 | border-top: none; 298 | } 299 | body.iOS.safari form > section.labelled > label + p { 300 | margin-top: 12px; 301 | margin-left: 0; 302 | } 303 | /* A checkbox options with nested options should indent their names */ 304 | body.iOS.safari section.checkbox > section:not(.group) > label { 305 | padding-left: 18px 306 | } 307 | /* A checkbox option with nested group should indent it */ 308 | body.iOS.safari section.checkbox > section.group { 309 | margin-left: 18px 310 | } 311 | body.iOS.safari summary { 312 | padding-left: 0; 313 | } 314 | body.iOS.safari p { 315 | font-size: inherit !important; 316 | color: rgb(95, 99, 104) !important; 317 | margin-right: 18px; 318 | } 319 | body.iOS.safari section.group > section > * { 320 | color: inherit; 321 | } 322 | 323 | /* iOS-style toggles */ 324 | body.iOS.safari .checkbox input[type="checkbox"] { 325 | position: absolute; 326 | overflow: hidden; 327 | clip: rect(0 0 0 0); 328 | height: 1px; 329 | width: 1px; 330 | margin: -1px; 331 | padding: 0; 332 | border: 0; 333 | } 334 | body.iOS.safari .checkbox .toggle { 335 | position: relative; 336 | display: inline-block; 337 | min-width: 46px; 338 | height: 26px; 339 | background-color: #e6e6e6; 340 | border-radius: 23px; 341 | vertical-align: text-bottom; 342 | transition: all 0.3s linear; 343 | margin-left: 8px; 344 | } 345 | body.iOS.safari .checkbox .toggle::before { 346 | content: ""; 347 | position: absolute; 348 | left: 0; 349 | width: 42px; 350 | height: 22px; 351 | background-color: #fff; 352 | border-radius: 11px; 353 | transform: translate3d(2px, 2px, 0) scale3d(1, 1, 1); 354 | transition: all 0.25s linear; 355 | } 356 | body.iOS.safari .checkbox .toggle::after { 357 | content: ""; 358 | position: absolute; 359 | left: 0; 360 | width: 22px; 361 | height: 22px; 362 | background-color: #fff; 363 | border-radius: 11px; 364 | box-shadow: 0 2px 2px rgba(0, 0, 0, 0.24); 365 | transform: translate3d(2px, 2px, 0); 366 | transition: all 0.2s ease-in-out; 367 | } 368 | body.iOS.safari .checkbox label:active .toggle::after, 369 | body.iOS.safari label.checkbox:active .toggle:after { 370 | width: 28px; 371 | transform: translate3d(2px, 2px, 0); 372 | } 373 | body.iOS.safari .checkbox label:active input:checked + .toggle::after, 374 | body.iOS.safari label.checkbox:active input:checked + .toggle::after { 375 | transform: translate3d(16px, 2px, 0); 376 | } 377 | body.iOS.safari input:checked + .toggle { 378 | background-color: #4BD763; 379 | } 380 | body.iOS.safari input:checked + .toggle::before { 381 | transform: translate3d(18px, 2px, 0) scale3d(0, 0, 0); 382 | } 383 | body.iOS.safari input:checked + .toggle::after { 384 | transform: translate3d(22px, 2px, 0); 385 | } 386 | body.iOS.safari .checkbox input:focus + .toggle { 387 | outline: 5px auto Highlight; 388 | outline: 5px auto -webkit-focus-ring-color; 389 | } 390 | 391 | /* Dark mode overrides */ 392 | @media (prefers-color-scheme: dark) { 393 | body { 394 | background-color: #292a2d; 395 | color: #e8eaed; 396 | } 397 | section { 398 | --border-color: #3f4042; 399 | } 400 | section.group > p, 401 | section.group > section > * { 402 | color: rgb(154, 160, 166); 403 | } 404 | 405 | /* Edge dark mode overrides */ 406 | body.edge { 407 | background-color: #3B3B3B; 408 | color: #A7A7A7; 409 | } 410 | body.edge section.group > label { 411 | color: #fff; 412 | } 413 | body.edge section { 414 | --border-color: #737373; 415 | } 416 | body.edge section.group > p, 417 | body.edge section.group > section > * { 418 | color: #A7A7A7; 419 | } 420 | 421 | /* Firefox dark mode overrides */ 422 | @-moz-document url-prefix() { 423 | body { 424 | background-color: #23222b; 425 | } 426 | section { 427 | --border-color: #4e4d54; 428 | } 429 | section.group > p, 430 | section.group > section > * { 431 | color: rgb(191, 191, 201); 432 | } 433 | } 434 | 435 | /* Safari dark mode overrides */ 436 | body.macOS.safari p { 437 | color: rgb(184, 184, 184) !important; 438 | } 439 | body.iOS.safari { 440 | background-color: rgb(0, 0, 0); 441 | } 442 | body.iOS.safari form > section { 443 | background-color: rgb(28, 28, 30); 444 | } 445 | body.iOS.safari form > section.labelled > label { 446 | color: rgb(115, 115, 121); 447 | } 448 | body.iOS.safari section.group section { 449 | --border-color: rgb(35, 35, 37); 450 | } 451 | body.iOS.safari p { 452 | color: rgb(132, 132, 138) !important; 453 | } 454 | body.iOS.safari .checkbox .toggle { 455 | background-color: rgb(57, 57, 61); 456 | } 457 | body.iOS.safari .checkbox .toggle::before { 458 | background-color: rgb(57, 57, 61); 459 | } 460 | } -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Control Panel for YouTube 7 | 8 | 9 | 10 |
11 |
12 | 20 |
21 |
22 |
23 | 28 |
29 |
30 | 35 |
36 |
37 | 42 |
43 |
44 | 49 |
50 |
51 |
52 | 55 |
56 | 61 |
62 |
63 | 68 |
69 |
70 | 78 |
79 |
80 | 90 |

In Home and Subscriptions

91 |
92 |
93 | 98 |
99 |
100 | 105 |
106 |
107 | 112 |
113 |
114 | 119 |
120 |
121 | 126 |
127 |
128 | 133 |
134 |
135 | 140 |
141 |
142 | 147 |
148 | 162 |
163 |
164 |
165 | 170 |

Hides the "Undo" button after 5 seconds

171 |
172 |
173 | 178 |

Adds "Hide channel" to video menus

179 |
180 | Hidden channels 181 |
182 |
183 |
184 |
185 | 190 |
191 |
192 | 197 |

Only when logged in - redirects to Subscriptions

198 |
199 |
200 |
201 | 204 |
205 | 210 |
211 |
212 | 217 |
218 |
219 | 224 |
225 |
226 | 231 |
232 | 237 |
238 |
239 | 244 |
245 |
246 |
247 | 252 |
253 |
254 | 259 |
260 |
261 | 266 |
267 |
268 | 273 |
274 |
275 | 280 |

281 | When not watching a video in a playlist 282 |

283 |
284 |
285 | 290 |
291 |
292 | 297 |
298 |
299 | 304 |
305 |
306 | 311 |
312 |
313 | 318 |
319 |
320 | 325 |
326 |
327 | 332 |
333 | 340 |
341 |
342 | 351 |
352 |
353 |
354 | 359 |
360 |
361 |
362 | 365 |
366 | 371 |
372 |
373 | 378 |
379 |
380 | 395 |

In Home and Subscriptions

396 |
397 |
398 | 403 |
404 |
405 | 410 |
411 |
412 | 417 |
418 |
419 | 424 |
425 |
426 |
427 | 430 |
431 | 436 |
437 |
438 | 443 |
444 |
445 | 450 |
451 |
452 | 457 |
458 |
459 | 464 |
465 |
466 | 471 |
472 |
473 | 478 |
479 |
480 | 485 |
486 |
487 | 492 |
493 |
494 |
495 | 498 |
499 | 504 |
505 |
506 | 511 |
512 |
513 | 514 |
515 | 516 |
517 | 522 |

Enables debug logging

523 |
524 |
525 | 530 |

Highlights elements instead of hiding them

531 |
532 |
533 |
v1.11.2
534 |
535 | 536 | 537 | 538 | -------------------------------------------------------------------------------- /options.js: -------------------------------------------------------------------------------- 1 | document.title = chrome.i18n.getMessage('extensionName') 2 | 3 | for (let optionValue of [ 4 | 'auto', 5 | 'large', 6 | 'medium', 7 | 'small', 8 | ]) { 9 | let label = chrome.i18n.getMessage(optionValue) 10 | for (let $option of document.querySelectorAll(`option[value="${optionValue}"]`)) { 11 | $option.textContent = label 12 | } 13 | } 14 | 15 | for (let translationClass of [ 16 | 'inHomeAndSubscriptionsNote', 17 | ]) { 18 | let translation = chrome.i18n.getMessage(translationClass) 19 | for (let $el of document.querySelectorAll(`.${translationClass}`)) { 20 | $el.textContent = translation 21 | } 22 | } 23 | 24 | for (let translationId of [ 25 | 'addTakeSnapshot', 26 | 'alwaysShowShortsProgressBar', 27 | 'alwaysUseOriginalAudio', 28 | 'alwaysUseTheaterMode', 29 | 'anyPercent', 30 | 'disableAutoplay', 31 | 'disableHomeFeed', 32 | 'disableHomeFeedNote', 33 | 'downloadTranscript', 34 | 'embeddedVideos', 35 | 'enabled', 36 | 'fullSizeTheaterMode', 37 | 'fullSizeTheaterModeHideHeader', 38 | 'fullSizeTheaterModeHideScrollbar', 39 | 'hideAI', 40 | 'hideChannelBanner', 41 | 'hideChannels', 42 | 'hideChannelsNote', 43 | 'hideChat', 44 | 'hideComments', 45 | 'hideEmbedPauseOverlay', 46 | 'hideEmbedShareButton', 47 | 'hideEndCards', 48 | 'hideEndVideos', 49 | 'hideExploreButton', 50 | 'hideHiddenVideos', 51 | 'hideHiddenVideosNote', 52 | 'hideHomeCategories', 53 | 'hideInfoPanels', 54 | 'hideLive', 55 | 'hideMembersOnly', 56 | 'hideMerchEtc', 57 | 'hideMetadata', 58 | 'hideMiniplayerButton', 59 | 'hideMixes', 60 | 'hideMoviesAndTV', 61 | 'hideNextButton', 62 | 'hideNextButtonNote', 63 | 'hideOpenApp', 64 | 'hidePlaylists', 65 | 'hidePremiumUpsells', 66 | 'hideRelated', 67 | 'hideShareThanksClip', 68 | 'hideShorts', 69 | 'hideShortsMetadataUntilHover', 70 | 'hideShortsSuggestedActions', 71 | 'hideSponsored', 72 | 'hideStreamed', 73 | 'hideSubscriptionsChannelList', 74 | 'hideSubscriptionsLatestBar', 75 | 'hideSuggestedSections', 76 | 'hideUpcoming', 77 | 'hideVoiceSearch', 78 | 'hideWatched', 79 | 'hideWatchedThreshold', 80 | 'homeMaxOption', 81 | 'minimumGridItemsPerRow', 82 | 'mobileGridView', 83 | 'pauseChannelTrailers', 84 | 'qualityFull', 85 | 'qualityHigh', 86 | 'qualityLow', 87 | 'qualityMedium', 88 | 'redirectShorts', 89 | 'removePink', 90 | 'searchThumbnailSize', 91 | 'shorts', 92 | 'skipAds', 93 | 'snapshotFormat', 94 | 'snapshotQuality', 95 | 'stopShortsLooping', 96 | 'tidyGuideSidebar', 97 | 'uiTweaks', 98 | 'videoLists', 99 | 'videoPages', 100 | ]) { 101 | document.getElementById(translationId).textContent = chrome.i18n.getMessage(translationId) 102 | } 103 | 104 | let $body = document.body 105 | let $form = document.querySelector('form') 106 | 107 | if (navigator.userAgent.includes('Safari/') && !/Chrom(e|ium)\//.test(navigator.userAgent)) { 108 | $body.classList.add('safari', /iP(ad|hone)/.test(navigator.userAgent) ? 'iOS' : 'macOS') 109 | } else { 110 | $body.classList.toggle('edge', navigator.userAgent.includes('Edg/')) 111 | } 112 | 113 | //#region Default config 114 | /** @type {import("./types").OptionsConfig} */ 115 | let defaultConfig = { 116 | enabled: true, 117 | // Default based on platform until the content script runs 118 | version: /(Android|iP(ad|hone))/.test(navigator.userAgent) ? 'mobile' : 'desktop', 119 | alwaysShowShortsProgressBar: false, 120 | disableAutoplay: true, 121 | disableHomeFeed: false, 122 | hiddenChannels: [], 123 | hideAI: true, 124 | hideChannelBanner: false, 125 | hideChannels: true, 126 | hideComments: false, 127 | hideHiddenVideos: true, 128 | hideHomeCategories: false, 129 | hideInfoPanels: false, 130 | hideLive: false, 131 | hideMembersOnly: false, 132 | hideMetadata: false, 133 | hideMixes: false, 134 | hideMoviesAndTV: false, 135 | hideNextButton: true, 136 | hidePlaylists: false, 137 | hidePremiumUpsells: false, 138 | hideRelated: false, 139 | hideShareThanksClip: false, 140 | hideShorts: true, 141 | hideShortsSuggestedActions: true, 142 | hideSponsored: true, 143 | hideStreamed: false, 144 | hideSuggestedSections: true, 145 | hideUpcoming: false, 146 | hideVoiceSearch: false, 147 | hideWatched: true, 148 | hideWatchedThreshold: '80', 149 | redirectShorts: true, 150 | removePink: false, 151 | skipAds: true, 152 | stopShortsLooping: false, 153 | // Desktop only 154 | addTakeSnapshot: true, 155 | alwaysUseOriginalAudio: false, 156 | alwaysUseTheaterMode: false, 157 | downloadTranscript: true, 158 | fullSizeTheaterMode: false, 159 | fullSizeTheaterModeHideHeader: true, 160 | fullSizeTheaterModeHideScrollbar: false, 161 | hideChat: false, 162 | hideEndCards: false, 163 | hideEndVideos: true, 164 | hideMerchEtc: true, 165 | hideMiniplayerButton: false, 166 | hideShortsMetadataUntilHover: true, 167 | hideSubscriptionsLatestBar: false, 168 | minimumGridItemsPerRow: 'auto', 169 | minimumShortsPerRow: 'auto', 170 | pauseChannelTrailers: true, 171 | searchThumbnailSize: 'medium', 172 | snapshotFormat: 'jpeg', 173 | snapshotQuality: '0.92', 174 | tidyGuideSidebar: false, 175 | // Mobile only 176 | hideExploreButton: true, 177 | hideOpenApp: true, 178 | hideSubscriptionsChannelList: false, 179 | mobileGridView: true, 180 | // Embedded videos 181 | hideEmbedPauseOverlay: true, 182 | hideEmbedShareButton: false, 183 | } 184 | //#endregion 185 | 186 | /** @type {import("./types").OptionsConfig} */ 187 | let optionsConfig 188 | 189 | let $hiddenChannels = /** @type {HTMLElement} */ (document.querySelector('#hiddenChannels')) 190 | let $hiddenChannelsDetails = /** @type {HTMLDetailsElement} */ (document.querySelector('#hiddenChannelsDetails')) 191 | let $hiddenChannelsSummary = /** @type {HTMLElement} */ (document.querySelector('#hiddenChannelsSummary')) 192 | 193 | /** 194 | * @param {keyof HTMLElementTagNameMap} tagName 195 | * @param {({[key: string]: any} | null)?} attributes 196 | * @param {...any} children 197 | * @returns {HTMLElement} 198 | */ 199 | function h(tagName, attributes, ...children) { 200 | let $el = document.createElement(tagName) 201 | 202 | if (attributes) { 203 | for (let [prop, value] of Object.entries(attributes)) { 204 | if (prop.startsWith('on') && typeof value == 'function') { 205 | $el.addEventListener(prop.slice(2).toLowerCase(), value) 206 | } else { 207 | $el[prop] = value 208 | } 209 | } 210 | } 211 | 212 | for (let child of children) { 213 | if (child == null || child === false) continue 214 | if (child instanceof Node) { 215 | $el.appendChild(child) 216 | } else { 217 | $el.insertAdjacentText('beforeend', String(child)) 218 | } 219 | } 220 | 221 | return $el 222 | } 223 | 224 | /** 225 | * @param {Event} e 226 | */ 227 | function onFormChanged(e) { 228 | let $el = /** @type {HTMLInputElement} */ (e.target) 229 | let prop = $el.name 230 | let value = $el.type == 'checkbox' ? $el.checked : $el.value 231 | optionsConfig[prop] = value 232 | storeConfigChanges({[prop]: value}) 233 | updateDisplay() 234 | } 235 | 236 | /** 237 | * @param {{[key: string]: chrome.storage.StorageChange}} changes 238 | */ 239 | function onStorageChanged(changes) { 240 | for (let prop in changes) { 241 | optionsConfig[prop] = changes[prop].newValue 242 | setFormValue(prop, changes[prop].newValue) 243 | } 244 | updateDisplay() 245 | } 246 | 247 | function setFormValue(prop, value) { 248 | if (!$form.elements.hasOwnProperty(prop)) return 249 | 250 | let $el = /** @type {HTMLInputElement} */ ($form.elements[prop]) 251 | if ($el.type == 'checkbox') { 252 | $el.checked = value 253 | } else { 254 | $el.value = value 255 | } 256 | } 257 | 258 | /** 259 | * Store config changes without triggering this page's own listener. 260 | * @param {Partial} changes 261 | */ 262 | function storeConfigChanges(changes) { 263 | chrome.storage.local.onChanged.removeListener(onStorageChanged) 264 | chrome.storage.local.set(changes, () => { 265 | chrome.storage.local.onChanged.addListener(onStorageChanged) 266 | }) 267 | } 268 | 269 | function shouldDisplayHiddenChannels() { 270 | return optionsConfig.hideChannels && optionsConfig.hiddenChannels.length > 0 271 | } 272 | 273 | function updateDisplay() { 274 | $body.classList.toggle('desktop', optionsConfig.version == 'desktop') 275 | $body.classList.toggle('disabled', !optionsConfig.enabled) 276 | $body.classList.toggle('fullSizeTheaterMode', optionsConfig.fullSizeTheaterMode) 277 | $body.classList.toggle('hiddenChannels', shouldDisplayHiddenChannels()) 278 | $body.classList.toggle('hidingWatched', optionsConfig.hideWatched) 279 | $body.classList.toggle('jpegSnapshot', optionsConfig.snapshotFormat == 'jpeg') 280 | $body.classList.toggle('mobile', optionsConfig.version == 'mobile') 281 | $body.classList.toggle('snapshot', optionsConfig.addTakeSnapshot) 282 | updateHiddenChannelsDisplay() 283 | } 284 | 285 | function updateHiddenChannelsDisplay() { 286 | if (!shouldDisplayHiddenChannels()) return 287 | 288 | $hiddenChannelsSummary.textContent = chrome.i18n.getMessage('hiddenChannelsSummary', String(optionsConfig.hiddenChannels.length)) 289 | 290 | if (!$hiddenChannelsDetails.open) return 291 | 292 | while ($hiddenChannels.hasChildNodes()) $hiddenChannels.firstChild.remove() 293 | for (let [index, {name}] of optionsConfig.hiddenChannels.entries()) { 294 | $hiddenChannels.appendChild( 295 | h('section', null, 296 | h('label', {className: 'button'}, 297 | h('span', null, name), 298 | h('button', { 299 | type: 'button', 300 | onclick() { 301 | optionsConfig.hiddenChannels = optionsConfig.hiddenChannels.filter((_, i) => i != index) 302 | storeConfigChanges({hiddenChannels: optionsConfig.hiddenChannels}) 303 | updateDisplay() 304 | } 305 | }, '×') 306 | ) 307 | ) 308 | ) 309 | } 310 | } 311 | 312 | //#region Main 313 | function main() { 314 | chrome.storage.local.get((storedConfig) => { 315 | optionsConfig = {...defaultConfig, ...storedConfig} 316 | 317 | for (let [prop, value] of Object.entries(optionsConfig)) { 318 | setFormValue(prop, value) 319 | } 320 | 321 | updateDisplay() 322 | 323 | $form.addEventListener('change', onFormChanged) 324 | $hiddenChannelsDetails.addEventListener('toggle', updateHiddenChannelsDisplay) 325 | chrome.storage.local.onChanged.addListener(onStorageChanged) 326 | 327 | $body.classList.toggle('debug', Boolean(optionsConfig.debug || optionsConfig.debugManualHiding)) 328 | if (!optionsConfig.debug && !optionsConfig.debugManualHiding) { 329 | let $version = document.querySelector('#version') 330 | let $debugCountdown = document.querySelector('#debugCountdown') 331 | let debugCountdown = 5 332 | 333 | function onClick(e) { 334 | if (e.target === $version || $version.contains(/** @type {Node} */ (e.target))) { 335 | debugCountdown-- 336 | } else { 337 | debugCountdown = 5 338 | } 339 | 340 | if (debugCountdown == 0) { 341 | $body.classList.add('debug') 342 | $debugCountdown.textContent = '' 343 | $form.removeEventListener('click', onClick) 344 | } 345 | else if (debugCountdown <= 3) { 346 | $debugCountdown.textContent = ` (${debugCountdown})` 347 | } 348 | } 349 | 350 | $form.addEventListener('click', onClick) 351 | } 352 | }) 353 | } 354 | 355 | main() 356 | //#endregion -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "create-browser-action": "node ./scripts/create-browser-action.js", 4 | "create-store-description": "node ./scripts/create-store-description.mjs", 5 | "release": "node ./scripts/release.js", 6 | "prebuild": "npm run create-browser-action", 7 | "build": "node ./scripts/build.js", 8 | "build-mv2": "node ./scripts/build.js 2", 9 | "build-mv3": "node ./scripts/build.js 3", 10 | "lint-mv2": "npm run copy-mv2 && web-ext lint", 11 | "lint-mv3": "npm run copy-mv3 && web-ext lint", 12 | "copy-mv2": "node ./scripts/copy.js manifest.mv2.json manifest.json", 13 | "copy-mv3": "node ./scripts/copy.js manifest.mv3.json manifest.json", 14 | "start": "npm run firefox", 15 | "prefirefox": "npm run create-browser-action && npm run copy-mv2", 16 | "firefox": "web-ext run --start-url https://www.youtube.com", 17 | "prechrome": "npm run create-browser-action && npm run copy-mv3", 18 | "chrome": "web-ext run --target chromium --start-url https://www.youtube.com", 19 | "preedge": "npm run create-browser-action && npm run copy-mv3", 20 | "edge": "web-ext run --target chromium --chromium-binary \"C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe\" --start-url https://www.youtube.com", 21 | "preedge-mac": "npm run create-browser-action && npm run copy-mv3", 22 | "edge-mac": "web-ext run --target chromium --chromium-binary \"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge\" --start-url https://www.youtube.com", 23 | "preandroid": "npm run create-browser-action && npm run copy-mv2", 24 | "android": "web-ext run -t firefox-android --firefox-apk org.mozilla.fenix" 25 | }, 26 | "webExt": { 27 | "ignoreFiles": [ 28 | "*.md", 29 | "icons/chrome-webstore-icon.png", 30 | "icons/icon.svg", 31 | "icons/icon256.png", 32 | "icons/icon512.png", 33 | "icons/toolbar-*.png", 34 | "jsconfig.json", 35 | "manifest.mv2.json", 36 | "manifest.mv3.json", 37 | "package.json", 38 | "safari/", 39 | "screenshots/", 40 | "scripts/", 41 | "types.d.ts", 42 | "wip/" 43 | ] 44 | }, 45 | "devDependencies": { 46 | "@types/chrome": "0.0.x", 47 | "@types/greasemonkey": "4.x", 48 | "clipboardy": "4.x", 49 | "semver": "7.x", 50 | "web-ext": "8.x" 51 | } 52 | } -------------------------------------------------------------------------------- /safari/.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata/ 2 | *.xcworkspace 3 | build/ -------------------------------------------------------------------------------- /safari/Control Panel for YouTube.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FBB23FE02DE55CAE007886FC /* page.js in Resources */ = {isa = PBXBuildFile; fileRef = FBB23FDF2DE55CAE007886FC /* page.js */; }; 11 | FBB23FE12DE55CAE007886FC /* page.js in Resources */ = {isa = PBXBuildFile; fileRef = FBB23FDF2DE55CAE007886FC /* page.js */; }; 12 | FBE734F72B64C91D0084B277 /* embed.js in Resources */ = {isa = PBXBuildFile; fileRef = FBE734F62B64C91D0084B277 /* embed.js */; }; 13 | FBE734F82B64C91D0084B277 /* embed.js in Resources */ = {isa = PBXBuildFile; fileRef = FBE734F62B64C91D0084B277 /* embed.js */; }; 14 | FBE8607B2B42F1E40053AD2F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE8607A2B42F1E40053AD2F /* AppDelegate.swift */; }; 15 | FBE8607D2B42F1E40053AD2F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE8607C2B42F1E40053AD2F /* SceneDelegate.swift */; }; 16 | FBE860802B42F1E40053AD2F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FBE8607E2B42F1E40053AD2F /* LaunchScreen.storyboard */; }; 17 | FBE860832B42F1E40053AD2F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FBE860812B42F1E40053AD2F /* Main.storyboard */; }; 18 | FBE8608C2B42F1E40053AD2F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE8608B2B42F1E40053AD2F /* AppDelegate.swift */; }; 19 | FBE8608F2B42F1E40053AD2F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FBE8608D2B42F1E40053AD2F /* Main.storyboard */; }; 20 | FBE860962B42F1E40053AD2F /* Control Panel for YouTube Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = FBE860952B42F1E40053AD2F /* Control Panel for YouTube Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 21 | FBE860A02B42F1E40053AD2F /* Control Panel for YouTube Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = FBE8609F2B42F1E40053AD2F /* Control Panel for YouTube Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 22 | FBE860A62B42F1E40053AD2F /* Main.html in Resources */ = {isa = PBXBuildFile; fileRef = FBE860612B42F1E30053AD2F /* Main.html */; }; 23 | FBE860A72B42F1E40053AD2F /* Main.html in Resources */ = {isa = PBXBuildFile; fileRef = FBE860612B42F1E30053AD2F /* Main.html */; }; 24 | FBE860A82B42F1E40053AD2F /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE860632B42F1E30053AD2F /* Icon.png */; }; 25 | FBE860A92B42F1E40053AD2F /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE860632B42F1E30053AD2F /* Icon.png */; }; 26 | FBE860AA2B42F1E40053AD2F /* Style.css in Resources */ = {isa = PBXBuildFile; fileRef = FBE860642B42F1E30053AD2F /* Style.css */; }; 27 | FBE860AB2B42F1E40053AD2F /* Style.css in Resources */ = {isa = PBXBuildFile; fileRef = FBE860642B42F1E30053AD2F /* Style.css */; }; 28 | FBE860AC2B42F1E40053AD2F /* Script.js in Resources */ = {isa = PBXBuildFile; fileRef = FBE860652B42F1E30053AD2F /* Script.js */; }; 29 | FBE860AD2B42F1E40053AD2F /* Script.js in Resources */ = {isa = PBXBuildFile; fileRef = FBE860652B42F1E30053AD2F /* Script.js */; }; 30 | FBE860AE2B42F1E40053AD2F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE860662B42F1E30053AD2F /* ViewController.swift */; }; 31 | FBE860AF2B42F1E40053AD2F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE860662B42F1E30053AD2F /* ViewController.swift */; }; 32 | FBE860B02B42F1E40053AD2F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FBE860672B42F1E40053AD2F /* Assets.xcassets */; }; 33 | FBE860B12B42F1E40053AD2F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FBE860672B42F1E40053AD2F /* Assets.xcassets */; }; 34 | FBE860B22B42F1E40053AD2F /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE860692B42F1E40053AD2F /* SafariWebExtensionHandler.swift */; }; 35 | FBE860B32B42F1E40053AD2F /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE860692B42F1E40053AD2F /* SafariWebExtensionHandler.swift */; }; 36 | FBE860B82B42F1E40053AD2F /* manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = FBE8606D2B42F1E40053AD2F /* manifest.json */; }; 37 | FBE860B92B42F1E40053AD2F /* manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = FBE8606D2B42F1E40053AD2F /* manifest.json */; }; 38 | FBE860D92B438ACE0053AD2F /* content.js in Resources */ = {isa = PBXBuildFile; fileRef = FBE860D42B438ACE0053AD2F /* content.js */; }; 39 | FBE860DA2B438ACE0053AD2F /* content.js in Resources */ = {isa = PBXBuildFile; fileRef = FBE860D42B438ACE0053AD2F /* content.js */; }; 40 | FBE860DB2B438ACE0053AD2F /* browser_action.html in Resources */ = {isa = PBXBuildFile; fileRef = FBE860D52B438ACE0053AD2F /* browser_action.html */; }; 41 | FBE860DC2B438ACE0053AD2F /* browser_action.html in Resources */ = {isa = PBXBuildFile; fileRef = FBE860D52B438ACE0053AD2F /* browser_action.html */; }; 42 | FBE860DD2B438ACE0053AD2F /* options.html in Resources */ = {isa = PBXBuildFile; fileRef = FBE860D62B438ACE0053AD2F /* options.html */; }; 43 | FBE860DE2B438ACE0053AD2F /* options.html in Resources */ = {isa = PBXBuildFile; fileRef = FBE860D62B438ACE0053AD2F /* options.html */; }; 44 | FBE860DF2B438ACE0053AD2F /* options.js in Resources */ = {isa = PBXBuildFile; fileRef = FBE860D72B438ACE0053AD2F /* options.js */; }; 45 | FBE860E02B438ACE0053AD2F /* options.js in Resources */ = {isa = PBXBuildFile; fileRef = FBE860D72B438ACE0053AD2F /* options.js */; }; 46 | FBE860E12B438ACE0053AD2F /* options.css in Resources */ = {isa = PBXBuildFile; fileRef = FBE860D82B438ACE0053AD2F /* options.css */; }; 47 | FBE860E22B438ACE0053AD2F /* options.css in Resources */ = {isa = PBXBuildFile; fileRef = FBE860D82B438ACE0053AD2F /* options.css */; }; 48 | FBE861052B438C950053AD2F /* _locales in Resources */ = {isa = PBXBuildFile; fileRef = FBE861042B438C950053AD2F /* _locales */; }; 49 | FBE861062B438C950053AD2F /* _locales in Resources */ = {isa = PBXBuildFile; fileRef = FBE861042B438C950053AD2F /* _locales */; }; 50 | FBE8610A2B439B0D0053AD2F /* Ad.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861072B439AF10053AD2F /* Ad.png */; }; 51 | FBE8610B2B439B0E0053AD2F /* Ad.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861072B439AF10053AD2F /* Ad.png */; }; 52 | FBE861CE2B43A6F80053AD2F /* toolbar-icon48.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861C32B43A6F80053AD2F /* toolbar-icon48.png */; }; 53 | FBE861CF2B43A6F80053AD2F /* toolbar-icon48.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861C32B43A6F80053AD2F /* toolbar-icon48.png */; }; 54 | FBE861D02B43A6F80053AD2F /* icon512.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861C42B43A6F80053AD2F /* icon512.png */; }; 55 | FBE861D12B43A6F80053AD2F /* icon512.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861C42B43A6F80053AD2F /* icon512.png */; }; 56 | FBE861D22B43A6F80053AD2F /* toolbar-icon19.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861C52B43A6F80053AD2F /* toolbar-icon19.png */; }; 57 | FBE861D32B43A6F80053AD2F /* toolbar-icon19.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861C52B43A6F80053AD2F /* toolbar-icon19.png */; }; 58 | FBE861D42B43A6F80053AD2F /* toolbar-icon38.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861C62B43A6F80053AD2F /* toolbar-icon38.png */; }; 59 | FBE861D52B43A6F80053AD2F /* toolbar-icon38.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861C62B43A6F80053AD2F /* toolbar-icon38.png */; }; 60 | FBE861D62B43A6F80053AD2F /* icon128.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861C72B43A6F80053AD2F /* icon128.png */; }; 61 | FBE861D72B43A6F80053AD2F /* icon128.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861C72B43A6F80053AD2F /* icon128.png */; }; 62 | FBE861D82B43A6F80053AD2F /* icon96.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861C82B43A6F80053AD2F /* icon96.png */; }; 63 | FBE861D92B43A6F80053AD2F /* icon96.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861C82B43A6F80053AD2F /* icon96.png */; }; 64 | FBE861DA2B43A6F80053AD2F /* icon256.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861C92B43A6F80053AD2F /* icon256.png */; }; 65 | FBE861DB2B43A6F80053AD2F /* icon256.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861C92B43A6F80053AD2F /* icon256.png */; }; 66 | FBE861DC2B43A6F80053AD2F /* toolbar-icon32.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861CA2B43A6F80053AD2F /* toolbar-icon32.png */; }; 67 | FBE861DD2B43A6F80053AD2F /* toolbar-icon32.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861CA2B43A6F80053AD2F /* toolbar-icon32.png */; }; 68 | FBE861DE2B43A6F80053AD2F /* icon48.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861CB2B43A6F80053AD2F /* icon48.png */; }; 69 | FBE861DF2B43A6F80053AD2F /* icon48.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861CB2B43A6F80053AD2F /* icon48.png */; }; 70 | FBE861E02B43A6F80053AD2F /* toolbar-icon16.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861CC2B43A6F80053AD2F /* toolbar-icon16.png */; }; 71 | FBE861E12B43A6F80053AD2F /* toolbar-icon16.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861CC2B43A6F80053AD2F /* toolbar-icon16.png */; }; 72 | FBE861E22B43A6F80053AD2F /* toolbar-icon72.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861CD2B43A6F80053AD2F /* toolbar-icon72.png */; }; 73 | FBE861E32B43A6F80053AD2F /* toolbar-icon72.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861CD2B43A6F80053AD2F /* toolbar-icon72.png */; }; 74 | FBE861E52B43A7610053AD2F /* options-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861E42B43A7610053AD2F /* options-icon.png */; }; 75 | FBE861E62B43A7610053AD2F /* options-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = FBE861E42B43A7610053AD2F /* options-icon.png */; }; 76 | /* End PBXBuildFile section */ 77 | 78 | /* Begin PBXContainerItemProxy section */ 79 | FBE860972B42F1E40053AD2F /* PBXContainerItemProxy */ = { 80 | isa = PBXContainerItemProxy; 81 | containerPortal = FBE8605B2B42F1E30053AD2F /* Project object */; 82 | proxyType = 1; 83 | remoteGlobalIDString = FBE860942B42F1E40053AD2F; 84 | remoteInfo = "Control Panel for YouTube Extension (iOS)"; 85 | }; 86 | FBE860A12B42F1E40053AD2F /* PBXContainerItemProxy */ = { 87 | isa = PBXContainerItemProxy; 88 | containerPortal = FBE8605B2B42F1E30053AD2F /* Project object */; 89 | proxyType = 1; 90 | remoteGlobalIDString = FBE8609E2B42F1E40053AD2F; 91 | remoteInfo = "Control Panel for YouTube Extension (macOS)"; 92 | }; 93 | /* End PBXContainerItemProxy section */ 94 | 95 | /* Begin PBXCopyFilesBuildPhase section */ 96 | FBE860C92B42F1E40053AD2F /* Embed Foundation Extensions */ = { 97 | isa = PBXCopyFilesBuildPhase; 98 | buildActionMask = 2147483647; 99 | dstPath = ""; 100 | dstSubfolderSpec = 13; 101 | files = ( 102 | FBE860962B42F1E40053AD2F /* Control Panel for YouTube Extension.appex in Embed Foundation Extensions */, 103 | ); 104 | name = "Embed Foundation Extensions"; 105 | runOnlyForDeploymentPostprocessing = 0; 106 | }; 107 | FBE860D02B42F1E40053AD2F /* Embed Foundation Extensions */ = { 108 | isa = PBXCopyFilesBuildPhase; 109 | buildActionMask = 2147483647; 110 | dstPath = ""; 111 | dstSubfolderSpec = 13; 112 | files = ( 113 | FBE860A02B42F1E40053AD2F /* Control Panel for YouTube Extension.appex in Embed Foundation Extensions */, 114 | ); 115 | name = "Embed Foundation Extensions"; 116 | runOnlyForDeploymentPostprocessing = 0; 117 | }; 118 | /* End PBXCopyFilesBuildPhase section */ 119 | 120 | /* Begin PBXFileReference section */ 121 | FBB23FDF2DE55CAE007886FC /* page.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = page.js; path = ../../../page.js; sourceTree = ""; }; 122 | FBE734F62B64C91D0084B277 /* embed.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = embed.js; path = ../../../embed.js; sourceTree = ""; }; 123 | FBE860622B42F1E30053AD2F /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = Base; path = ../Base.lproj/Main.html; sourceTree = ""; }; 124 | FBE860632B42F1E30053AD2F /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = ""; }; 125 | FBE860642B42F1E30053AD2F /* Style.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = Style.css; sourceTree = ""; }; 126 | FBE860652B42F1E30053AD2F /* Script.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Script.js; sourceTree = ""; }; 127 | FBE860662B42F1E30053AD2F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 128 | FBE860672B42F1E40053AD2F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 129 | FBE860692B42F1E40053AD2F /* SafariWebExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariWebExtensionHandler.swift; sourceTree = ""; }; 130 | FBE8606D2B42F1E40053AD2F /* manifest.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = manifest.json; sourceTree = ""; }; 131 | FBE860772B42F1E40053AD2F /* Control Panel for YouTube.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Control Panel for YouTube.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 132 | FBE8607A2B42F1E40053AD2F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 133 | FBE8607C2B42F1E40053AD2F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 134 | FBE8607F2B42F1E40053AD2F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 135 | FBE860822B42F1E40053AD2F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 136 | FBE860842B42F1E40053AD2F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 137 | FBE860892B42F1E40053AD2F /* Control Panel for YouTube.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Control Panel for YouTube.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 138 | FBE8608B2B42F1E40053AD2F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 139 | FBE8608E2B42F1E40053AD2F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 140 | FBE860902B42F1E40053AD2F /* Control Panel for YouTube.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Control Panel for YouTube.entitlements"; sourceTree = ""; }; 141 | FBE860952B42F1E40053AD2F /* Control Panel for YouTube Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Control Panel for YouTube Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 142 | FBE8609A2B42F1E40053AD2F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 143 | FBE8609F2B42F1E40053AD2F /* Control Panel for YouTube Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Control Panel for YouTube Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 144 | FBE860A42B42F1E40053AD2F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 145 | FBE860A52B42F1E40053AD2F /* Control Panel for YouTube.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Control Panel for YouTube.entitlements"; sourceTree = ""; }; 146 | FBE860D42B438ACE0053AD2F /* content.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = content.js; path = ../../../content.js; sourceTree = ""; }; 147 | FBE860D52B438ACE0053AD2F /* browser_action.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = browser_action.html; path = ../../../browser_action.html; sourceTree = ""; }; 148 | FBE860D62B438ACE0053AD2F /* options.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = options.html; path = ../../../options.html; sourceTree = ""; }; 149 | FBE860D72B438ACE0053AD2F /* options.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = options.js; path = ../../../options.js; sourceTree = ""; }; 150 | FBE860D82B438ACE0053AD2F /* options.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = options.css; path = ../../../options.css; sourceTree = ""; }; 151 | FBE861042B438C950053AD2F /* _locales */ = {isa = PBXFileReference; lastKnownFileType = folder; name = _locales; path = ../../../_locales; sourceTree = ""; }; 152 | FBE861072B439AF10053AD2F /* Ad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Ad.png; sourceTree = ""; }; 153 | FBE861C32B43A6F80053AD2F /* toolbar-icon48.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "toolbar-icon48.png"; path = "../../../icons/toolbar-icon48.png"; sourceTree = ""; }; 154 | FBE861C42B43A6F80053AD2F /* icon512.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = icon512.png; path = ../../../icons/icon512.png; sourceTree = ""; }; 155 | FBE861C52B43A6F80053AD2F /* toolbar-icon19.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "toolbar-icon19.png"; path = "../../../icons/toolbar-icon19.png"; sourceTree = ""; }; 156 | FBE861C62B43A6F80053AD2F /* toolbar-icon38.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "toolbar-icon38.png"; path = "../../../icons/toolbar-icon38.png"; sourceTree = ""; }; 157 | FBE861C72B43A6F80053AD2F /* icon128.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = icon128.png; path = ../../../icons/icon128.png; sourceTree = ""; }; 158 | FBE861C82B43A6F80053AD2F /* icon96.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = icon96.png; path = ../../../icons/icon96.png; sourceTree = ""; }; 159 | FBE861C92B43A6F80053AD2F /* icon256.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = icon256.png; path = ../../../icons/icon256.png; sourceTree = ""; }; 160 | FBE861CA2B43A6F80053AD2F /* toolbar-icon32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "toolbar-icon32.png"; path = "../../../icons/toolbar-icon32.png"; sourceTree = ""; }; 161 | FBE861CB2B43A6F80053AD2F /* icon48.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = icon48.png; path = ../../../icons/icon48.png; sourceTree = ""; }; 162 | FBE861CC2B43A6F80053AD2F /* toolbar-icon16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "toolbar-icon16.png"; path = "../../../icons/toolbar-icon16.png"; sourceTree = ""; }; 163 | FBE861CD2B43A6F80053AD2F /* toolbar-icon72.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "toolbar-icon72.png"; path = "../../../icons/toolbar-icon72.png"; sourceTree = ""; }; 164 | FBE861E42B43A7610053AD2F /* options-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "options-icon.png"; path = "../../../options-icon.png"; sourceTree = ""; }; 165 | /* End PBXFileReference section */ 166 | 167 | /* Begin PBXFrameworksBuildPhase section */ 168 | FBE860742B42F1E40053AD2F /* Frameworks */ = { 169 | isa = PBXFrameworksBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | ); 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | FBE860862B42F1E40053AD2F /* Frameworks */ = { 176 | isa = PBXFrameworksBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | ); 180 | runOnlyForDeploymentPostprocessing = 0; 181 | }; 182 | FBE860922B42F1E40053AD2F /* Frameworks */ = { 183 | isa = PBXFrameworksBuildPhase; 184 | buildActionMask = 2147483647; 185 | files = ( 186 | ); 187 | runOnlyForDeploymentPostprocessing = 0; 188 | }; 189 | FBE8609C2B42F1E40053AD2F /* Frameworks */ = { 190 | isa = PBXFrameworksBuildPhase; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | ); 194 | runOnlyForDeploymentPostprocessing = 0; 195 | }; 196 | /* End PBXFrameworksBuildPhase section */ 197 | 198 | /* Begin PBXGroup section */ 199 | FBE8605A2B42F1E30053AD2F = { 200 | isa = PBXGroup; 201 | children = ( 202 | FBE8605F2B42F1E30053AD2F /* Shared (App) */, 203 | FBE860682B42F1E40053AD2F /* Shared (Extension) */, 204 | FBE860792B42F1E40053AD2F /* iOS (App) */, 205 | FBE8608A2B42F1E40053AD2F /* macOS (App) */, 206 | FBE860992B42F1E40053AD2F /* iOS (Extension) */, 207 | FBE860A32B42F1E40053AD2F /* macOS (Extension) */, 208 | FBE860782B42F1E40053AD2F /* Products */, 209 | ); 210 | sourceTree = ""; 211 | }; 212 | FBE8605F2B42F1E30053AD2F /* Shared (App) */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | FBE860662B42F1E30053AD2F /* ViewController.swift */, 216 | FBE860672B42F1E40053AD2F /* Assets.xcassets */, 217 | FBE860602B42F1E30053AD2F /* Resources */, 218 | ); 219 | path = "Shared (App)"; 220 | sourceTree = ""; 221 | }; 222 | FBE860602B42F1E30053AD2F /* Resources */ = { 223 | isa = PBXGroup; 224 | children = ( 225 | FBE861072B439AF10053AD2F /* Ad.png */, 226 | FBE860612B42F1E30053AD2F /* Main.html */, 227 | FBE860632B42F1E30053AD2F /* Icon.png */, 228 | FBE860642B42F1E30053AD2F /* Style.css */, 229 | FBE860652B42F1E30053AD2F /* Script.js */, 230 | ); 231 | path = Resources; 232 | sourceTree = ""; 233 | }; 234 | FBE860682B42F1E40053AD2F /* Shared (Extension) */ = { 235 | isa = PBXGroup; 236 | children = ( 237 | FBE860692B42F1E40053AD2F /* SafariWebExtensionHandler.swift */, 238 | FBE8606A2B42F1E40053AD2F /* Resources */, 239 | ); 240 | path = "Shared (Extension)"; 241 | sourceTree = ""; 242 | }; 243 | FBE8606A2B42F1E40053AD2F /* Resources */ = { 244 | isa = PBXGroup; 245 | children = ( 246 | FBE861042B438C950053AD2F /* _locales */, 247 | FBE860D52B438ACE0053AD2F /* browser_action.html */, 248 | FBE860D42B438ACE0053AD2F /* content.js */, 249 | FBE734F62B64C91D0084B277 /* embed.js */, 250 | FBE861CB2B43A6F80053AD2F /* icon48.png */, 251 | FBE861C82B43A6F80053AD2F /* icon96.png */, 252 | FBE861C72B43A6F80053AD2F /* icon128.png */, 253 | FBE861C92B43A6F80053AD2F /* icon256.png */, 254 | FBE861C42B43A6F80053AD2F /* icon512.png */, 255 | FBE8606D2B42F1E40053AD2F /* manifest.json */, 256 | FBE860D82B438ACE0053AD2F /* options.css */, 257 | FBE860D62B438ACE0053AD2F /* options.html */, 258 | FBE860D72B438ACE0053AD2F /* options.js */, 259 | FBE861E42B43A7610053AD2F /* options-icon.png */, 260 | FBB23FDF2DE55CAE007886FC /* page.js */, 261 | FBE861CC2B43A6F80053AD2F /* toolbar-icon16.png */, 262 | FBE861C52B43A6F80053AD2F /* toolbar-icon19.png */, 263 | FBE861CA2B43A6F80053AD2F /* toolbar-icon32.png */, 264 | FBE861C62B43A6F80053AD2F /* toolbar-icon38.png */, 265 | FBE861C32B43A6F80053AD2F /* toolbar-icon48.png */, 266 | FBE861CD2B43A6F80053AD2F /* toolbar-icon72.png */, 267 | ); 268 | path = Resources; 269 | sourceTree = ""; 270 | }; 271 | FBE860782B42F1E40053AD2F /* Products */ = { 272 | isa = PBXGroup; 273 | children = ( 274 | FBE860772B42F1E40053AD2F /* Control Panel for YouTube.app */, 275 | FBE860892B42F1E40053AD2F /* Control Panel for YouTube.app */, 276 | FBE860952B42F1E40053AD2F /* Control Panel for YouTube Extension.appex */, 277 | FBE8609F2B42F1E40053AD2F /* Control Panel for YouTube Extension.appex */, 278 | ); 279 | name = Products; 280 | sourceTree = ""; 281 | }; 282 | FBE860792B42F1E40053AD2F /* iOS (App) */ = { 283 | isa = PBXGroup; 284 | children = ( 285 | FBE8607A2B42F1E40053AD2F /* AppDelegate.swift */, 286 | FBE8607C2B42F1E40053AD2F /* SceneDelegate.swift */, 287 | FBE8607E2B42F1E40053AD2F /* LaunchScreen.storyboard */, 288 | FBE860812B42F1E40053AD2F /* Main.storyboard */, 289 | FBE860842B42F1E40053AD2F /* Info.plist */, 290 | ); 291 | path = "iOS (App)"; 292 | sourceTree = ""; 293 | }; 294 | FBE8608A2B42F1E40053AD2F /* macOS (App) */ = { 295 | isa = PBXGroup; 296 | children = ( 297 | FBE8608B2B42F1E40053AD2F /* AppDelegate.swift */, 298 | FBE8608D2B42F1E40053AD2F /* Main.storyboard */, 299 | FBE860902B42F1E40053AD2F /* Control Panel for YouTube.entitlements */, 300 | ); 301 | path = "macOS (App)"; 302 | sourceTree = ""; 303 | }; 304 | FBE860992B42F1E40053AD2F /* iOS (Extension) */ = { 305 | isa = PBXGroup; 306 | children = ( 307 | FBE8609A2B42F1E40053AD2F /* Info.plist */, 308 | ); 309 | path = "iOS (Extension)"; 310 | sourceTree = ""; 311 | }; 312 | FBE860A32B42F1E40053AD2F /* macOS (Extension) */ = { 313 | isa = PBXGroup; 314 | children = ( 315 | FBE860A42B42F1E40053AD2F /* Info.plist */, 316 | FBE860A52B42F1E40053AD2F /* Control Panel for YouTube.entitlements */, 317 | ); 318 | path = "macOS (Extension)"; 319 | sourceTree = ""; 320 | }; 321 | /* End PBXGroup section */ 322 | 323 | /* Begin PBXNativeTarget section */ 324 | FBE860762B42F1E40053AD2F /* Control Panel for YouTube (iOS) */ = { 325 | isa = PBXNativeTarget; 326 | buildConfigurationList = FBE860CA2B42F1E40053AD2F /* Build configuration list for PBXNativeTarget "Control Panel for YouTube (iOS)" */; 327 | buildPhases = ( 328 | FBE860732B42F1E40053AD2F /* Sources */, 329 | FBE860742B42F1E40053AD2F /* Frameworks */, 330 | FBE860752B42F1E40053AD2F /* Resources */, 331 | FBE860C92B42F1E40053AD2F /* Embed Foundation Extensions */, 332 | ); 333 | buildRules = ( 334 | ); 335 | dependencies = ( 336 | FBE860982B42F1E40053AD2F /* PBXTargetDependency */, 337 | ); 338 | name = "Control Panel for YouTube (iOS)"; 339 | productName = "Control Panel for YouTube (iOS)"; 340 | productReference = FBE860772B42F1E40053AD2F /* Control Panel for YouTube.app */; 341 | productType = "com.apple.product-type.application"; 342 | }; 343 | FBE860882B42F1E40053AD2F /* Control Panel for YouTube (macOS) */ = { 344 | isa = PBXNativeTarget; 345 | buildConfigurationList = FBE860D12B42F1E40053AD2F /* Build configuration list for PBXNativeTarget "Control Panel for YouTube (macOS)" */; 346 | buildPhases = ( 347 | FBE860852B42F1E40053AD2F /* Sources */, 348 | FBE860862B42F1E40053AD2F /* Frameworks */, 349 | FBE860872B42F1E40053AD2F /* Resources */, 350 | FBE860D02B42F1E40053AD2F /* Embed Foundation Extensions */, 351 | ); 352 | buildRules = ( 353 | ); 354 | dependencies = ( 355 | FBE860A22B42F1E40053AD2F /* PBXTargetDependency */, 356 | ); 357 | name = "Control Panel for YouTube (macOS)"; 358 | productName = "Control Panel for YouTube (macOS)"; 359 | productReference = FBE860892B42F1E40053AD2F /* Control Panel for YouTube.app */; 360 | productType = "com.apple.product-type.application"; 361 | }; 362 | FBE860942B42F1E40053AD2F /* Control Panel for YouTube Extension (iOS) */ = { 363 | isa = PBXNativeTarget; 364 | buildConfigurationList = FBE860C62B42F1E40053AD2F /* Build configuration list for PBXNativeTarget "Control Panel for YouTube Extension (iOS)" */; 365 | buildPhases = ( 366 | FBE860912B42F1E40053AD2F /* Sources */, 367 | FBE860922B42F1E40053AD2F /* Frameworks */, 368 | FBE860932B42F1E40053AD2F /* Resources */, 369 | ); 370 | buildRules = ( 371 | ); 372 | dependencies = ( 373 | ); 374 | name = "Control Panel for YouTube Extension (iOS)"; 375 | productName = "Control Panel for YouTube Extension (iOS)"; 376 | productReference = FBE860952B42F1E40053AD2F /* Control Panel for YouTube Extension.appex */; 377 | productType = "com.apple.product-type.app-extension"; 378 | }; 379 | FBE8609E2B42F1E40053AD2F /* Control Panel for YouTube Extension (macOS) */ = { 380 | isa = PBXNativeTarget; 381 | buildConfigurationList = FBE860CD2B42F1E40053AD2F /* Build configuration list for PBXNativeTarget "Control Panel for YouTube Extension (macOS)" */; 382 | buildPhases = ( 383 | FBE8609B2B42F1E40053AD2F /* Sources */, 384 | FBE8609C2B42F1E40053AD2F /* Frameworks */, 385 | FBE8609D2B42F1E40053AD2F /* Resources */, 386 | ); 387 | buildRules = ( 388 | ); 389 | dependencies = ( 390 | ); 391 | name = "Control Panel for YouTube Extension (macOS)"; 392 | productName = "Control Panel for YouTube Extension (macOS)"; 393 | productReference = FBE8609F2B42F1E40053AD2F /* Control Panel for YouTube Extension.appex */; 394 | productType = "com.apple.product-type.app-extension"; 395 | }; 396 | /* End PBXNativeTarget section */ 397 | 398 | /* Begin PBXProject section */ 399 | FBE8605B2B42F1E30053AD2F /* Project object */ = { 400 | isa = PBXProject; 401 | attributes = { 402 | BuildIndependentTargetsInParallel = 1; 403 | LastSwiftUpdateCheck = 1510; 404 | LastUpgradeCheck = 1630; 405 | TargetAttributes = { 406 | FBE860762B42F1E40053AD2F = { 407 | CreatedOnToolsVersion = 15.1; 408 | }; 409 | FBE860882B42F1E40053AD2F = { 410 | CreatedOnToolsVersion = 15.1; 411 | }; 412 | FBE860942B42F1E40053AD2F = { 413 | CreatedOnToolsVersion = 15.1; 414 | }; 415 | FBE8609E2B42F1E40053AD2F = { 416 | CreatedOnToolsVersion = 15.1; 417 | }; 418 | }; 419 | }; 420 | buildConfigurationList = FBE8605E2B42F1E30053AD2F /* Build configuration list for PBXProject "Control Panel for YouTube" */; 421 | compatibilityVersion = "Xcode 14.0"; 422 | developmentRegion = en; 423 | hasScannedForEncodings = 0; 424 | knownRegions = ( 425 | en, 426 | Base, 427 | ); 428 | mainGroup = FBE8605A2B42F1E30053AD2F; 429 | productRefGroup = FBE860782B42F1E40053AD2F /* Products */; 430 | projectDirPath = ""; 431 | projectRoot = ""; 432 | targets = ( 433 | FBE860762B42F1E40053AD2F /* Control Panel for YouTube (iOS) */, 434 | FBE860882B42F1E40053AD2F /* Control Panel for YouTube (macOS) */, 435 | FBE860942B42F1E40053AD2F /* Control Panel for YouTube Extension (iOS) */, 436 | FBE8609E2B42F1E40053AD2F /* Control Panel for YouTube Extension (macOS) */, 437 | ); 438 | }; 439 | /* End PBXProject section */ 440 | 441 | /* Begin PBXResourcesBuildPhase section */ 442 | FBE860752B42F1E40053AD2F /* Resources */ = { 443 | isa = PBXResourcesBuildPhase; 444 | buildActionMask = 2147483647; 445 | files = ( 446 | FBE860A82B42F1E40053AD2F /* Icon.png in Resources */, 447 | FBE860802B42F1E40053AD2F /* LaunchScreen.storyboard in Resources */, 448 | FBE860A62B42F1E40053AD2F /* Main.html in Resources */, 449 | FBE860AC2B42F1E40053AD2F /* Script.js in Resources */, 450 | FBE860B02B42F1E40053AD2F /* Assets.xcassets in Resources */, 451 | FBE860832B42F1E40053AD2F /* Main.storyboard in Resources */, 452 | FBE8610B2B439B0E0053AD2F /* Ad.png in Resources */, 453 | FBE860AA2B42F1E40053AD2F /* Style.css in Resources */, 454 | ); 455 | runOnlyForDeploymentPostprocessing = 0; 456 | }; 457 | FBE860872B42F1E40053AD2F /* Resources */ = { 458 | isa = PBXResourcesBuildPhase; 459 | buildActionMask = 2147483647; 460 | files = ( 461 | FBE860A92B42F1E40053AD2F /* Icon.png in Resources */, 462 | FBE860AB2B42F1E40053AD2F /* Style.css in Resources */, 463 | FBE8608F2B42F1E40053AD2F /* Main.storyboard in Resources */, 464 | FBE860AD2B42F1E40053AD2F /* Script.js in Resources */, 465 | FBE8610A2B439B0D0053AD2F /* Ad.png in Resources */, 466 | FBE860B12B42F1E40053AD2F /* Assets.xcassets in Resources */, 467 | FBE860A72B42F1E40053AD2F /* Main.html in Resources */, 468 | ); 469 | runOnlyForDeploymentPostprocessing = 0; 470 | }; 471 | FBE860932B42F1E40053AD2F /* Resources */ = { 472 | isa = PBXResourcesBuildPhase; 473 | buildActionMask = 2147483647; 474 | files = ( 475 | FBB23FE02DE55CAE007886FC /* page.js in Resources */, 476 | FBE734F72B64C91D0084B277 /* embed.js in Resources */, 477 | FBE861052B438C950053AD2F /* _locales in Resources */, 478 | FBE860D92B438ACE0053AD2F /* content.js in Resources */, 479 | FBE861DE2B43A6F80053AD2F /* icon48.png in Resources */, 480 | FBE861D62B43A6F80053AD2F /* icon128.png in Resources */, 481 | FBE861E52B43A7610053AD2F /* options-icon.png in Resources */, 482 | FBE861D22B43A6F80053AD2F /* toolbar-icon19.png in Resources */, 483 | FBE861CE2B43A6F80053AD2F /* toolbar-icon48.png in Resources */, 484 | FBE860B82B42F1E40053AD2F /* manifest.json in Resources */, 485 | FBE861E22B43A6F80053AD2F /* toolbar-icon72.png in Resources */, 486 | FBE860E12B438ACE0053AD2F /* options.css in Resources */, 487 | FBE861D02B43A6F80053AD2F /* icon512.png in Resources */, 488 | FBE861DA2B43A6F80053AD2F /* icon256.png in Resources */, 489 | FBE861E02B43A6F80053AD2F /* toolbar-icon16.png in Resources */, 490 | FBE861D82B43A6F80053AD2F /* icon96.png in Resources */, 491 | FBE861D42B43A6F80053AD2F /* toolbar-icon38.png in Resources */, 492 | FBE860DB2B438ACE0053AD2F /* browser_action.html in Resources */, 493 | FBE861DC2B43A6F80053AD2F /* toolbar-icon32.png in Resources */, 494 | FBE860DD2B438ACE0053AD2F /* options.html in Resources */, 495 | FBE860DF2B438ACE0053AD2F /* options.js in Resources */, 496 | ); 497 | runOnlyForDeploymentPostprocessing = 0; 498 | }; 499 | FBE8609D2B42F1E40053AD2F /* Resources */ = { 500 | isa = PBXResourcesBuildPhase; 501 | buildActionMask = 2147483647; 502 | files = ( 503 | FBB23FE12DE55CAE007886FC /* page.js in Resources */, 504 | FBE734F82B64C91D0084B277 /* embed.js in Resources */, 505 | FBE861062B438C950053AD2F /* _locales in Resources */, 506 | FBE860DA2B438ACE0053AD2F /* content.js in Resources */, 507 | FBE861DF2B43A6F80053AD2F /* icon48.png in Resources */, 508 | FBE861D72B43A6F80053AD2F /* icon128.png in Resources */, 509 | FBE861E62B43A7610053AD2F /* options-icon.png in Resources */, 510 | FBE861D32B43A6F80053AD2F /* toolbar-icon19.png in Resources */, 511 | FBE861CF2B43A6F80053AD2F /* toolbar-icon48.png in Resources */, 512 | FBE860B92B42F1E40053AD2F /* manifest.json in Resources */, 513 | FBE861E32B43A6F80053AD2F /* toolbar-icon72.png in Resources */, 514 | FBE860E22B438ACE0053AD2F /* options.css in Resources */, 515 | FBE861D12B43A6F80053AD2F /* icon512.png in Resources */, 516 | FBE861DB2B43A6F80053AD2F /* icon256.png in Resources */, 517 | FBE861E12B43A6F80053AD2F /* toolbar-icon16.png in Resources */, 518 | FBE861D92B43A6F80053AD2F /* icon96.png in Resources */, 519 | FBE861D52B43A6F80053AD2F /* toolbar-icon38.png in Resources */, 520 | FBE860DC2B438ACE0053AD2F /* browser_action.html in Resources */, 521 | FBE861DD2B43A6F80053AD2F /* toolbar-icon32.png in Resources */, 522 | FBE860DE2B438ACE0053AD2F /* options.html in Resources */, 523 | FBE860E02B438ACE0053AD2F /* options.js in Resources */, 524 | ); 525 | runOnlyForDeploymentPostprocessing = 0; 526 | }; 527 | /* End PBXResourcesBuildPhase section */ 528 | 529 | /* Begin PBXSourcesBuildPhase section */ 530 | FBE860732B42F1E40053AD2F /* Sources */ = { 531 | isa = PBXSourcesBuildPhase; 532 | buildActionMask = 2147483647; 533 | files = ( 534 | FBE860AE2B42F1E40053AD2F /* ViewController.swift in Sources */, 535 | FBE8607B2B42F1E40053AD2F /* AppDelegate.swift in Sources */, 536 | FBE8607D2B42F1E40053AD2F /* SceneDelegate.swift in Sources */, 537 | ); 538 | runOnlyForDeploymentPostprocessing = 0; 539 | }; 540 | FBE860852B42F1E40053AD2F /* Sources */ = { 541 | isa = PBXSourcesBuildPhase; 542 | buildActionMask = 2147483647; 543 | files = ( 544 | FBE860AF2B42F1E40053AD2F /* ViewController.swift in Sources */, 545 | FBE8608C2B42F1E40053AD2F /* AppDelegate.swift in Sources */, 546 | ); 547 | runOnlyForDeploymentPostprocessing = 0; 548 | }; 549 | FBE860912B42F1E40053AD2F /* Sources */ = { 550 | isa = PBXSourcesBuildPhase; 551 | buildActionMask = 2147483647; 552 | files = ( 553 | FBE860B22B42F1E40053AD2F /* SafariWebExtensionHandler.swift in Sources */, 554 | ); 555 | runOnlyForDeploymentPostprocessing = 0; 556 | }; 557 | FBE8609B2B42F1E40053AD2F /* Sources */ = { 558 | isa = PBXSourcesBuildPhase; 559 | buildActionMask = 2147483647; 560 | files = ( 561 | FBE860B32B42F1E40053AD2F /* SafariWebExtensionHandler.swift in Sources */, 562 | ); 563 | runOnlyForDeploymentPostprocessing = 0; 564 | }; 565 | /* End PBXSourcesBuildPhase section */ 566 | 567 | /* Begin PBXTargetDependency section */ 568 | FBE860982B42F1E40053AD2F /* PBXTargetDependency */ = { 569 | isa = PBXTargetDependency; 570 | target = FBE860942B42F1E40053AD2F /* Control Panel for YouTube Extension (iOS) */; 571 | targetProxy = FBE860972B42F1E40053AD2F /* PBXContainerItemProxy */; 572 | }; 573 | FBE860A22B42F1E40053AD2F /* PBXTargetDependency */ = { 574 | isa = PBXTargetDependency; 575 | target = FBE8609E2B42F1E40053AD2F /* Control Panel for YouTube Extension (macOS) */; 576 | targetProxy = FBE860A12B42F1E40053AD2F /* PBXContainerItemProxy */; 577 | }; 578 | /* End PBXTargetDependency section */ 579 | 580 | /* Begin PBXVariantGroup section */ 581 | FBE860612B42F1E30053AD2F /* Main.html */ = { 582 | isa = PBXVariantGroup; 583 | children = ( 584 | FBE860622B42F1E30053AD2F /* Base */, 585 | ); 586 | name = Main.html; 587 | sourceTree = ""; 588 | }; 589 | FBE8607E2B42F1E40053AD2F /* LaunchScreen.storyboard */ = { 590 | isa = PBXVariantGroup; 591 | children = ( 592 | FBE8607F2B42F1E40053AD2F /* Base */, 593 | ); 594 | name = LaunchScreen.storyboard; 595 | sourceTree = ""; 596 | }; 597 | FBE860812B42F1E40053AD2F /* Main.storyboard */ = { 598 | isa = PBXVariantGroup; 599 | children = ( 600 | FBE860822B42F1E40053AD2F /* Base */, 601 | ); 602 | name = Main.storyboard; 603 | sourceTree = ""; 604 | }; 605 | FBE8608D2B42F1E40053AD2F /* Main.storyboard */ = { 606 | isa = PBXVariantGroup; 607 | children = ( 608 | FBE8608E2B42F1E40053AD2F /* Base */, 609 | ); 610 | name = Main.storyboard; 611 | sourceTree = ""; 612 | }; 613 | /* End PBXVariantGroup section */ 614 | 615 | /* Begin XCBuildConfiguration section */ 616 | FBE860C42B42F1E40053AD2F /* Debug */ = { 617 | isa = XCBuildConfiguration; 618 | buildSettings = { 619 | ALWAYS_SEARCH_USER_PATHS = NO; 620 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 621 | CLANG_ANALYZER_NONNULL = YES; 622 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 623 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 624 | CLANG_ENABLE_MODULES = YES; 625 | CLANG_ENABLE_OBJC_ARC = YES; 626 | CLANG_ENABLE_OBJC_WEAK = YES; 627 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 628 | CLANG_WARN_BOOL_CONVERSION = YES; 629 | CLANG_WARN_COMMA = YES; 630 | CLANG_WARN_CONSTANT_CONVERSION = YES; 631 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 632 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 633 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 634 | CLANG_WARN_EMPTY_BODY = YES; 635 | CLANG_WARN_ENUM_CONVERSION = YES; 636 | CLANG_WARN_INFINITE_RECURSION = YES; 637 | CLANG_WARN_INT_CONVERSION = YES; 638 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 639 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 640 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 641 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 642 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 643 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 644 | CLANG_WARN_STRICT_PROTOTYPES = YES; 645 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 646 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 647 | CLANG_WARN_UNREACHABLE_CODE = YES; 648 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 649 | COPY_PHASE_STRIP = NO; 650 | CURRENT_PROJECT_VERSION = 30; 651 | DEAD_CODE_STRIPPING = YES; 652 | DEBUG_INFORMATION_FORMAT = dwarf; 653 | DEVELOPMENT_TEAM = 2RDKJDLNY8; 654 | ENABLE_STRICT_OBJC_MSGSEND = YES; 655 | ENABLE_TESTABILITY = YES; 656 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 657 | GCC_C_LANGUAGE_STANDARD = gnu17; 658 | GCC_DYNAMIC_NO_PIC = NO; 659 | GCC_NO_COMMON_BLOCKS = YES; 660 | GCC_OPTIMIZATION_LEVEL = 0; 661 | GCC_PREPROCESSOR_DEFINITIONS = ( 662 | "DEBUG=1", 663 | "$(inherited)", 664 | ); 665 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 666 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 667 | GCC_WARN_UNDECLARED_SELECTOR = YES; 668 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 669 | GCC_WARN_UNUSED_FUNCTION = YES; 670 | GCC_WARN_UNUSED_VARIABLE = YES; 671 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 672 | MARKETING_VERSION = 1.11.2; 673 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 674 | MTL_FAST_MATH = YES; 675 | ONLY_ACTIVE_ARCH = YES; 676 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 677 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 678 | }; 679 | name = Debug; 680 | }; 681 | FBE860C52B42F1E40053AD2F /* Release */ = { 682 | isa = XCBuildConfiguration; 683 | buildSettings = { 684 | ALWAYS_SEARCH_USER_PATHS = NO; 685 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 686 | CLANG_ANALYZER_NONNULL = YES; 687 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 688 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 689 | CLANG_ENABLE_MODULES = YES; 690 | CLANG_ENABLE_OBJC_ARC = YES; 691 | CLANG_ENABLE_OBJC_WEAK = YES; 692 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 693 | CLANG_WARN_BOOL_CONVERSION = YES; 694 | CLANG_WARN_COMMA = YES; 695 | CLANG_WARN_CONSTANT_CONVERSION = YES; 696 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 697 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 698 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 699 | CLANG_WARN_EMPTY_BODY = YES; 700 | CLANG_WARN_ENUM_CONVERSION = YES; 701 | CLANG_WARN_INFINITE_RECURSION = YES; 702 | CLANG_WARN_INT_CONVERSION = YES; 703 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 704 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 705 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 706 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 707 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 708 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 709 | CLANG_WARN_STRICT_PROTOTYPES = YES; 710 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 711 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 712 | CLANG_WARN_UNREACHABLE_CODE = YES; 713 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 714 | COPY_PHASE_STRIP = NO; 715 | CURRENT_PROJECT_VERSION = 30; 716 | DEAD_CODE_STRIPPING = YES; 717 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 718 | DEVELOPMENT_TEAM = 2RDKJDLNY8; 719 | ENABLE_NS_ASSERTIONS = NO; 720 | ENABLE_STRICT_OBJC_MSGSEND = YES; 721 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 722 | GCC_C_LANGUAGE_STANDARD = gnu17; 723 | GCC_NO_COMMON_BLOCKS = YES; 724 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 725 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 726 | GCC_WARN_UNDECLARED_SELECTOR = YES; 727 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 728 | GCC_WARN_UNUSED_FUNCTION = YES; 729 | GCC_WARN_UNUSED_VARIABLE = YES; 730 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 731 | MARKETING_VERSION = 1.11.2; 732 | MTL_ENABLE_DEBUG_INFO = NO; 733 | MTL_FAST_MATH = YES; 734 | SWIFT_COMPILATION_MODE = wholemodule; 735 | }; 736 | name = Release; 737 | }; 738 | FBE860C72B42F1E40053AD2F /* Debug */ = { 739 | isa = XCBuildConfiguration; 740 | buildSettings = { 741 | CODE_SIGN_STYLE = Automatic; 742 | GENERATE_INFOPLIST_FILE = YES; 743 | INFOPLIST_FILE = "iOS (Extension)/Info.plist"; 744 | INFOPLIST_KEY_CFBundleDisplayName = "Control Panel for YouTube Extension"; 745 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 746 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 747 | LD_RUNPATH_SEARCH_PATHS = ( 748 | "$(inherited)", 749 | "@executable_path/Frameworks", 750 | "@executable_path/../../Frameworks", 751 | ); 752 | OTHER_LDFLAGS = ( 753 | "-framework", 754 | SafariServices, 755 | ); 756 | PRODUCT_BUNDLE_IDENTIFIER = "dev.jbscript.Control-Panel-for-YouTube.Extension"; 757 | PRODUCT_NAME = "Control Panel for YouTube Extension"; 758 | SDKROOT = iphoneos; 759 | SKIP_INSTALL = YES; 760 | SWIFT_EMIT_LOC_STRINGS = YES; 761 | SWIFT_VERSION = 5.0; 762 | TARGETED_DEVICE_FAMILY = "1,2"; 763 | }; 764 | name = Debug; 765 | }; 766 | FBE860C82B42F1E40053AD2F /* Release */ = { 767 | isa = XCBuildConfiguration; 768 | buildSettings = { 769 | CODE_SIGN_STYLE = Automatic; 770 | GENERATE_INFOPLIST_FILE = YES; 771 | INFOPLIST_FILE = "iOS (Extension)/Info.plist"; 772 | INFOPLIST_KEY_CFBundleDisplayName = "Control Panel for YouTube Extension"; 773 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 774 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 775 | LD_RUNPATH_SEARCH_PATHS = ( 776 | "$(inherited)", 777 | "@executable_path/Frameworks", 778 | "@executable_path/../../Frameworks", 779 | ); 780 | OTHER_LDFLAGS = ( 781 | "-framework", 782 | SafariServices, 783 | ); 784 | PRODUCT_BUNDLE_IDENTIFIER = "dev.jbscript.Control-Panel-for-YouTube.Extension"; 785 | PRODUCT_NAME = "Control Panel for YouTube Extension"; 786 | SDKROOT = iphoneos; 787 | SKIP_INSTALL = YES; 788 | SWIFT_EMIT_LOC_STRINGS = YES; 789 | SWIFT_VERSION = 5.0; 790 | TARGETED_DEVICE_FAMILY = "1,2"; 791 | VALIDATE_PRODUCT = YES; 792 | }; 793 | name = Release; 794 | }; 795 | FBE860CB2B42F1E40053AD2F /* Debug */ = { 796 | isa = XCBuildConfiguration; 797 | buildSettings = { 798 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 799 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 800 | CODE_SIGN_STYLE = Automatic; 801 | CURRENT_PROJECT_VERSION = 30; 802 | GENERATE_INFOPLIST_FILE = YES; 803 | INFOPLIST_FILE = "iOS (App)/Info.plist"; 804 | INFOPLIST_KEY_CFBundleDisplayName = "Control Panel for YouTube"; 805 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 806 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 807 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 808 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 809 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 810 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 811 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 812 | LD_RUNPATH_SEARCH_PATHS = ( 813 | "$(inherited)", 814 | "@executable_path/Frameworks", 815 | ); 816 | MARKETING_VERSION = 1.11.2; 817 | OTHER_LDFLAGS = ( 818 | "-framework", 819 | SafariServices, 820 | "-framework", 821 | WebKit, 822 | ); 823 | PRODUCT_BUNDLE_IDENTIFIER = "dev.jbscript.Control-Panel-for-YouTube"; 824 | PRODUCT_NAME = "Control Panel for YouTube"; 825 | SDKROOT = iphoneos; 826 | SWIFT_EMIT_LOC_STRINGS = YES; 827 | SWIFT_VERSION = 5.0; 828 | TARGETED_DEVICE_FAMILY = "1,2"; 829 | }; 830 | name = Debug; 831 | }; 832 | FBE860CC2B42F1E40053AD2F /* Release */ = { 833 | isa = XCBuildConfiguration; 834 | buildSettings = { 835 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 836 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 837 | CODE_SIGN_STYLE = Automatic; 838 | CURRENT_PROJECT_VERSION = 30; 839 | GENERATE_INFOPLIST_FILE = YES; 840 | INFOPLIST_FILE = "iOS (App)/Info.plist"; 841 | INFOPLIST_KEY_CFBundleDisplayName = "Control Panel for YouTube"; 842 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 843 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 844 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 845 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 846 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 847 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 848 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 849 | LD_RUNPATH_SEARCH_PATHS = ( 850 | "$(inherited)", 851 | "@executable_path/Frameworks", 852 | ); 853 | MARKETING_VERSION = 1.11.2; 854 | OTHER_LDFLAGS = ( 855 | "-framework", 856 | SafariServices, 857 | "-framework", 858 | WebKit, 859 | ); 860 | PRODUCT_BUNDLE_IDENTIFIER = "dev.jbscript.Control-Panel-for-YouTube"; 861 | PRODUCT_NAME = "Control Panel for YouTube"; 862 | SDKROOT = iphoneos; 863 | SWIFT_EMIT_LOC_STRINGS = YES; 864 | SWIFT_VERSION = 5.0; 865 | TARGETED_DEVICE_FAMILY = "1,2"; 866 | VALIDATE_PRODUCT = YES; 867 | }; 868 | name = Release; 869 | }; 870 | FBE860CE2B42F1E40053AD2F /* Debug */ = { 871 | isa = XCBuildConfiguration; 872 | buildSettings = { 873 | CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/Control Panel for YouTube.entitlements"; 874 | CODE_SIGN_STYLE = Automatic; 875 | DEAD_CODE_STRIPPING = YES; 876 | ENABLE_HARDENED_RUNTIME = YES; 877 | GENERATE_INFOPLIST_FILE = YES; 878 | INFOPLIST_FILE = "macOS (Extension)/Info.plist"; 879 | INFOPLIST_KEY_CFBundleDisplayName = "Control Panel for YouTube Extension"; 880 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 881 | LD_RUNPATH_SEARCH_PATHS = ( 882 | "$(inherited)", 883 | "@executable_path/../Frameworks", 884 | "@executable_path/../../../../Frameworks", 885 | ); 886 | MACOSX_DEPLOYMENT_TARGET = 11.0; 887 | OTHER_LDFLAGS = ( 888 | "-framework", 889 | SafariServices, 890 | ); 891 | PRODUCT_BUNDLE_IDENTIFIER = "dev.jbscript.Control-Panel-for-YouTube.Extension"; 892 | PRODUCT_NAME = "Control Panel for YouTube Extension"; 893 | SDKROOT = macosx; 894 | SKIP_INSTALL = YES; 895 | SWIFT_EMIT_LOC_STRINGS = YES; 896 | SWIFT_VERSION = 5.0; 897 | }; 898 | name = Debug; 899 | }; 900 | FBE860CF2B42F1E40053AD2F /* Release */ = { 901 | isa = XCBuildConfiguration; 902 | buildSettings = { 903 | CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/Control Panel for YouTube.entitlements"; 904 | CODE_SIGN_STYLE = Automatic; 905 | DEAD_CODE_STRIPPING = YES; 906 | ENABLE_HARDENED_RUNTIME = YES; 907 | GENERATE_INFOPLIST_FILE = YES; 908 | INFOPLIST_FILE = "macOS (Extension)/Info.plist"; 909 | INFOPLIST_KEY_CFBundleDisplayName = "Control Panel for YouTube Extension"; 910 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 911 | LD_RUNPATH_SEARCH_PATHS = ( 912 | "$(inherited)", 913 | "@executable_path/../Frameworks", 914 | "@executable_path/../../../../Frameworks", 915 | ); 916 | MACOSX_DEPLOYMENT_TARGET = 11.0; 917 | OTHER_LDFLAGS = ( 918 | "-framework", 919 | SafariServices, 920 | ); 921 | PRODUCT_BUNDLE_IDENTIFIER = "dev.jbscript.Control-Panel-for-YouTube.Extension"; 922 | PRODUCT_NAME = "Control Panel for YouTube Extension"; 923 | SDKROOT = macosx; 924 | SKIP_INSTALL = YES; 925 | SWIFT_EMIT_LOC_STRINGS = YES; 926 | SWIFT_VERSION = 5.0; 927 | }; 928 | name = Release; 929 | }; 930 | FBE860D22B42F1E40053AD2F /* Debug */ = { 931 | isa = XCBuildConfiguration; 932 | buildSettings = { 933 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 934 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 935 | CODE_SIGN_ENTITLEMENTS = "macOS (App)/Control Panel for YouTube.entitlements"; 936 | CODE_SIGN_STYLE = Automatic; 937 | DEAD_CODE_STRIPPING = YES; 938 | ENABLE_HARDENED_RUNTIME = YES; 939 | GENERATE_INFOPLIST_FILE = YES; 940 | INFOPLIST_KEY_CFBundleDisplayName = "Control Panel for YouTube"; 941 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 942 | INFOPLIST_KEY_NSMainStoryboardFile = Main; 943 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 944 | LD_RUNPATH_SEARCH_PATHS = ( 945 | "$(inherited)", 946 | "@executable_path/../Frameworks", 947 | ); 948 | MACOSX_DEPLOYMENT_TARGET = 11.0; 949 | OTHER_LDFLAGS = ( 950 | "-framework", 951 | SafariServices, 952 | "-framework", 953 | WebKit, 954 | ); 955 | PRODUCT_BUNDLE_IDENTIFIER = "dev.jbscript.Control-Panel-for-YouTube"; 956 | PRODUCT_NAME = "Control Panel for YouTube"; 957 | SDKROOT = macosx; 958 | SWIFT_EMIT_LOC_STRINGS = YES; 959 | SWIFT_VERSION = 5.0; 960 | }; 961 | name = Debug; 962 | }; 963 | FBE860D32B42F1E40053AD2F /* Release */ = { 964 | isa = XCBuildConfiguration; 965 | buildSettings = { 966 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 967 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 968 | CODE_SIGN_ENTITLEMENTS = "macOS (App)/Control Panel for YouTube.entitlements"; 969 | CODE_SIGN_STYLE = Automatic; 970 | DEAD_CODE_STRIPPING = YES; 971 | ENABLE_HARDENED_RUNTIME = YES; 972 | GENERATE_INFOPLIST_FILE = YES; 973 | INFOPLIST_KEY_CFBundleDisplayName = "Control Panel for YouTube"; 974 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 975 | INFOPLIST_KEY_NSMainStoryboardFile = Main; 976 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 977 | LD_RUNPATH_SEARCH_PATHS = ( 978 | "$(inherited)", 979 | "@executable_path/../Frameworks", 980 | ); 981 | MACOSX_DEPLOYMENT_TARGET = 11.0; 982 | OTHER_LDFLAGS = ( 983 | "-framework", 984 | SafariServices, 985 | "-framework", 986 | WebKit, 987 | ); 988 | PRODUCT_BUNDLE_IDENTIFIER = "dev.jbscript.Control-Panel-for-YouTube"; 989 | PRODUCT_NAME = "Control Panel for YouTube"; 990 | SDKROOT = macosx; 991 | SWIFT_EMIT_LOC_STRINGS = YES; 992 | SWIFT_VERSION = 5.0; 993 | }; 994 | name = Release; 995 | }; 996 | /* End XCBuildConfiguration section */ 997 | 998 | /* Begin XCConfigurationList section */ 999 | FBE8605E2B42F1E30053AD2F /* Build configuration list for PBXProject "Control Panel for YouTube" */ = { 1000 | isa = XCConfigurationList; 1001 | buildConfigurations = ( 1002 | FBE860C42B42F1E40053AD2F /* Debug */, 1003 | FBE860C52B42F1E40053AD2F /* Release */, 1004 | ); 1005 | defaultConfigurationIsVisible = 0; 1006 | defaultConfigurationName = Release; 1007 | }; 1008 | FBE860C62B42F1E40053AD2F /* Build configuration list for PBXNativeTarget "Control Panel for YouTube Extension (iOS)" */ = { 1009 | isa = XCConfigurationList; 1010 | buildConfigurations = ( 1011 | FBE860C72B42F1E40053AD2F /* Debug */, 1012 | FBE860C82B42F1E40053AD2F /* Release */, 1013 | ); 1014 | defaultConfigurationIsVisible = 0; 1015 | defaultConfigurationName = Release; 1016 | }; 1017 | FBE860CA2B42F1E40053AD2F /* Build configuration list for PBXNativeTarget "Control Panel for YouTube (iOS)" */ = { 1018 | isa = XCConfigurationList; 1019 | buildConfigurations = ( 1020 | FBE860CB2B42F1E40053AD2F /* Debug */, 1021 | FBE860CC2B42F1E40053AD2F /* Release */, 1022 | ); 1023 | defaultConfigurationIsVisible = 0; 1024 | defaultConfigurationName = Release; 1025 | }; 1026 | FBE860CD2B42F1E40053AD2F /* Build configuration list for PBXNativeTarget "Control Panel for YouTube Extension (macOS)" */ = { 1027 | isa = XCConfigurationList; 1028 | buildConfigurations = ( 1029 | FBE860CE2B42F1E40053AD2F /* Debug */, 1030 | FBE860CF2B42F1E40053AD2F /* Release */, 1031 | ); 1032 | defaultConfigurationIsVisible = 0; 1033 | defaultConfigurationName = Release; 1034 | }; 1035 | FBE860D12B42F1E40053AD2F /* Build configuration list for PBXNativeTarget "Control Panel for YouTube (macOS)" */ = { 1036 | isa = XCConfigurationList; 1037 | buildConfigurations = ( 1038 | FBE860D22B42F1E40053AD2F /* Debug */, 1039 | FBE860D32B42F1E40053AD2F /* Release */, 1040 | ); 1041 | defaultConfigurationIsVisible = 0; 1042 | defaultConfigurationName = Release; 1043 | }; 1044 | /* End XCConfigurationList section */ 1045 | }; 1046 | rootObject = FBE8605B2B42F1E30053AD2F /* Project object */; 1047 | } 1048 | -------------------------------------------------------------------------------- /safari/Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "appicon1024-fullbleed.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | }, 9 | { 10 | "filename" : "appicon16.png", 11 | "idiom" : "mac", 12 | "scale" : "1x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "appicon32.png", 17 | "idiom" : "mac", 18 | "scale" : "2x", 19 | "size" : "16x16" 20 | }, 21 | { 22 | "filename" : "appicon32.png", 23 | "idiom" : "mac", 24 | "scale" : "1x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "appicon64.png", 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "32x32" 32 | }, 33 | { 34 | "filename" : "appicon128.png", 35 | "idiom" : "mac", 36 | "scale" : "1x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "appicon256.png", 41 | "idiom" : "mac", 42 | "scale" : "2x", 43 | "size" : "128x128" 44 | }, 45 | { 46 | "filename" : "appicon256.png", 47 | "idiom" : "mac", 48 | "scale" : "1x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "appicon512.png", 53 | "idiom" : "mac", 54 | "scale" : "2x", 55 | "size" : "256x256" 56 | }, 57 | { 58 | "filename" : "appicon512.png", 59 | "idiom" : "mac", 60 | "scale" : "1x", 61 | "size" : "512x512" 62 | }, 63 | { 64 | "filename" : "appicon1024.png", 65 | "idiom" : "mac", 66 | "scale" : "2x", 67 | "size" : "512x512" 68 | } 69 | ], 70 | "info" : { 71 | "author" : "xcode", 72 | "version" : 1 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon1024-fullbleed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon1024-fullbleed.png -------------------------------------------------------------------------------- /safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon1024.png -------------------------------------------------------------------------------- /safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon128.png -------------------------------------------------------------------------------- /safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon16.png -------------------------------------------------------------------------------- /safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon256.png -------------------------------------------------------------------------------- /safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon32.png -------------------------------------------------------------------------------- /safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon512.png -------------------------------------------------------------------------------- /safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/appicon64.png -------------------------------------------------------------------------------- /safari/Shared (App)/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /safari/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /safari/Shared (App)/Base.lproj/Main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | Control Panel for YouTube 13 |
You can turn on Control Panel for YouTube’s Safari extension in Settings.
14 |
You can turn on Control Panel for YouTube’s extension in Safari Extensions preferences.
15 |
Control Panel for YouTube’s extension is currently on. You can turn it off in Safari Extensions preferences.
16 |
Control Panel for YouTube’s extension is currently off. You can turn it on in Safari Extensions preferences.
17 | 18 |
19 |
20 |
21 |
Ad
22 | Control Panel for Twitter 23 |
24 |
25 |
26 | Missing third-party Twitter clients on iOS? 27 | Ditch the app and take control of the web version with Control Panel for Twitter 28 |
29 |
30 | “Works beautifully in Safari. Beautiful UI, too” — @zeldman 31 |
32 |
33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /safari/Shared (App)/Resources/Ad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/safari/Shared (App)/Resources/Ad.png -------------------------------------------------------------------------------- /safari/Shared (App)/Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insin/control-panel-for-youtube/0f9828afd73e1476c802dcd640bafb46874c568c/safari/Shared (App)/Resources/Icon.png -------------------------------------------------------------------------------- /safari/Shared (App)/Resources/Script.js: -------------------------------------------------------------------------------- 1 | function show(platform, enabled, useSettingsInsteadOfPreferences) { 2 | document.body.classList.add(`platform-${platform}`) 3 | 4 | if (useSettingsInsteadOfPreferences) { 5 | document.querySelector('.platform-mac.state-on').innerText = 'Control Panel for YouTube’s extension is currently on. You can turn it off in the Extensions section of Safari Settings.' 6 | document.querySelector('.platform-mac.state-off').innerText = 'Control Panel for YouTube’s extension is currently off. You can turn it on in the Extensions section of Safari Settings.' 7 | document.querySelector('.platform-mac.state-unknown').innerText = 'You can turn on Control Panel for YouTube’s extension in the Extensions section of Safari Settings.' 8 | document.querySelector('.open-preferences').innerText = 'Quit and Open Safari Settings…' 9 | } 10 | 11 | if (typeof enabled === 'boolean') { 12 | document.body.classList.toggle('state-on', enabled) 13 | document.body.classList.toggle('state-off', !enabled) 14 | } else { 15 | document.body.classList.remove('state-on', 'state-off') 16 | } 17 | 18 | if (platform === 'ios') { 19 | document.querySelector('.open-preferences').innerText = 'Open Safari Extensions Preferences…' 20 | } 21 | } 22 | 23 | document.querySelector('button.open-preferences').addEventListener('click', () => { 24 | webkit.messageHandlers.controller.postMessage('open-preferences') 25 | }) 26 | 27 | document.querySelector('.ad').addEventListener('click', () =>{ 28 | webkit.messageHandlers.controller.postMessage('open-ad') 29 | }) 30 | -------------------------------------------------------------------------------- /safari/Shared (App)/Resources/Style.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-user-select: none; 3 | -webkit-user-drag: none; 4 | cursor: default; 5 | } 6 | 7 | :root { 8 | color-scheme: light dark; 9 | } 10 | 11 | html { 12 | height: 100%; 13 | } 14 | 15 | body { 16 | display: flex; 17 | flex-direction: column; 18 | align-items: center; 19 | height: 100%; 20 | font: -apple-system-short-body; 21 | } 22 | 23 | body:not(.platform-mac, .platform-ios) :is(.platform-mac, .platform-ios), 24 | body:not(.state-on, .state-off) :is(.state-on, .state-off), 25 | body.platform-ios .platform-mac, 26 | body.platform-mac .platform-ios, 27 | body.state-on :is(.state-off, .state-unknown), 28 | body.state-off :is(.state-on, .state-unknown) { 29 | display: none; 30 | } 31 | 32 | .app { 33 | flex: 1; 34 | display: flex; 35 | flex-direction: column; 36 | align-items: center; 37 | justify-content: center; 38 | gap: 1.25rem; 39 | text-align: center; 40 | } 41 | 42 | .ad { 43 | display: flex; 44 | flex-direction: row; 45 | align-items: stretch; 46 | gap: .5rem; 47 | border: 1px solid; 48 | border-radius: 1.5rem; 49 | font-size: .875rem; 50 | margin: 0 1rem 1rem 1rem; 51 | padding: .75rem 1rem 1rem .5rem; 52 | max-width: 26rem; 53 | } 54 | 55 | button { 56 | font-size: 1rem; 57 | } 58 | 59 | .flex { 60 | display: flex; 61 | } 62 | 63 | .flex-col { 64 | flex-direction: column; 65 | } 66 | 67 | .font-bold { 68 | font-weight: bold; 69 | } 70 | 71 | .justify-between { 72 | justify-content: space-between; 73 | } 74 | 75 | .-mb-2 { 76 | margin-bottom: -.5rem; 77 | } 78 | 79 | .mb-4 { 80 | margin-bottom: 1rem; 81 | } 82 | 83 | .text-center { 84 | text-align: center; 85 | } -------------------------------------------------------------------------------- /safari/Shared (App)/ViewController.swift: -------------------------------------------------------------------------------- 1 | import WebKit 2 | 3 | #if os(iOS) 4 | import UIKit 5 | typealias PlatformViewController = UIViewController 6 | #elseif os(macOS) 7 | import Cocoa 8 | import SafariServices 9 | typealias PlatformViewController = NSViewController 10 | #endif 11 | 12 | let extensionBundleIdentifier = "dev.jbscript.Control-Panel-for-YouTube.Extension" 13 | 14 | class ViewController: PlatformViewController, WKNavigationDelegate, WKScriptMessageHandler { 15 | 16 | @IBOutlet var webView: WKWebView! 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | self.webView.navigationDelegate = self 22 | 23 | #if os(iOS) 24 | self.webView.scrollView.isScrollEnabled = false 25 | #endif 26 | 27 | self.webView.configuration.userContentController.add(self, name: "controller") 28 | 29 | self.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!) 30 | } 31 | 32 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 33 | #if os(iOS) 34 | webView.evaluateJavaScript("show('ios')") 35 | #elseif os(macOS) 36 | webView.evaluateJavaScript("show('mac')") 37 | 38 | SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in 39 | guard let state = state, error == nil else { 40 | // Insert code to inform the user that something went wrong. 41 | return 42 | } 43 | 44 | DispatchQueue.main.async { 45 | if #available(macOS 13, *) { 46 | webView.evaluateJavaScript("show('mac', \(state.isEnabled), true)") 47 | } else { 48 | webView.evaluateJavaScript("show('mac', \(state.isEnabled), false)") 49 | } 50 | } 51 | } 52 | #endif 53 | } 54 | 55 | func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { 56 | #if os(iOS) 57 | if (message.body as! String == "open-ad") { 58 | let url = URL(string: "https://soitis.dev/control-panel-for-twitter")! 59 | if UIApplication.shared.canOpenURL(url){ 60 | UIApplication.shared.open(url, options: [:], completionHandler: nil) 61 | } 62 | return; 63 | } 64 | #endif 65 | if (message.body as! String != "open-preferences") { 66 | return 67 | } 68 | #if os(iOS) 69 | let url = URL(string: "App-Prefs:Safari&path=WEB_EXTENSIONS")! 70 | guard UIApplication.shared.canOpenURL(url) else { 71 | return 72 | } 73 | UIApplication.shared.open(url) 74 | #elseif os(macOS) 75 | SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in 76 | guard error == nil else { 77 | // Insert code to inform the user that something went wrong. 78 | return 79 | } 80 | 81 | DispatchQueue.main.async { 82 | NSApp.terminate(self) 83 | } 84 | } 85 | #endif 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /safari/Shared (Extension)/Resources/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "default_locale": "en", 4 | "name": "__MSG_extensionName__", 5 | "description": "__MSG_extensionDescription__", 6 | "homepage_url": "https://soitis.dev/control-panel-for-youtube", 7 | "version": "1.11.2", 8 | "icons": { 9 | "48": "icon48.png", 10 | "96": "icon96.png", 11 | "128": "icon128.png", 12 | "256": "icon256.png", 13 | "512": "icon512.png" 14 | }, 15 | "content_scripts": [ 16 | { 17 | "matches": [ 18 | "https://www.youtube.com/*", 19 | "https://m.youtube.com/*" 20 | ], 21 | "exclude_matches": [ 22 | "https://www.youtube.com/embed/*" 23 | ], 24 | "js": [ 25 | "content.js" 26 | ] 27 | }, 28 | { 29 | "world": "MAIN", 30 | "matches": [ 31 | "https://www.youtube.com/*", 32 | "https://m.youtube.com/*" 33 | ], 34 | "exclude_matches": [ 35 | "https://www.youtube.com/embed/*" 36 | ], 37 | "js": [ 38 | "page.js" 39 | ] 40 | }, 41 | { 42 | "matches": [ 43 | "https://www.youtube.com/embed/*", 44 | "https://www.youtube-nocookie.com/embed/*" 45 | ], 46 | "js": [ 47 | "embed.js" 48 | ], 49 | "all_frames": true 50 | } 51 | ], 52 | "options_ui": { 53 | "page": "options.html" 54 | }, 55 | "action": { 56 | "default_title": "__MSG_extensionName__", 57 | "default_popup": "browser_action.html", 58 | "default_icon": { 59 | "16": "toolbar-icon16.png", 60 | "19": "toolbar-icon19.png", 61 | "32": "toolbar-icon32.png", 62 | "38": "toolbar-icon38.png", 63 | "48": "toolbar-icon48.png", 64 | "72": "toolbar-icon72.png" 65 | } 66 | }, 67 | "permissions": [ 68 | "storage" 69 | ] 70 | } -------------------------------------------------------------------------------- /safari/Shared (Extension)/SafariWebExtensionHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariWebExtensionHandler.swift 3 | // Shared (Extension) 4 | // 5 | 6 | import SafariServices 7 | import os.log 8 | 9 | class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { 10 | 11 | func beginRequest(with context: NSExtensionContext) { 12 | let request = context.inputItems.first as? NSExtensionItem 13 | 14 | let profile: UUID? 15 | if #available(iOS 17.0, macOS 14.0, *) { 16 | profile = request?.userInfo?[SFExtensionProfileKey] as? UUID 17 | } else { 18 | profile = request?.userInfo?["profile"] as? UUID 19 | } 20 | 21 | let message: Any? 22 | if #available(iOS 17.0, macOS 14.0, *) { 23 | message = request?.userInfo?[SFExtensionMessageKey] 24 | } else { 25 | message = request?.userInfo?["message"] 26 | } 27 | 28 | os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@ (profile: %@)", String(describing: message), profile?.uuidString ?? "none") 29 | 30 | let response = NSExtensionItem() 31 | response.userInfo = [ SFExtensionMessageKey: [ "echo": message ] ] 32 | 33 | context.completeRequest(returningItems: [ response ], completionHandler: nil) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /safari/iOS (App)/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // iOS (App) 4 | // 5 | 6 | import UIKit 7 | 8 | @main 9 | class AppDelegate: UIResponder, UIApplicationDelegate { 10 | 11 | var window: UIWindow? 12 | 13 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 14 | // Override point for customization after application launch. 15 | return true 16 | } 17 | 18 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 19 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /safari/iOS (App)/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /safari/iOS (App)/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /safari/iOS (App)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | UISceneStoryboardFile 19 | Main 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /safari/iOS (App)/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // iOS (App) 4 | // 5 | 6 | import UIKit 7 | 8 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 9 | 10 | var window: UIWindow? 11 | 12 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 13 | guard let _ = (scene as? UIWindowScene) else { return } 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /safari/iOS (Extension)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.Safari.web-extension 9 | NSExtensionPrincipalClass 10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /safari/macOS (App)/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // macOS (App) 4 | // 5 | 6 | import Cocoa 7 | 8 | @main 9 | class AppDelegate: NSObject, NSApplicationDelegate { 10 | 11 | func applicationDidFinishLaunching(_ notification: Notification) { 12 | // Override point for customization after application launch. 13 | } 14 | 15 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 16 | return true 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /safari/macOS (App)/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /safari/macOS (App)/Control Panel for YouTube.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /safari/macOS (Extension)/Control Panel for YouTube.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /safari/macOS (Extension)/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.Safari.web-extension 9 | NSExtensionPrincipalClass 10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | const {execSync} = require('child_process') 2 | const path = require('path') 3 | const fs = require('fs') 4 | 5 | let manifestVersions = [2, 3] 6 | if (process.argv[2] && manifestVersions.includes(Number(process.argv[2]))) { 7 | manifestVersions = [Number(process.argv[2])] 8 | } 9 | 10 | for (let manifestVersion of manifestVersions) { 11 | console.log(`\nBuilding MV${manifestVersion} version`) 12 | let manifestFile = `manifest.mv${manifestVersion}.json` 13 | let manifestData = require(`../${manifestFile}`) 14 | fs.copyFileSync(`./${manifestFile}`, './manifest.json') 15 | execSync('web-ext build', {stdio: 'inherit'}) 16 | let renameTo = `./web-ext-artifacts/control_panel_for_youtube-${manifestData['version']}.mv${manifestVersion}.zip` 17 | fs.renameSync( 18 | `./web-ext-artifacts/control_panel_for_youtube-${manifestData['version']}.zip`, 19 | renameTo, 20 | ) 21 | console.log('Moved to:', path.resolve(renameTo)) 22 | fs.rmSync('./manifest.json') 23 | } -------------------------------------------------------------------------------- /scripts/copy.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | fs.copyFileSync(process.argv[2], process.argv[3]) -------------------------------------------------------------------------------- /scripts/create-browser-action.js: -------------------------------------------------------------------------------- 1 | // Creates browser_action.html, which is just options.html with styling to 2 | // control the popup width appropriately for each browser. 3 | const fs = require('fs') 4 | 5 | let options = fs.readFileSync('./options.html', {encoding: 'utf8'}) 6 | 7 | fs.writeFileSync( 8 | './browser_action.html', 9 | options.replace('', ''), 10 | {encoding: 'utf8'} 11 | ) -------------------------------------------------------------------------------- /scripts/create-store-description.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | 3 | import clipboard from 'clipboardy' 4 | 5 | let captions = { 6 | "subs": { 7 | "en": "Improved Subscriptions page, which acts more like an inbox of videos", 8 | "ja": "ビデオの受信箱のように機能する、改善されたサブスクリプションページ", 9 | "zh_CN": "改进的订阅页面,更像是一个视频收件箱", 10 | "fr": "Page d'abonnements améliorée, agissant comme une boîte de réception pour les vidéos", 11 | }, 12 | "ads": { 13 | "en": "Video ads can be automatically skipped and other promoted content is hidden", 14 | "ja": "動画広告は自動的にスキップされ、他のプロモーションコンテンツは非表示になります", 15 | "zh_CN": "视频广告可自动跳过,其他推广内容将被隐藏", 16 | "fr": "Les publicités vidéo sont automatiquement ignorées et les autres contenus promus sont masqués", 17 | }, 18 | "search": { 19 | "en": "Nothing but search results on the Search page, no suggested sections", 20 | "ja": "検索ページでは検索結果のみ表示され、提案されたセクションは表示されません", 21 | "zh_CN": "搜索页面仅显示搜索结果,不包含推荐内容", 22 | "fr": "Uniquement les résultats de recherche sur la page Recherche, aucune section suggérée", 23 | }, 24 | "home": { 25 | "en": "Hide non-video content on the Home page and improve YouTube's built-in hiding", 26 | "ja": "ホームページの非動画コンテンツを非表示にし、YouTubeの組み込み非表示機能を改善します", 27 | "zh_CN": "隐藏主页上的非视频内容,并改进YouTube的内置隐藏功能", 28 | "fr": "Masquer le contenu non vidéo sur la page d'accueil et améliorer la fonction de masquage intégrée de YouTube", 29 | }, 30 | "disengage": { 31 | "en": "Disable related videos, the Home timeline and other algorithmic content", 32 | "ja": "関連動画、ホームタイムライン、およびその他のアルゴリズムコンテンツを無効にします", 33 | "zh_CN": "禁用相关视频、主页时间线和其他算法推荐内容", 34 | "fr": "Désactiver les vidéos associées, le fil d'accueil et les autres contenus algorithmiques", 35 | }, 36 | "embedded": { 37 | "en": "Hide algorithmic recommendations when viewing YouTube videos embedded in other sites", 38 | "ja": "他のサイトに埋め込まれたYouTube動画を表示する際にアルゴリズム推薦を非表示にします", 39 | "zh_CN": "在其他网站观看嵌入的YouTube视频时隐藏算法推荐", 40 | "fr": "Masquer les recommandations algorithmiques lors du visionnage de vidéos YouTube intégrées sur d'autres sites", 41 | } 42 | } 43 | 44 | let extraTranslations = { 45 | "desktopVersion": { 46 | "en": " (desktop version)", 47 | "ja": "(デスクトップ版)", 48 | "zh_CN": "(桌面版)", 49 | "fr": " (version bureau)", 50 | }, 51 | "mobileVersion": { 52 | "en": " (mobile version)", 53 | "ja": "(モバイル版)", 54 | "zh_CN": "(手机版)", 55 | "fr": " (version mobile)", 56 | } 57 | } 58 | 59 | let localeCode = process.argv[2] || 'en' 60 | 61 | if (process.argv.some(arg => /^-h|--help$/.test(arg))) { 62 | console.log(` 63 | Usage: 64 | npm run create-store-description ja 65 | npm run create-store-description ja [html|md] 66 | `.trim()) 67 | process.exit(1) 68 | } 69 | 70 | // Get translated messages for locale 71 | let locale = JSON.parse(fs.readFileSync(`./_locales/${localeCode}/messages.json`, {encoding: 'utf8'})) 72 | let messages = Object.fromEntries(Object.entries(locale).map(([prop, value]) => ([prop, value.message]))) 73 | // Add extra translations 74 | Object.assign(messages, Object.fromEntries(Object.entries(extraTranslations).map(([prop, value]) => [prop, value[localeCode]]))) 75 | 76 | let storeDescription = ` 77 | ${messages.features} 78 | 79 | • ${messages.hideAI} 80 | • ${messages.removePink} 81 | • ${messages.hidePremiumUpsells} 82 | • ${messages.pauseChannelTrailers}${messages.desktopVersion} 83 | 84 | ${messages.videoLists}: 85 | 86 | • ${messages.hideSponsored} 87 | • ${messages.hideSuggestedSections} 88 | • ${messages.hideLive} 89 | • ${messages.hideStreamed} 90 | • ${messages.hideMixes} 91 | • ${messages.hidePlaylists} 92 | • ${messages.hideMoviesAndTV} 93 | • ${messages.hideUpcoming} 94 | • ${messages.hideMembersOnly} 95 | • ${messages.hideWatched} 96 | • ${messages.hideHiddenVideos} - ${messages.hideHiddenVideosNote} 97 | • ${messages.hideChannels} - ${messages.hideChannelsNote} 98 | • ${messages.disableHomeFeed} 99 | • ${messages.searchThumbnailSize}${messages.desktopVersion} 100 | • ${messages.minimumGridItemsPerRow}${messages.desktopVersion} - ${messages.minimumGridItemsPerRowNote} 101 | 102 | ${messages.videoPages}: 103 | 104 | • ${messages.skipAds} 105 | • ${messages.disableAutoplay} 106 | • ${messages.hideRelated} 107 | • ${messages.hideNextButton} 108 | • ${messages.hideShareThanksClip} 109 | • ${messages.hideMetadata} 110 | • ${messages.hideComments} 111 | • ${messages.alwaysUseTheaterMode}${messages.desktopVersion} 112 | • ${messages.fullSizeTheaterMode}${messages.desktopVersion} - ${messages.fullSizeTheaterModeHideHeader} / ${messages.fullSizeTheaterModeHideScrollbar} 113 | • ${messages.alwaysUseOriginalAudio}${messages.desktopVersion} 114 | • ${messages.hideMiniplayerButton}${messages.desktopVersion} 115 | • ${messages.hideEndCards}${messages.desktopVersion} 116 | • ${messages.hideEndVideos}${messages.desktopVersion} 117 | • ${messages.hideMerchEtc}${messages.desktopVersion} 118 | • ${messages.hideChat}${messages.desktopVersion} 119 | • ${messages.addTakeSnapshot} (JPEG / PNG) ${messages.desktopVersion} 120 | • ${messages.downloadTranscript}${messages.desktopVersion} 121 | 122 | ${messages.shorts}: 123 | 124 | • ${messages.hideShorts} 125 | • ${messages.redirectShorts} 126 | • ${messages.hideShortsSuggestedActions} 127 | • ${messages.alwaysShowShortsProgressBar} 128 | • ${messages.stopShortsLooping} 129 | • ${messages.hideShortsMetadataUntilHover}${messages.desktopVersion} 130 | • ${messages.minimumShortsPerRow}${messages.desktopVersion} 131 | 132 | ${messages.uiTweaks}: 133 | 134 | • ${messages.hideInfoPanels} 135 | • ${messages.hideChannelBanner} 136 | • ${messages.hideHomeCategories} 137 | • ${messages.hideVoiceSearch} 138 | • ${messages.tidyGuideSidebar}${messages.desktopVersion} 139 | • ${messages.hideSubscriptionsLatestBar}${messages.desktopVersion} 140 | • ${messages.mobileGridView}${messages.mobileVersion} 141 | • ${messages.hideExploreButton}${messages.mobileVersion} 142 | • ${messages.hideSubscriptionsChannelList}${messages.mobileVersion} 143 | • ${messages.hideOpenApp}${messages.mobileVersion} 144 | 145 | ${messages.embeddedVideos}: 146 | 147 | • ${messages.hideEmbedShareButton} 148 | • ${messages.hideEmbedPauseOverlay} 149 | `.trim() 150 | 151 | if (process.argv[3] == 'md') { 152 | storeDescription = storeDescription 153 | // Section titles 154 | .replace(/^([^:\n]+):$/gm, '**$1:**') 155 | // List tiems 156 | // .replace(/•/g, '-') 157 | } 158 | 159 | clipboard.writeSync(storeDescription) 160 | console.log(storeDescription) -------------------------------------------------------------------------------- /scripts/lib.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Record} locale 3 | */ 4 | function sortProperties(locale) { 5 | let entries = Object.entries(locale) 6 | entries.sort(([a], [b]) => { 7 | if (a < b) return -1 8 | if (a > b) return 1 9 | return 0 10 | }) 11 | return Object.fromEntries(entries) 12 | } 13 | 14 | module.exports = { 15 | sortProperties, 16 | } -------------------------------------------------------------------------------- /scripts/locales/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": false, 3 | "semi": false, 4 | "singleQuote": true 5 | } -------------------------------------------------------------------------------- /scripts/locales/base-locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "af-ZA": { 3 | "ORIGINAL": "oorspronklike" 4 | }, 5 | "am-ET": { 6 | "ORIGINAL": "የመጀመሪያ" 7 | }, 8 | "ar": { 9 | "ORIGINAL": "أصلي" 10 | }, 11 | "as-IN": { 12 | "ORIGINAL": "মূল" 13 | }, 14 | "az-Latn-AZ": { 15 | "ORIGINAL": "orijinal" 16 | }, 17 | "be-BY": { 18 | "ORIGINAL": "арыгінальны" 19 | }, 20 | "bg-BG": { 21 | "ORIGINAL": "оригинален" 22 | }, 23 | "bn-BD": { 24 | "ORIGINAL": "মূল" 25 | }, 26 | "bs-Latn-BA": { 27 | "ORIGINAL": "original" 28 | }, 29 | "ca-ES": { 30 | "ORIGINAL": "original" 31 | }, 32 | "cs-CZ": { 33 | "ORIGINAL": "původní" 34 | }, 35 | "da-DK": { 36 | "ORIGINAL": "originalt" 37 | }, 38 | "de-DE": { 39 | "ORIGINAL": "Original" 40 | }, 41 | "el-GR": { 42 | "ORIGINAL": "πρωτότυπο" 43 | }, 44 | "en": { 45 | "CLIP": "Clip", 46 | "HIDE_CHANNEL": "Hide channel", 47 | "MIXES": "Mixes", 48 | "ORIGINAL": "original", 49 | "SHARE": "Share", 50 | "STREAMED_METADATA_INNERTEXT_RE": "\\n\\s*Streamed", 51 | "STREAMED_TITLE_ARIA_LABEL": "views Streamed", 52 | "TAKE_SNAPSHOT": "Take snapshot", 53 | "TELL_US_WHY": "Tell us why", 54 | "THANKS": "Thanks", 55 | "UNHIDE_CHANNEL": "Unhide channel" 56 | }, 57 | "es-419": { 58 | "ORIGINAL": "original" 59 | }, 60 | "es-ES": { 61 | "ORIGINAL": "original" 62 | }, 63 | "es-US": { 64 | "ORIGINAL": "original" 65 | }, 66 | "et-EE": { 67 | "ORIGINAL": "algne" 68 | }, 69 | "eu-ES": { 70 | "ORIGINAL": "jatorrizkoa" 71 | }, 72 | "fa-IR": { 73 | "ORIGINAL": "اصلی" 74 | }, 75 | "fil-PH": { 76 | "ORIGINAL": "orihinal" 77 | }, 78 | "fr-CA": { 79 | "ORIGINAL": "originale" 80 | }, 81 | "fr-FR": { 82 | "HIDE_CHANNEL": "Masquer la chaîne", 83 | "MIXES": "Mix", 84 | "ORIGINAL": "original", 85 | "SHARE": "Partager", 86 | "STREAMED_METADATA_INNERTEXT_RE": "\\n\\s*Diffusé", 87 | "STREAMED_TITLE_ARIA_LABEL": "vues Diffusé", 88 | "TAKE_SNAPSHOT": "Prendre une capture", 89 | "TELL_US_WHY": "Dites-nous pourquoi", 90 | "THANKS": "Merci", 91 | "UNHIDE_CHANNEL": "Afficher la chaîne" 92 | }, 93 | "gl-ES": { 94 | "ORIGINAL": "orixinal" 95 | }, 96 | "gu-IN": { 97 | "ORIGINAL": "ઑરિજિનલ" 98 | }, 99 | "he-IL": { 100 | "ORIGINAL": "מקור" 101 | }, 102 | "hi-IN": { 103 | "ORIGINAL": "मूल" 104 | }, 105 | "hr-HR": { 106 | "ORIGINAL": "izvorno" 107 | }, 108 | "hu-HU": { 109 | "ORIGINAL": "eredeti" 110 | }, 111 | "hy-AM": { 112 | "ORIGINAL": "բնօրինակ" 113 | }, 114 | "id-ID": { 115 | "ORIGINAL": "asli" 116 | }, 117 | "is-IS": { 118 | "ORIGINAL": "upprunalegt" 119 | }, 120 | "it-IT": { 121 | "ORIGINAL": "originale" 122 | }, 123 | "ja-JP": { 124 | "CLIP": "クリップ", 125 | "HIDE_CHANNEL": "チャンネルを隠す", 126 | "MIXES": "ミックス", 127 | "ORIGINAL": "オリジナル", 128 | "SHARE": "共有", 129 | "STREAMED_METADATA_INNERTEXT_RE": "に配信済み\\s*$", 130 | "STREAMED_TITLE_ARIA_LABEL": "前 に配信済み", 131 | "TAKE_SNAPSHOT": "スナップショットを撮る", 132 | "TELL_US_WHY": "理由を教えてください", 133 | "UNHIDE_CHANNEL": "チャンネルの再表示" 134 | }, 135 | "ka-GE": { 136 | "ORIGINAL": "ორიგინალია" 137 | }, 138 | "kk-KZ": { 139 | "ORIGINAL": "түпнұсқа" 140 | }, 141 | "km-KH": { 142 | "ORIGINAL": "ដើម" 143 | }, 144 | "kn-IN": { 145 | "ORIGINAL": "ಮೂಲ" 146 | }, 147 | "ko-KR": { 148 | "ORIGINAL": "원본" 149 | }, 150 | "ky-KG": { 151 | "ORIGINAL": "түпнуска" 152 | }, 153 | "lo-LA": { 154 | "ORIGINAL": "ຕົ້ນສະບັບ" 155 | }, 156 | "lt-LT": { 157 | "ORIGINAL": "pradinis" 158 | }, 159 | "lv-LV": { 160 | "ORIGINAL": "oriģināls" 161 | }, 162 | "mk-MK": { 163 | "ORIGINAL": "оригинален" 164 | }, 165 | "ml-IN": { 166 | "ORIGINAL": "ഒറിജിനൽ" 167 | }, 168 | "mn-MN": { 169 | "ORIGINAL": "эх хувь" 170 | }, 171 | "mr-IN": { 172 | "ORIGINAL": "मूळ" 173 | }, 174 | "ms-MY": { 175 | "ORIGINAL": "asal" 176 | }, 177 | "my-MM": { 178 | "ORIGINAL": "မူရင်း" 179 | }, 180 | "nb-NO": { 181 | "ORIGINAL": "original" 182 | }, 183 | "ne-NP": { 184 | "ORIGINAL": "मूल" 185 | }, 186 | "nl-NL": { 187 | "ORIGINAL": "Originele" 188 | }, 189 | "or-IN": { 190 | "ORIGINAL": "ମୂଳ" 191 | }, 192 | "pa-Guru-IN": { 193 | "ORIGINAL": "ਮੂਲ" 194 | }, 195 | "pl-PL": { 196 | "ORIGINAL": "oryginalny" 197 | }, 198 | "pt-BR": { 199 | "ORIGINAL": "original" 200 | }, 201 | "pt-PT": { 202 | "ORIGINAL": "original" 203 | }, 204 | "ro-RO": { 205 | "ORIGINAL": "original" 206 | }, 207 | "ru-RU": { 208 | "ORIGINAL": "оригинальная" 209 | }, 210 | "si-LK": { 211 | "ORIGINAL": "මුල්" 212 | }, 213 | "sk-SK": { 214 | "ORIGINAL": "pôvodná" 215 | }, 216 | "sl-SI": { 217 | "ORIGINAL": "Izvirnik" 218 | }, 219 | "sq-AL": { 220 | "ORIGINAL": "origjinale" 221 | }, 222 | "sr-Cyrl-RS": { 223 | "ORIGINAL": "оригинална" 224 | }, 225 | "sr-Latn-RS": { 226 | "ORIGINAL": "originalna" 227 | }, 228 | "sw-TZ": { 229 | "ORIGINAL": "halisi" 230 | }, 231 | "ta-IN": { 232 | "ORIGINAL": "அசல்" 233 | }, 234 | "te-IN": { 235 | "ORIGINAL": "అసలైనది" 236 | }, 237 | "th-TH": { 238 | "ORIGINAL": "เสียงต้นฉบับ" 239 | }, 240 | "tr-TR": { 241 | "ORIGINAL": "orijinal" 242 | }, 243 | "uk-UA": { 244 | "ORIGINAL": "оригінал" 245 | }, 246 | "ur-PK": { 247 | "ORIGINAL": "اصل" 248 | }, 249 | "uz-Latn-UZ": { 250 | "ORIGINAL": "original" 251 | }, 252 | "vi-VN": { 253 | "ORIGINAL": "gốc" 254 | }, 255 | "zh-Hans-CN": { 256 | "CLIP": "剪辑", 257 | "HIDE_CHANNEL": "隐藏频道", 258 | "MIXES": "合辑", 259 | "ORIGINAL": "原始", 260 | "SHARE": "分享", 261 | "STREAMED_METADATA_INNERTEXT_RE": "直播时间:", 262 | "STREAMED_TITLE_ARIA_LABEL": "直播时间:", 263 | "TAKE_SNAPSHOT": "截取快照", 264 | "TELL_US_WHY": "告诉我们原因", 265 | "THANKS": "感谢", 266 | "UNHIDE_CHANNEL": "取消隐藏频道" 267 | }, 268 | "zh-Hant-HK": { 269 | "ORIGINAL": "原聲" 270 | }, 271 | "zh-Hant-TW": { 272 | "ORIGINAL": "原文" 273 | }, 274 | "zu-ZA": { 275 | "ORIGINAL": "yokuqala" 276 | } 277 | } -------------------------------------------------------------------------------- /scripts/locales/create-locales.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | const {sortProperties} = require('../lib') 5 | 6 | /** @type {Record>} */ 7 | const locales = JSON.parse(fs.readFileSync('./base-locales.json', 'utf-8')) 8 | 9 | // These codes are from YouTube's ytcfg.msgs object 10 | let template = { 11 | SHORTS: 'SHORTS_TAB_LABEL', 12 | } 13 | 14 | for (let file of fs.readdirSync('./mobile')) { 15 | let localeCode = file.split('.')[0] 16 | locales[localeCode] ??= {} 17 | let locale = locales[localeCode] 18 | let src = fs.readFileSync(path.join('mobile', file), {encoding: 'utf8'}) 19 | for (let [key, code] of Object.entries(template)) { 20 | let match = src.match(new RegExp(`"${code}":("[^"]+")`)) 21 | if (match) { 22 | locale[key] = JSON.parse(match[1]) 23 | } else { 24 | console.log('no match', {file, key, code}) 25 | } 26 | } 27 | locales[localeCode] = sortProperties(locale) 28 | } 29 | 30 | fs.writeFileSync( 31 | 'locales.js', 32 | `const locales = ${JSON.stringify(locales, null, 2)}`, 33 | 'utf8' 34 | ) 35 | -------------------------------------------------------------------------------- /scripts/locales/mobile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | url="https://m.youtube.com" 4 | user_agent="Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1" 5 | 6 | languages=( 7 | af-ZA 8 | am-ET 9 | ar 10 | as-IN 11 | az-Latn-AZ 12 | be-BY 13 | bg-BG 14 | bn-BD 15 | bs-Latn-BA 16 | ca-ES 17 | cs-CZ 18 | da-DK 19 | de-DE 20 | el-GR 21 | en 22 | es-419 23 | es-ES 24 | es-US 25 | et-EE 26 | eu-ES 27 | fa-IR 28 | fil-PH 29 | fr-CA 30 | fr-FR 31 | gl-ES 32 | gu-IN 33 | he-IL 34 | hi-IN 35 | hr-HR 36 | hu-HU 37 | hy-AM 38 | id-ID 39 | is-IS 40 | it-IT 41 | ja-JP 42 | ka-GE 43 | kk-KZ 44 | km-KH 45 | kn-IN 46 | ko-KR 47 | ky-KG 48 | lo-LA 49 | lt-LT 50 | lv-LV 51 | mk-MK 52 | ml-IN 53 | mn-MN 54 | mr-IN 55 | ms-MY 56 | my-MM 57 | nb-NO 58 | ne-NP 59 | nl-NL 60 | or-IN 61 | pa-Guru-IN 62 | pl-PL 63 | pt-BR 64 | pt-PT 65 | ro-RO 66 | ru-RU 67 | si-LK 68 | sk-SK 69 | sl-SI 70 | sq-AL 71 | sr-Cyrl-RS 72 | sr-Latn-RS 73 | sw-TZ 74 | ta-IN 75 | te-IN 76 | th-TH 77 | tr-TR 78 | uk-UA 79 | ur-PK 80 | uz-Latn-UZ 81 | vi-VN 82 | zh-Hans-CN 83 | zh-Hant-HK 84 | zh-Hant-TW 85 | zu-ZA 86 | ) 87 | 88 | mkdir -p mobile 89 | for lang in "${languages[@]}"; do 90 | echo "$lang" 91 | curl -A "$user_agent" \ 92 | -H "Accept-Language: ${lang}" \ 93 | -L \ 94 | -o "mobile/${lang}.html" \ 95 | "$url" 96 | sleep 2 97 | done -------------------------------------------------------------------------------- /scripts/mac-appicon-assets.sh: -------------------------------------------------------------------------------- 1 | # Mac: run via `zsh -i ./mac-appicon-assets.sh` if using an alias 2 | 3 | # Requires inkscape to be in your PATH or aliased, e.g. on Mac: 4 | # alias inkscape="/Applications/Inkscape.app/Contents/MacOS/inkscape" 5 | inkscape -w 16 -h 16 ../icons/icon.svg -o ../safari/Shared\ \(App\)/Assets.xcassets/AppIcon.appiconset/appicon16.png 6 | inkscape -w 32 -h 32 ../icons/icon.svg -o ../safari/Shared\ \(App\)/Assets.xcassets/AppIcon.appiconset/appicon32.png 7 | inkscape -w 64 -h 64 ../icons/icon.svg -o ../safari/Shared\ \(App\)/Assets.xcassets/AppIcon.appiconset/appicon64.png 8 | inkscape -w 128 -h 128 ../icons/icon.svg -o ../safari/Shared\ \(App\)/Assets.xcassets/AppIcon.appiconset/appicon128.png 9 | inkscape -w 256 -h 256 ../icons/icon.svg -o ../safari/Shared\ \(App\)/Assets.xcassets/AppIcon.appiconset/appicon256.png 10 | inkscape -w 512 -h 512 ../icons/icon.svg -o ../safari/Shared\ \(App\)/Assets.xcassets/AppIcon.appiconset/appicon512.png 11 | inkscape -w 1024 -h 1024 ../icons/icon.svg -o ../safari/Shared\ \(App\)/Assets.xcassets/AppIcon.appiconset/appicon1024.png -------------------------------------------------------------------------------- /scripts/release.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | const semver = require('semver') 4 | 5 | const manifestPaths = ['./manifest.mv2.json', './manifest.mv3.json', './Safari/Shared (Extension)/Resources/manifest.json'] 6 | const optionsPath = './options.html' 7 | const safariProjectPath = './safari/Control Panel for YouTube.xcodeproj/project.pbxproj' 8 | 9 | let currentVersion = JSON.parse(fs.readFileSync(manifestPaths[0], {encoding: 'utf8'})).version 10 | let nextVersion = process.argv[2] 11 | 12 | if (semver.valid(nextVersion)) { 13 | if (!semver.satisfies(nextVersion, `>${currentVersion}`)) { 14 | console.log(`next version must be >${currentVersion}`) 15 | process.exit(1) 16 | } 17 | } 18 | else if (nextVersion == 'patch' || nextVersion == 'minor' || nextVersion == 'major') { 19 | nextVersion = semver.inc(currentVersion, nextVersion) 20 | } 21 | else { 22 | console.log(` 23 | Usage: 24 | npm run release (patch|minor|major) 25 | npm run release 1.2.3 26 | `.trim()) 27 | process.exit(1) 28 | } 29 | 30 | for (let manifestPath of manifestPaths) { 31 | fs.writeFileSync( 32 | manifestPath, 33 | fs.readFileSync(manifestPath, {encoding: 'utf8'}) 34 | .replace(/"version": "[^"]+"/, `"version": "${nextVersion}"`), 35 | {encoding: 'utf8'} 36 | ) 37 | } 38 | 39 | fs.writeFileSync( 40 | optionsPath, 41 | fs.readFileSync(optionsPath, {encoding: 'utf8'}) 42 | .replace(/id="version">[^<]+v${nextVersion}<`), 43 | {encoding: 'utf8'} 44 | ) 45 | 46 | fs.writeFileSync( 47 | safariProjectPath, 48 | fs.readFileSync(safariProjectPath, {encoding: 'utf8'}) 49 | .replace(/CURRENT_PROJECT_VERSION = (\d+)/g, (_, current) => `CURRENT_PROJECT_VERSION = ${Number(current) + 1}`) 50 | .replace(/MARKETING_VERSION = [^;]+/g, `MARKETING_VERSION = ${nextVersion}`), 51 | {encoding: 'utf8'} 52 | ) 53 | 54 | console.log(`Bumped to v${nextVersion}`) -------------------------------------------------------------------------------- /scripts/update-locale-messages.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | if (process.argv.some(arg => /^-h|--help$/.test(arg))) { 4 | console.log(` 5 | Updates ../_locales/**/messages.json with translations from ./scripts/translations.json 6 | 7 | Where translations.json is in the format: 8 | 9 | { 10 | "exampleMessageLabel": { 11 | "en": "English version", 12 | "es": "Versión en español" 13 | } 14 | } 15 | `.trim() 16 | ) 17 | process.exit() 18 | } 19 | 20 | function sortProperties(locale) { 21 | let entries = Object.entries(locale) 22 | entries.sort(([a], [b]) => { 23 | if (a < b) return -1 24 | if (a > b) return 1 25 | return 0 26 | }) 27 | return Object.fromEntries(entries) 28 | } 29 | 30 | let translationsJson = JSON.parse(fs.readFileSync('./translations.json', 'utf-8')) 31 | let localeMessagesJson = new Map() 32 | 33 | for (let [messageProp, translations] of Object.entries(translationsJson)) { 34 | for (let [localeCode, message] of Object.entries(translations)) { 35 | if (!localeMessagesJson.has(localeCode)) { 36 | localeMessagesJson.set( 37 | localeCode, 38 | JSON.parse( 39 | fs.readFileSync(`../_locales/${localeCode}/messages.json`, 'utf-8') 40 | ) 41 | ) 42 | } 43 | let messagesJson = localeMessagesJson.get(localeCode) 44 | messagesJson[messageProp] = {...messagesJson[messageProp], message} 45 | } 46 | } 47 | 48 | for (let [localeCode, messagesJson] of localeMessagesJson.entries()) { 49 | fs.writeFileSync( 50 | `../_locales/${localeCode}/messages.json`, 51 | JSON.stringify(sortProperties(messagesJson), null, 2), 52 | 'utf8' 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | export type Channel = { 2 | name: string 3 | url?: string 4 | } 5 | 6 | export type CustomMutationObserver = MutationObserver & {name: string, onDisconnect?: () => void} 7 | 8 | export type Disconnectable = {disconnect: () => void} 9 | 10 | export type EmbedConfig = { 11 | enabled: boolean 12 | debug?: boolean; 13 | hideEmbedPauseOverlay: boolean 14 | hideEmbedShareButton: boolean 15 | hideEndCards: boolean 16 | hideEndVideos: boolean 17 | hideInfoPanels: boolean 18 | removePink: boolean 19 | } 20 | 21 | export type Locale = { 22 | [key in LocaleKey]?: string 23 | } 24 | 25 | export type LocaleKey = 26 | | 'CLIP' 27 | | 'HIDE_CHANNEL' 28 | | 'MIXES' 29 | | 'ORIGINAL' 30 | | 'SHARE' 31 | | 'SHORTS' 32 | // This needs to match both innerText and textContent 33 | | 'STREAMED_METADATA_INNERTEXT_RE' 34 | | 'STREAMED_TITLE_ARIA_LABEL' 35 | | 'TAKE_SNAPSHOT' 36 | | 'TELL_US_WHY' 37 | | 'THANKS' 38 | | 'UNHIDE_CHANNEL' 39 | 40 | export type OptionsConfig = EmbedConfig & SiteConfig & { 41 | version?: Version 42 | } 43 | 44 | export type SiteConfig = { 45 | enabled: boolean 46 | debug?: boolean, 47 | debugManualHiding?: boolean, 48 | alwaysShowShortsProgressBar: boolean, 49 | disableAutoplay: boolean 50 | disableHomeFeed: boolean 51 | hiddenChannels: Channel[] 52 | hideAI: boolean 53 | hideChannelBanner: boolean 54 | hideChannels: boolean 55 | hideComments: boolean 56 | hideHiddenVideos: boolean 57 | hideHomeCategories: boolean 58 | hideInfoPanels: boolean 59 | hideLive: boolean 60 | hideMembersOnly: boolean 61 | hideMetadata: boolean 62 | hideMixes: boolean 63 | hideMoviesAndTV: boolean 64 | hideNextButton: boolean 65 | hidePlaylists: boolean 66 | hidePremiumUpsells: boolean 67 | hideRelated: boolean 68 | hideShareThanksClip: boolean 69 | hideShorts: boolean 70 | hideShortsSuggestedActions: boolean 71 | hideSponsored: boolean 72 | hideStreamed: boolean 73 | hideSuggestedSections: boolean 74 | hideUpcoming: boolean 75 | hideVoiceSearch: boolean 76 | hideWatched: boolean 77 | hideWatchedThreshold: string 78 | redirectShorts: boolean 79 | removePink: boolean 80 | skipAds: boolean 81 | stopShortsLooping: boolean 82 | // Desktop only 83 | addTakeSnapshot: boolean 84 | alwaysUseOriginalAudio: boolean 85 | alwaysUseTheaterMode: boolean 86 | downloadTranscript: boolean 87 | fullSizeTheaterMode: boolean 88 | fullSizeTheaterModeHideHeader: boolean 89 | fullSizeTheaterModeHideScrollbar: boolean 90 | hideChat: boolean 91 | hideEndCards: boolean 92 | hideEndVideos: boolean 93 | hideMerchEtc: boolean 94 | hideMiniplayerButton: boolean 95 | hideSubscriptionsLatestBar: boolean 96 | minimumGridItemsPerRow: 'auto' | '3' | '4' | '5' | '6' 97 | minimumShortsPerRow: 'auto' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12' 98 | pauseChannelTrailers: boolean 99 | searchThumbnailSize: 'large' | 'medium' | 'small' 100 | hideShortsMetadataUntilHover: boolean 101 | snapshotFormat: 'jpeg' | 'png' 102 | snapshotQuality: string 103 | tidyGuideSidebar: boolean 104 | // Mobile only 105 | hideExploreButton: boolean 106 | hideOpenApp: boolean 107 | hideSubscriptionsChannelList: boolean 108 | mobileGridView: boolean 109 | } 110 | 111 | export type SiteConfigMessage = { 112 | type: 'initial' | 'change' 113 | siteConfig: Partial 114 | } 115 | 116 | export type Version = 'mobile' | 'desktop' --------------------------------------------------------------------------------