Browse Source

feat: almost finished auto-like

Sv443 11 months ago
parent
commit
5357aea38f

+ 51 - 44
assets/translations/README.md

@@ -16,15 +16,15 @@ To submit or edit a translation, please follow [this guide](../../contributing.m
 ### Translation progress:
 |   | Locale | Translated keys | Based on |
 | :----: | ------ | --------------- | :------: |
-| ─ | [`en_US`](./en_US.json) | 236 (default locale) |  |
-| ‼️ | [`de_DE`](./de_DE.json) | `214/236` (90.7%) | ─ |
-| ─ | [`en_UK`](./en_UK.json) | `236/236` (100%) | `en_US` |
-| ‼️ | [`es_ES`](./es_ES.json) | `214/236` (90.7%) | ─ |
-| ‼️ | [`fr_FR`](./fr_FR.json) | `214/236` (90.7%) | ─ |
-| ‼️ | [`hi_IN`](./hi_IN.json) | `214/236` (90.7%) | ─ |
-| ‼️ | [`ja_JA`](./ja_JA.json) | `214/236` (90.7%) | ─ |
-| ‼️ | [`pt_BR`](./pt_BR.json) | `214/236` (90.7%) | ─ |
-| ‼️ | [`zh_CN`](./zh_CN.json) | `214/236` (90.7%) | ─ |
+| ─ | [`en_US`](./en_US.json) | 237 (default locale) |  |
+| ‼️ | [`de_DE`](./de_DE.json) | `214/237` (90.3%) | ─ |
+| ─ | [`en_UK`](./en_UK.json) | `237/237` (100%) | `en_US` |
+| ‼️ | [`es_ES`](./es_ES.json) | `214/237` (90.3%) | ─ |
+| ‼️ | [`fr_FR`](./fr_FR.json) | `214/237` (90.3%) | ─ |
+| ‼️ | [`hi_IN`](./hi_IN.json) | `214/237` (90.3%) | ─ |
+| ‼️ | [`ja_JA`](./ja_JA.json) | `214/237` (90.3%) | ─ |
+| ‼️ | [`pt_BR`](./pt_BR.json) | `214/237` (90.3%) | ─ |
+| ‼️ | [`zh_CN`](./zh_CN.json) | `214/237` (90.3%) | ─ |
 
 <sub>
 ✅ - Fully translated
@@ -45,7 +45,7 @@ This means to figure out which keys are untranslated, you will need to manually
 
 ### Missing keys:
 
-<details><summary><code>de_DE</code> - 22 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>de_DE</code> - 23 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
@@ -56,17 +56,18 @@ This means to figure out which keys are untranslated, you will need to manually
 | `auto_like` | `Auto-like` |
 | `auto_like_button_tooltip_enabled` | `Click to disable auto-liking. Shift-click to open the management dialog.` |
 | `auto_like_button_tooltip_disabled` | `Click to enable auto-liking. Shift-click to open the management dialog.` |
-| `add_auto_like_channel_id_prompt` | `Enter the channel ID (the part after "/channel/" in the URL) of the channel you want to auto-like.\nPress "cancel" to exit.` |
+| `add_auto_like_channel_id_prompt` | `Enter the channel ID or full URL of the channel you want to auto-like.\nPress "cancel" to exit.` |
 | `add_auto_like_channel_invalid_id` | `The entered channel ID is invalid.\nPlease make sure you copy only the part *after* "/channel/" in the URL, excluding the slash.` |
 | `add_auto_like_channel_already_exists_prompt_new_name` | `A channel with that ID is already in the list.\nDo you instead want to change its name?` |
 | `add_auto_like_channel_name_prompt` | `Enter the name of the channel.\nPress "cancel" to exit.` |
 | `auto_like_enabled_toast` | `Auto-liking enabled` |
 | `auto_like_disabled_toast` | `Auto-liking disabled` |
+| `auto_liked_video` | `Auto-liked the video` |
 | `feature_desc_autoLikeChannels` | `Automatically like all songs and videos of certain channels` |
 | `feature_helpText_autoLikeChannels` | `Once enabled, you can enable this feature for certain channels by opening their page and clicking the toggle button. Afterwards, any song you play of that channel will be liked automatically.\nUse the option below to open a dialog to manage the channels.` |
-| `feature_desc_openAutoLikeChannelsDialog` | `Open the dialog to manage auto-liked channels` |
-| `feature_btn_openAutoLikeChannelsDialog` | `Open dialog` |
-| `feature_btn_openAutoLikeChannelsDialog_running` | `Opening...` |
+| `feature_desc_autoLikeOpenMgmtDialog` | `Open the dialog to manage auto-liked channels` |
+| `feature_btn_autoLikeOpenMgmtDialog` | `Open dialog` |
+| `feature_btn_autoLikeOpenMgmtDialog_running` | `Opening...` |
 | `feature_desc_initTimeout` | `How long to wait for features to initialize before considering them to likely be in an errored state` |
 | `feature_helptext_initTimeout` | `This is the amount of time in milliseconds that the script will wait for features to initialize before considering them to likely be in an errored state.\nThis will not affect the script's behavior in a significant way, but if one of your plugins can't initialize in time, you should try increasing this value.` |
 | `plugin_validation_error_invalid_property-1` | `Property '%1' with value '%2' is invalid. Example value: %3` |
@@ -74,7 +75,7 @@ This means to figure out which keys are untranslated, you will need to manually
 
 <br></details>
 
-<details><summary><code>es_ES</code> - 22 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>es_ES</code> - 23 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
@@ -85,17 +86,18 @@ This means to figure out which keys are untranslated, you will need to manually
 | `auto_like` | `Auto-like` |
 | `auto_like_button_tooltip_enabled` | `Click to disable auto-liking. Shift-click to open the management dialog.` |
 | `auto_like_button_tooltip_disabled` | `Click to enable auto-liking. Shift-click to open the management dialog.` |
-| `add_auto_like_channel_id_prompt` | `Enter the channel ID (the part after "/channel/" in the URL) of the channel you want to auto-like.\nPress "cancel" to exit.` |
+| `add_auto_like_channel_id_prompt` | `Enter the channel ID or full URL of the channel you want to auto-like.\nPress "cancel" to exit.` |
 | `add_auto_like_channel_invalid_id` | `The entered channel ID is invalid.\nPlease make sure you copy only the part *after* "/channel/" in the URL, excluding the slash.` |
 | `add_auto_like_channel_already_exists_prompt_new_name` | `A channel with that ID is already in the list.\nDo you instead want to change its name?` |
 | `add_auto_like_channel_name_prompt` | `Enter the name of the channel.\nPress "cancel" to exit.` |
 | `auto_like_enabled_toast` | `Auto-liking enabled` |
 | `auto_like_disabled_toast` | `Auto-liking disabled` |
+| `auto_liked_video` | `Auto-liked the video` |
 | `feature_desc_autoLikeChannels` | `Automatically like all songs and videos of certain channels` |
 | `feature_helpText_autoLikeChannels` | `Once enabled, you can enable this feature for certain channels by opening their page and clicking the toggle button. Afterwards, any song you play of that channel will be liked automatically.\nUse the option below to open a dialog to manage the channels.` |
-| `feature_desc_openAutoLikeChannelsDialog` | `Open the dialog to manage auto-liked channels` |
-| `feature_btn_openAutoLikeChannelsDialog` | `Open dialog` |
-| `feature_btn_openAutoLikeChannelsDialog_running` | `Opening...` |
+| `feature_desc_autoLikeOpenMgmtDialog` | `Open the dialog to manage auto-liked channels` |
+| `feature_btn_autoLikeOpenMgmtDialog` | `Open dialog` |
+| `feature_btn_autoLikeOpenMgmtDialog_running` | `Opening...` |
 | `feature_desc_initTimeout` | `How long to wait for features to initialize before considering them to likely be in an errored state` |
 | `feature_helptext_initTimeout` | `This is the amount of time in milliseconds that the script will wait for features to initialize before considering them to likely be in an errored state.\nThis will not affect the script's behavior in a significant way, but if one of your plugins can't initialize in time, you should try increasing this value.` |
 | `plugin_validation_error_invalid_property-1` | `Property '%1' with value '%2' is invalid. Example value: %3` |
@@ -103,7 +105,7 @@ This means to figure out which keys are untranslated, you will need to manually
 
 <br></details>
 
-<details><summary><code>fr_FR</code> - 22 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>fr_FR</code> - 23 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
@@ -114,17 +116,18 @@ This means to figure out which keys are untranslated, you will need to manually
 | `auto_like` | `Auto-like` |
 | `auto_like_button_tooltip_enabled` | `Click to disable auto-liking. Shift-click to open the management dialog.` |
 | `auto_like_button_tooltip_disabled` | `Click to enable auto-liking. Shift-click to open the management dialog.` |
-| `add_auto_like_channel_id_prompt` | `Enter the channel ID (the part after "/channel/" in the URL) of the channel you want to auto-like.\nPress "cancel" to exit.` |
+| `add_auto_like_channel_id_prompt` | `Enter the channel ID or full URL of the channel you want to auto-like.\nPress "cancel" to exit.` |
 | `add_auto_like_channel_invalid_id` | `The entered channel ID is invalid.\nPlease make sure you copy only the part *after* "/channel/" in the URL, excluding the slash.` |
 | `add_auto_like_channel_already_exists_prompt_new_name` | `A channel with that ID is already in the list.\nDo you instead want to change its name?` |
 | `add_auto_like_channel_name_prompt` | `Enter the name of the channel.\nPress "cancel" to exit.` |
 | `auto_like_enabled_toast` | `Auto-liking enabled` |
 | `auto_like_disabled_toast` | `Auto-liking disabled` |
+| `auto_liked_video` | `Auto-liked the video` |
 | `feature_desc_autoLikeChannels` | `Automatically like all songs and videos of certain channels` |
 | `feature_helpText_autoLikeChannels` | `Once enabled, you can enable this feature for certain channels by opening their page and clicking the toggle button. Afterwards, any song you play of that channel will be liked automatically.\nUse the option below to open a dialog to manage the channels.` |
-| `feature_desc_openAutoLikeChannelsDialog` | `Open the dialog to manage auto-liked channels` |
-| `feature_btn_openAutoLikeChannelsDialog` | `Open dialog` |
-| `feature_btn_openAutoLikeChannelsDialog_running` | `Opening...` |
+| `feature_desc_autoLikeOpenMgmtDialog` | `Open the dialog to manage auto-liked channels` |
+| `feature_btn_autoLikeOpenMgmtDialog` | `Open dialog` |
+| `feature_btn_autoLikeOpenMgmtDialog_running` | `Opening...` |
 | `feature_desc_initTimeout` | `How long to wait for features to initialize before considering them to likely be in an errored state` |
 | `feature_helptext_initTimeout` | `This is the amount of time in milliseconds that the script will wait for features to initialize before considering them to likely be in an errored state.\nThis will not affect the script's behavior in a significant way, but if one of your plugins can't initialize in time, you should try increasing this value.` |
 | `plugin_validation_error_invalid_property-1` | `Property '%1' with value '%2' is invalid. Example value: %3` |
@@ -132,7 +135,7 @@ This means to figure out which keys are untranslated, you will need to manually
 
 <br></details>
 
-<details><summary><code>hi_IN</code> - 22 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>hi_IN</code> - 23 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
@@ -143,17 +146,18 @@ This means to figure out which keys are untranslated, you will need to manually
 | `auto_like` | `Auto-like` |
 | `auto_like_button_tooltip_enabled` | `Click to disable auto-liking. Shift-click to open the management dialog.` |
 | `auto_like_button_tooltip_disabled` | `Click to enable auto-liking. Shift-click to open the management dialog.` |
-| `add_auto_like_channel_id_prompt` | `Enter the channel ID (the part after "/channel/" in the URL) of the channel you want to auto-like.\nPress "cancel" to exit.` |
+| `add_auto_like_channel_id_prompt` | `Enter the channel ID or full URL of the channel you want to auto-like.\nPress "cancel" to exit.` |
 | `add_auto_like_channel_invalid_id` | `The entered channel ID is invalid.\nPlease make sure you copy only the part *after* "/channel/" in the URL, excluding the slash.` |
 | `add_auto_like_channel_already_exists_prompt_new_name` | `A channel with that ID is already in the list.\nDo you instead want to change its name?` |
 | `add_auto_like_channel_name_prompt` | `Enter the name of the channel.\nPress "cancel" to exit.` |
 | `auto_like_enabled_toast` | `Auto-liking enabled` |
 | `auto_like_disabled_toast` | `Auto-liking disabled` |
+| `auto_liked_video` | `Auto-liked the video` |
 | `feature_desc_autoLikeChannels` | `Automatically like all songs and videos of certain channels` |
 | `feature_helpText_autoLikeChannels` | `Once enabled, you can enable this feature for certain channels by opening their page and clicking the toggle button. Afterwards, any song you play of that channel will be liked automatically.\nUse the option below to open a dialog to manage the channels.` |
-| `feature_desc_openAutoLikeChannelsDialog` | `Open the dialog to manage auto-liked channels` |
-| `feature_btn_openAutoLikeChannelsDialog` | `Open dialog` |
-| `feature_btn_openAutoLikeChannelsDialog_running` | `Opening...` |
+| `feature_desc_autoLikeOpenMgmtDialog` | `Open the dialog to manage auto-liked channels` |
+| `feature_btn_autoLikeOpenMgmtDialog` | `Open dialog` |
+| `feature_btn_autoLikeOpenMgmtDialog_running` | `Opening...` |
 | `feature_desc_initTimeout` | `How long to wait for features to initialize before considering them to likely be in an errored state` |
 | `feature_helptext_initTimeout` | `This is the amount of time in milliseconds that the script will wait for features to initialize before considering them to likely be in an errored state.\nThis will not affect the script's behavior in a significant way, but if one of your plugins can't initialize in time, you should try increasing this value.` |
 | `plugin_validation_error_invalid_property-1` | `Property '%1' with value '%2' is invalid. Example value: %3` |
@@ -161,7 +165,7 @@ This means to figure out which keys are untranslated, you will need to manually
 
 <br></details>
 
-<details><summary><code>ja_JA</code> - 22 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>ja_JA</code> - 23 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
@@ -172,17 +176,18 @@ This means to figure out which keys are untranslated, you will need to manually
 | `auto_like` | `Auto-like` |
 | `auto_like_button_tooltip_enabled` | `Click to disable auto-liking. Shift-click to open the management dialog.` |
 | `auto_like_button_tooltip_disabled` | `Click to enable auto-liking. Shift-click to open the management dialog.` |
-| `add_auto_like_channel_id_prompt` | `Enter the channel ID (the part after "/channel/" in the URL) of the channel you want to auto-like.\nPress "cancel" to exit.` |
+| `add_auto_like_channel_id_prompt` | `Enter the channel ID or full URL of the channel you want to auto-like.\nPress "cancel" to exit.` |
 | `add_auto_like_channel_invalid_id` | `The entered channel ID is invalid.\nPlease make sure you copy only the part *after* "/channel/" in the URL, excluding the slash.` |
 | `add_auto_like_channel_already_exists_prompt_new_name` | `A channel with that ID is already in the list.\nDo you instead want to change its name?` |
 | `add_auto_like_channel_name_prompt` | `Enter the name of the channel.\nPress "cancel" to exit.` |
 | `auto_like_enabled_toast` | `Auto-liking enabled` |
 | `auto_like_disabled_toast` | `Auto-liking disabled` |
+| `auto_liked_video` | `Auto-liked the video` |
 | `feature_desc_autoLikeChannels` | `Automatically like all songs and videos of certain channels` |
 | `feature_helpText_autoLikeChannels` | `Once enabled, you can enable this feature for certain channels by opening their page and clicking the toggle button. Afterwards, any song you play of that channel will be liked automatically.\nUse the option below to open a dialog to manage the channels.` |
-| `feature_desc_openAutoLikeChannelsDialog` | `Open the dialog to manage auto-liked channels` |
-| `feature_btn_openAutoLikeChannelsDialog` | `Open dialog` |
-| `feature_btn_openAutoLikeChannelsDialog_running` | `Opening...` |
+| `feature_desc_autoLikeOpenMgmtDialog` | `Open the dialog to manage auto-liked channels` |
+| `feature_btn_autoLikeOpenMgmtDialog` | `Open dialog` |
+| `feature_btn_autoLikeOpenMgmtDialog_running` | `Opening...` |
 | `feature_desc_initTimeout` | `How long to wait for features to initialize before considering them to likely be in an errored state` |
 | `feature_helptext_initTimeout` | `This is the amount of time in milliseconds that the script will wait for features to initialize before considering them to likely be in an errored state.\nThis will not affect the script's behavior in a significant way, but if one of your plugins can't initialize in time, you should try increasing this value.` |
 | `plugin_validation_error_invalid_property-1` | `Property '%1' with value '%2' is invalid. Example value: %3` |
@@ -190,7 +195,7 @@ This means to figure out which keys are untranslated, you will need to manually
 
 <br></details>
 
-<details><summary><code>pt_BR</code> - 22 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>pt_BR</code> - 23 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
@@ -201,17 +206,18 @@ This means to figure out which keys are untranslated, you will need to manually
 | `auto_like` | `Auto-like` |
 | `auto_like_button_tooltip_enabled` | `Click to disable auto-liking. Shift-click to open the management dialog.` |
 | `auto_like_button_tooltip_disabled` | `Click to enable auto-liking. Shift-click to open the management dialog.` |
-| `add_auto_like_channel_id_prompt` | `Enter the channel ID (the part after "/channel/" in the URL) of the channel you want to auto-like.\nPress "cancel" to exit.` |
+| `add_auto_like_channel_id_prompt` | `Enter the channel ID or full URL of the channel you want to auto-like.\nPress "cancel" to exit.` |
 | `add_auto_like_channel_invalid_id` | `The entered channel ID is invalid.\nPlease make sure you copy only the part *after* "/channel/" in the URL, excluding the slash.` |
 | `add_auto_like_channel_already_exists_prompt_new_name` | `A channel with that ID is already in the list.\nDo you instead want to change its name?` |
 | `add_auto_like_channel_name_prompt` | `Enter the name of the channel.\nPress "cancel" to exit.` |
 | `auto_like_enabled_toast` | `Auto-liking enabled` |
 | `auto_like_disabled_toast` | `Auto-liking disabled` |
+| `auto_liked_video` | `Auto-liked the video` |
 | `feature_desc_autoLikeChannels` | `Automatically like all songs and videos of certain channels` |
 | `feature_helpText_autoLikeChannels` | `Once enabled, you can enable this feature for certain channels by opening their page and clicking the toggle button. Afterwards, any song you play of that channel will be liked automatically.\nUse the option below to open a dialog to manage the channels.` |
-| `feature_desc_openAutoLikeChannelsDialog` | `Open the dialog to manage auto-liked channels` |
-| `feature_btn_openAutoLikeChannelsDialog` | `Open dialog` |
-| `feature_btn_openAutoLikeChannelsDialog_running` | `Opening...` |
+| `feature_desc_autoLikeOpenMgmtDialog` | `Open the dialog to manage auto-liked channels` |
+| `feature_btn_autoLikeOpenMgmtDialog` | `Open dialog` |
+| `feature_btn_autoLikeOpenMgmtDialog_running` | `Opening...` |
 | `feature_desc_initTimeout` | `How long to wait for features to initialize before considering them to likely be in an errored state` |
 | `feature_helptext_initTimeout` | `This is the amount of time in milliseconds that the script will wait for features to initialize before considering them to likely be in an errored state.\nThis will not affect the script's behavior in a significant way, but if one of your plugins can't initialize in time, you should try increasing this value.` |
 | `plugin_validation_error_invalid_property-1` | `Property '%1' with value '%2' is invalid. Example value: %3` |
@@ -219,7 +225,7 @@ This means to figure out which keys are untranslated, you will need to manually
 
 <br></details>
 
-<details><summary><code>zh_CN</code> - 22 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>zh_CN</code> - 23 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
@@ -230,17 +236,18 @@ This means to figure out which keys are untranslated, you will need to manually
 | `auto_like` | `Auto-like` |
 | `auto_like_button_tooltip_enabled` | `Click to disable auto-liking. Shift-click to open the management dialog.` |
 | `auto_like_button_tooltip_disabled` | `Click to enable auto-liking. Shift-click to open the management dialog.` |
-| `add_auto_like_channel_id_prompt` | `Enter the channel ID (the part after "/channel/" in the URL) of the channel you want to auto-like.\nPress "cancel" to exit.` |
+| `add_auto_like_channel_id_prompt` | `Enter the channel ID or full URL of the channel you want to auto-like.\nPress "cancel" to exit.` |
 | `add_auto_like_channel_invalid_id` | `The entered channel ID is invalid.\nPlease make sure you copy only the part *after* "/channel/" in the URL, excluding the slash.` |
 | `add_auto_like_channel_already_exists_prompt_new_name` | `A channel with that ID is already in the list.\nDo you instead want to change its name?` |
 | `add_auto_like_channel_name_prompt` | `Enter the name of the channel.\nPress "cancel" to exit.` |
 | `auto_like_enabled_toast` | `Auto-liking enabled` |
 | `auto_like_disabled_toast` | `Auto-liking disabled` |
+| `auto_liked_video` | `Auto-liked the video` |
 | `feature_desc_autoLikeChannels` | `Automatically like all songs and videos of certain channels` |
 | `feature_helpText_autoLikeChannels` | `Once enabled, you can enable this feature for certain channels by opening their page and clicking the toggle button. Afterwards, any song you play of that channel will be liked automatically.\nUse the option below to open a dialog to manage the channels.` |
-| `feature_desc_openAutoLikeChannelsDialog` | `Open the dialog to manage auto-liked channels` |
-| `feature_btn_openAutoLikeChannelsDialog` | `Open dialog` |
-| `feature_btn_openAutoLikeChannelsDialog_running` | `Opening...` |
+| `feature_desc_autoLikeOpenMgmtDialog` | `Open the dialog to manage auto-liked channels` |
+| `feature_btn_autoLikeOpenMgmtDialog` | `Open dialog` |
+| `feature_btn_autoLikeOpenMgmtDialog_running` | `Opening...` |
 | `feature_desc_initTimeout` | `How long to wait for features to initialize before considering them to likely be in an errored state` |
 | `feature_helptext_initTimeout` | `This is the amount of time in milliseconds that the script will wait for features to initialize before considering them to likely be in an errored state.\nThis will not affect the script's behavior in a significant way, but if one of your plugins can't initialize in time, you should try increasing this value.` |
 | `plugin_validation_error_invalid_property-1` | `Property '%1' with value '%2' is invalid. Example value: %3` |

+ 5 - 4
assets/translations/en_US.json

@@ -135,12 +135,13 @@
     "auto_like": "Auto-like",
     "auto_like_button_tooltip_enabled": "Click to disable auto-liking. Shift-click to open the management dialog.",
     "auto_like_button_tooltip_disabled": "Click to enable auto-liking. Shift-click to open the management dialog.",
-    "add_auto_like_channel_id_prompt": "Enter the channel ID (the part after \"/channel/\" in the URL) of the channel you want to auto-like.\nPress \"cancel\" to exit.",
+    "add_auto_like_channel_id_prompt": "Enter the channel ID or full URL of the channel you want to auto-like.\nPress \"cancel\" to exit.",
     "add_auto_like_channel_invalid_id": "The entered channel ID is invalid.\nPlease make sure you copy only the part *after* \"/channel/\" in the URL, excluding the slash.",
     "add_auto_like_channel_already_exists_prompt_new_name": "A channel with that ID is already in the list.\nDo you instead want to change its name?",
     "add_auto_like_channel_name_prompt": "Enter the name of the channel.\nPress \"cancel\" to exit.",
     "auto_like_enabled_toast": "Auto-liking enabled",
     "auto_like_disabled_toast": "Auto-liking disabled",
+    "auto_liked_video": "Auto-liked the video",
 
     "unit_entries-1": "entry",
     "unit_entries-n": "entries",
@@ -218,9 +219,9 @@
     "feature_helptext_anchorImprovements": "Some elements on the page are only clickable with the left mouse button, which means you can't open them in a new tab by middle-clicking or through the context menu using shift + right-click. This feature adds links to a lot of them or enlarges existing ones to make clicking easier.",
     "feature_desc_autoLikeChannels": "Automatically like all songs and videos of certain channels",
     "feature_helpText_autoLikeChannels": "Once enabled, you can enable this feature for certain channels by opening their page and clicking the toggle button. Afterwards, any song you play of that channel will be liked automatically.\nUse the option below to open a dialog to manage the channels.",
-    "feature_desc_openAutoLikeChannelsDialog": "Open the dialog to manage auto-liked channels",
-    "feature_btn_openAutoLikeChannelsDialog": "Open dialog",
-    "feature_btn_openAutoLikeChannelsDialog_running": "Opening...",
+    "feature_desc_autoLikeOpenMgmtDialog": "Open the dialog to manage auto-liked channels",
+    "feature_btn_autoLikeOpenMgmtDialog": "Open dialog",
+    "feature_btn_autoLikeOpenMgmtDialog_running": "Opening...",
 
     "feature_desc_geniusLyrics": "Add a button to the media controls of the currently playing song to open its lyrics on genius.com",
     "feature_desc_geniUrlBase": "Base URL of your geniURL instance, see https://github.com/Sv443/geniURL",

+ 1 - 0
src/components/index.ts

@@ -2,5 +2,6 @@ export * from "./BytmDialog";
 export * from "./circularButton";
 export * from "./hotkeyInput";
 export * from "./longButton";
+export * from "./ripple";
 export * from "./toast";
 export * from "./toggleInput";

+ 15 - 12
src/components/toast.css

@@ -1,12 +1,15 @@
-#bytm-toast {
+:root {
   --bytm-toast-bg-color: #323232;
   --bytm-toast-text-color: #fff;
-  --bytm-toast-offset: 25px;
-  --bytm-toast-transform-distance: 100px;
+  --bytm-toast-offset-ver: 80px;
+  --bytm-toast-offset-hor: 25px;
   --bytm-toast-transition-time: 0.5s;
+  --bytm-toast-transform-distance: 175px;
+}
 
+#bytm-toast {
   position: fixed;
-  z-index: 100;
+  z-index: 1042069;
   opacity: 0.000001;
   pointer-events: none;
   padding: 10px 20px;
@@ -25,26 +28,26 @@
 }
 
 #bytm-toast.pos-tl {
-  top: var(--bytm-toast-offset);
-  left: var(--bytm-toast-offset);
+  top: var(--bytm-toast-offset-ver);
+  left: var(--bytm-toast-offset-hor);
   transform: translate(0, calc(var(--bytm-toast-transform-distance) * -1));
 }
 
 #bytm-toast.pos-tr {
-  top: var(--bytm-toast-offset);
-  right: var(--bytm-toast-offset);
+  top: var(--bytm-toast-offset-ver);
+  right: var(--bytm-toast-offset-hor);
   transform: translate(0, calc(var(--bytm-toast-transform-distance) * -1));
 }
 
 #bytm-toast.pos-br {
-  bottom: var(--bytm-toast-offset);
-  right: var(--bytm-toast-offset);
+  bottom: var(--bytm-toast-offset-ver);
+  right: var(--bytm-toast-offset-hor);
   transform: translate(0, var(--bytm-toast-transform-distance));
 }
 
 #bytm-toast.pos-bl {
-  bottom: var(--bytm-toast-offset);
-  left: var(--bytm-toast-offset);
+  bottom: var(--bytm-toast-offset-ver);
+  left: var(--bytm-toast-offset-hor);
   transform: translate(0, var(--bytm-toast-transform-distance));
 }
 

+ 20 - 6
src/components/toast.ts

@@ -1,13 +1,13 @@
 import { pauseFor } from "@sv443-network/userutils";
+import { resourceToHTMLString } from "../utils";
 import type { ResourceKey } from "../types";
 import "./toast.css";
-import { resourceToHTMLString } from "src/utils";
 
 type ToastPos = "tl" | "tr" | "bl" | "br";
 
 type ToastProps = {
-  duration: number;
-  position: ToastPos;
+  duration?: number;
+  position?: ToastPos;
 } & (
   | {
     message: string;
@@ -18,10 +18,19 @@ type ToastProps = {
   }
 );
 
+type IconToastProps = ToastProps & {
+  icon: ResourceKey;
+};
+
 let timeout: NodeJS.Timeout | undefined;
 
 /** Shows a toast message with an icon */
-export async function showIconToast(message: string, icon: ResourceKey, duration = 3000, position: ToastPos = "tr") {
+export async function showIconToast({
+  icon,
+  duration = 3000,
+  position = "tr",
+  ...rest
+}: IconToastProps) {
   const toastWrapper = document.createElement("div");
   toastWrapper.classList.add("bytm-toast-flex-wrapper");
 
@@ -33,7 +42,10 @@ export async function showIconToast(message: string, icon: ResourceKey, duration
 
   const toastMessage = document.createElement("div");
   toastMessage.classList.add("bytm-toast-message");
-  toastMessage.textContent = message;
+  if("message" in rest)
+    toastMessage.textContent = rest.message;
+  else
+    toastMessage.appendChild(rest.element);
 
   toastWrapper.appendChild(toastIcon);
   toastWrapper.appendChild(toastMessage);
@@ -42,7 +54,7 @@ export async function showIconToast(message: string, icon: ResourceKey, duration
     duration,
     position,
     element: toastWrapper,
-    title: message,
+    title: "message" in rest ? rest.message : rest.title,
   });
 }
 
@@ -62,6 +74,8 @@ export async function showToast({
   toastElem.ariaLive = "assertive";
   toastElem.ariaAtomic = "true";
 
+  toastElem.addEventListener("click", async () => await closeToast(), { once: true });
+
   if("message" in rest)
     toastElem.title = toastElem.ariaLabel = toastElem.textContent = rest.message;
   else {

+ 3 - 3
src/config.ts

@@ -10,7 +10,7 @@ import type { FeatureConfig, FeatureKey } from "./types";
 export const formatVersion = 5;
 /** Config data format migration dictionary */
 export const migrations: DataMigrationsDict = {
-  // 1 -> 2 (v1.0)
+  // 1 -> 2 (<=v1.0)
   2: (oldData: Record<string, unknown>) => {
     const queueBtnsEnabled = Boolean(oldData.queueButtons);
     delete oldData.queueButtons;
@@ -60,8 +60,8 @@ export const migrations: DataMigrationsDict = {
   ]),
   // 5 -> 6 (v2.1)
   6: (oldData: FeatureConfig) => useDefaultConfig(oldData, [
-    "autoLikeChannels", "openAutoLikeChannelsDialog",
-    "autoLikeChannelToggleButtons",
+    "autoLikeChannels", "autoLikeChannelToggleBtn", "autoLikePlayerBarToggleBtn",
+    "autoLikeTimeout", "autoLikeOpenMgmtDialog",
   ]),
   // TODO: once advanced filtering is fully implemented, clear cache on migration to fv6
   // 6 -> 7 (v2.x)

+ 23 - 14
src/dialogs/autoLikeChannels.ts

@@ -1,6 +1,6 @@
 import { getDomain, onInteraction, t } from "../utils";
 import { BytmDialog, createCircularBtn, createToggleInput } from "../components";
-import { autoLikeChannelsStore } from "../features";
+import { autoLikeStore } from "../features";
 import { debounce } from "@sv443-network/userutils";
 
 let autoLikeChannelsDialog: BytmDialog | null = null;
@@ -32,7 +32,7 @@ export function initAutoLikeChannelsStore() {
   if(isLoaded)
     return;
   isLoaded = true;
-  return autoLikeChannelsStore.loadData();
+  return autoLikeStore.loadData();
 }
 
 async function renderHeader() {
@@ -56,6 +56,7 @@ async function renderBody() {
   contElem.appendChild(descriptionEl);
 
   const addNewWrapper = document.createElement("div");
+  addNewWrapper.id = "bytm-auto-like-channels-add-new-wrapper";
 
   const addNewEl = document.createElement("span");
   addNewEl.id = "bytm-auto-like-channels-add-new";
@@ -68,16 +69,24 @@ async function renderBody() {
   addNewWrapper.appendChild(addNewEl);
 
   onInteraction(addNewEl, async () => {
-    const id = prompt(t("add_auto_like_channel_id_prompt"))?.trim();
-    if(!id)
+    const idPrompt = prompt(t("add_auto_like_channel_id_prompt"))?.trim();
+    if(!idPrompt)
       return;
 
-    if(!id.match(/^[a-zA-Z0-9_-]{20,}$/))
+    const isId = idPrompt.match(/^@?.+$/);
+    const isUrl = idPrompt.match(/^(?:https?:\/\/)?(?:www\.)?(?:music\.)?youtube\.com\/(?:channel\/|@)([a-zA-Z0-9_-]+)/);
+
+    if(isId?.[0]?.startsWith("@"))
+      isId[0] = isId[0].slice(1);
+
+    const id = (isId?.[0] || isUrl?.[1] || "").trim();
+
+    if(!id || id.length <= 0)
       return alert(t("add_auto_like_channel_invalid_id"));
 
     let overwriteName = false;
 
-    if(autoLikeChannelsStore.getData().channels.some((ch) => ch.id === id)) {
+    if(autoLikeStore.getData().channels.some((ch) => ch.id === id)) {
       if(!confirm(t("add_auto_like_channel_already_exists_prompt_new_name")))
         return;
       overwriteName = true;
@@ -87,15 +96,15 @@ async function renderBody() {
     if(!name || name.length === 0)
       return;
 
-    await autoLikeChannelsStore.setData(
+    await autoLikeStore.setData(
       overwriteName
         ? {
-          channels: autoLikeChannelsStore.getData().channels
+          channels: autoLikeStore.getData().channels
             .map((ch) => ch.id === id ? { ...ch, name } : ch),
         }
         : {
           channels: [
-            ...autoLikeChannelsStore.getData().channels,
+            ...autoLikeStore.getData().channels,
             { id, name, enabled: true },
           ],
         }
@@ -114,20 +123,20 @@ async function renderBody() {
   const channelListCont = document.createElement("div");
   channelListCont.id = "bytm-auto-like-channels-list";
 
-  const removeChannel = (id: string) => autoLikeChannelsStore.setData({
-    channels: autoLikeChannelsStore.getData().channels.filter((ch) => ch.id !== id),
+  const removeChannel = (id: string) => autoLikeStore.setData({
+    channels: autoLikeStore.getData().channels.filter((ch) => ch.id !== id),
   });
 
   const setChannelEnabled = (id: string, enabled: boolean) => debounce(
-    () => autoLikeChannelsStore.setData({
-      channels: autoLikeChannelsStore.getData().channels
+    () => autoLikeStore.setData({
+      channels: autoLikeStore.getData().channels
         .map((ch) => ch.id === id ? { ...ch, enabled } : ch),
     }),
     250,
     "rising"
   );
 
-  const sortedChannels = autoLikeChannelsStore
+  const sortedChannels = autoLikeStore
     .getData().channels
     .sort((a, b) => a.name.localeCompare(b.name));
 

+ 23 - 3
src/features/index.ts

@@ -449,16 +449,36 @@ export const featInfo = {
   autoLikeChannels: {
     type: "toggle",
     category: "input",
-    default: false,
+    default: true,
     textAdornment: adornments.reloadRequired,
   },
-  autoLikeChannelToggleButtons: {
+  autoLikeChannelToggleBtn: {
     type: "toggle",
     category: "input",
     default: true,
+    reloadRequired: false,
+    enable: noop,
+  },
+  autoLikePlayerBarToggleBtn: {
+    type: "toggle",
+    category: "input",
+    default: false,
+    textAdornment: adornments.reloadRequired,
+  },
+  autoLikeTimeout: {
+    type: "slider",
+    category: "input",
+    min: 3,
+    max: 30,
+    step: 0.5,
+    default: 5,
+    unit: "s",
+    advanced: true,
+    reloadRequired: false,
+    enable: noop,
     textAdornment: adornments.reloadRequired,
   },
-  openAutoLikeChannelsDialog: {
+  autoLikeOpenMgmtDialog: {
     type: "button",
     category: "input",
     click: () => getAutoLikeChannelsDialog().then(d => d.open()),

+ 17 - 4
src/features/input.css

@@ -2,6 +2,15 @@
   --bytm-auto-like-btn-color: #bf87f0;
 }
 
+#bytm-auto-like-channels-list {
+  display: flex;
+  flex-direction: column;
+  overflow-x: hidden;
+  overflow-y: auto;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+
 .bytm-auto-like-channel-row-left-cont {
   display: flex;
   align-items: center;
@@ -23,10 +32,9 @@
   margin-bottom: 15px;
 }
 
-#bytm-auto-like-channels-add-new {
-  padding: 4px 15px;
-  margin-top: 20px;
-  margin-bottom: 10px;
+#bytm-auto-like-channels-add-new-wrapper {
+  margin: 15px;
+  margin-top: 5px;
 }
 
 .bytm-auto-like-channel-row:hover {
@@ -65,6 +73,11 @@
   animation: none;
 }
 
+.bytm-auto-like-toggle-btn.left-margin {
+  margin: 0;
+  margin-left: 8px;
+}
+
 .bytm-auto-like-toggle-btn:active {
   background-color: rgba(255, 255, 255, 0.1);
 }

+ 106 - 30
src/features/input.ts

@@ -5,7 +5,7 @@ import { isCfgMenuOpen } from "../menu/menu_old";
 import { disableBeforeUnload } from "./behavior";
 import { siteEvents } from "../siteEvents";
 import { featInfo } from "./index";
-import { getFeatures } from "../config";
+import { getFeature } from "../config";
 import { compressionFormat } from "../constants";
 import { addSelectorListener } from "../observers";
 import { createLongBtn, showIconToast } from "../components";
@@ -18,7 +18,7 @@ export const inputIgnoreTagNames = ["INPUT", "TEXTAREA", "SELECT", "BUTTON", "A"
 
 export async function initArrowKeySkip() {
   document.addEventListener("keydown", (evt) => {
-    if(!getFeatures().arrowKeySupport)
+    if(!getFeature("arrowKeySupport"))
       return;
 
     if(!["ArrowLeft", "ArrowRight"].includes(evt.code))
@@ -36,7 +36,7 @@ export async function initArrowKeySkip() {
     evt.preventDefault();
     evt.stopImmediatePropagation();
 
-    let skipBy = getFeatures().arrowKeySkipBy ?? featInfo.arrowKeySkipBy.default;
+    let skipBy = getFeature("arrowKeySkipBy") ?? featInfo.arrowKeySkipBy.default;
     if(evt.code === "ArrowLeft")
       skipBy *= -1;
 
@@ -59,14 +59,14 @@ let siteSwitchEnabled = true;
 /** Initializes the site switch feature */
 export async function initSiteSwitch(domain: Domain) {
   document.addEventListener("keydown", (e) => {
-    if(!getFeatures().switchBetweenSites)
+    if(!getFeature("switchBetweenSites"))
       return;
-    const hk = getFeatures().switchSitesHotkey;
+    const hk = getFeature("switchSitesHotkey");
     if(siteSwitchEnabled && e.code === hk.code && e.shiftKey === hk.shift && e.ctrlKey === hk.ctrl && e.altKey === hk.alt)
       switchSite(domain === "yt" ? "ytm" : "yt");
   });
   siteEvents.on("hotkeyInputActive", (state) => {
-    if(!getFeatures().switchBetweenSites)
+    if(!getFeature("switchBetweenSites"))
       return;
     siteSwitchEnabled = !state;
   });
@@ -126,7 +126,7 @@ const numKeysIgnoreIds = ["progress-bar", "song-media-window"];
 /** Adds the ability to skip to a certain time in the video by pressing a number key (0-9) */
 export async function initNumKeysSkip() {
   document.addEventListener("keydown", (e) => {
-    if(!getFeatures().numKeysSkipToTime)
+    if(!getFeature("numKeysSkipToTime"))
       return;
     if(!e.key.trim().match(/^[0-9]$/))
       return;
@@ -157,10 +157,14 @@ export async function initNumKeysSkip() {
 
 let canCompress = false;
 
-export const autoLikeChannelsStore = new DataStore<{
+/** DataStore instance for all auto-liked channels */
+export const autoLikeStore = new DataStore<{
   channels: {
+    /** 24-character channel ID or user ID without the @ */
     id: string;
+    /** Channel name (for display purposes only) */
     name: string;
+    /** Whether the channel should be auto-liked */
     enabled: boolean;
   }[];
 }>({
@@ -180,7 +184,6 @@ export async function initAutoLikeChannels() {
     await initAutoLikeChannelsStore();
     if(getDomain() === "ytm") {
       let timeout: NodeJS.Timeout;
-      // TODO:FIXME: needs actual fix instead of timeout
       siteEvents.on("songTitleChanged", () => {
         timeout && clearTimeout(timeout);
         timeout = setTimeout(() => {
@@ -188,7 +191,7 @@ export async function initAutoLikeChannels() {
           const artistEls = document.querySelectorAll<HTMLAnchorElement>("ytmusic-player-bar .content-info-wrapper .subtitle a.yt-formatted-string[href]");
           const channelIds = [...artistEls].map(a => a.href.split("/").pop()).filter(a => typeof a === "string") as string[];
 
-          const likeChan = autoLikeChannelsStore.getData().channels.find((ch) => channelIds.includes(ch.id));
+          const likeChan = autoLikeStore.getData().channels.find((ch) => channelIds.includes(ch.id));
 
           if(!likeChan || !likeChan.enabled)
             return;
@@ -206,38 +209,111 @@ export async function initAutoLikeChannels() {
             likeBtn.click();
             log(`Auto-liked channel '${likeChan.name}' (ID: '${likeChan.id}')`);
           }
-        }, 5_000);
+        }, (getFeature("autoLikeTimeout") ?? 5) * 1000);
       });
 
       siteEvents.on("pathChanged", (path) => {
-        if(path.match(/\/channel\/.+/)) {
+        if(getFeature("autoLikeChannelToggleBtn") && path.match(/\/channel\/.+/)) {
           const chanId = path.split("/").pop();
           if(!chanId)
             return error("Couldn't extract channel ID from URL");
 
           document.querySelectorAll<HTMLElement>(".bytm-auto-like-toggle-btn").forEach((btn) => clearNode(btn));
 
-          addSelectorListener("browseResponse", "ytmusic-browse-response #header .actions .buttons", {
-            listener(buttonsCont) {
-              const lastBtn = buttonsCont.querySelector<HTMLElement>("ytmusic-subscribe-button-renderer");
-              const chanName = document.querySelector<HTMLElement>("ytmusic-immersive-header-renderer .content-container yt-formatted-string[role=\"heading\"]")?.textContent ?? null;
-              lastBtn && addAutoLikeToggleBtn(lastBtn, chanId, chanName);
+          addSelectorListener("browseResponse", "ytmusic-browse-response #header.ytmusic-browse-response", {
+            listener(headerCont) {
+              const buttonsCont = headerCont.querySelector<HTMLElement>(".buttons");
+              if(buttonsCont) {
+                const lastBtn = buttonsCont.querySelector<HTMLElement>("ytmusic-subscribe-button-renderer");
+                const chanName = document.querySelector<HTMLElement>("ytmusic-immersive-header-renderer .content-container yt-formatted-string[role=\"heading\"]")?.textContent ?? null;
+                lastBtn && addAutoLikeToggleBtn(lastBtn, chanId, chanName);
+              }
+              else {
+                // some channels don't have a subscribe button and instead only have a "share" button for some bullshit reason
+                // (this is only the case on YTM, on YT the subscribe button exists and works perfectly fine)
+
+                const shareBtnEl = headerCont.querySelector<HTMLElement>("ytmusic-menu-renderer #top-level-buttons yt-button-renderer:last-of-type");
+                const chanName = headerCont.querySelector<HTMLElement>("ytmusic-visual-header-renderer .content-container h2 yt-formatted-string")?.textContent ?? null;
+                shareBtnEl && chanName && addAutoLikeToggleBtn(shareBtnEl, chanId, chanName);
+              }
             }
           });
         }
       });
     }
     else if(getDomain() === "yt") {
-      // TODO:
+      let timeout: NodeJS.Timeout;
+      siteEvents.on("watchIdChanged", () => {
+        timeout && clearTimeout(timeout);
+        timeout = setTimeout(() => {
+          addSelectorListener<HTMLAnchorElement, "yt">("watchMetadata", "#owner ytd-channel-name yt-formatted-string a", {
+            listener(chanElem) {
+              let chanId = chanElem.href.split("/").pop();
+              if(chanId?.startsWith("@"))
+                chanId = chanId.slice(1);
+
+              const likeChan = autoLikeStore.getData().channels.find((ch) => ch.id === chanId);
+              if(!likeChan || !likeChan.enabled)
+                return;
+
+              const likeBtn = document.querySelector<HTMLButtonElement>("#actions ytd-menu-renderer like-button-view-model button");
+              if(!likeBtn)
+                return error("Couldn't auto-like channel because the like button couldn't be found");
+
+              if(likeBtn.getAttribute("aria-pressed") !== "true") {
+                likeBtn.click();
+                showIconToast({
+                  message: t("auto_liked_video"),
+                  icon: "icon-auto_like",
+                });
+                log(`Auto-liked channel '${likeChan.name}' (ID: '${likeChan.id}')`);
+              }
+            }
+          });
+        }, (getFeature("autoLikeTimeout") ?? 5) * 1000);
+      });
+
+      siteEvents.on("pathChanged", (path) => {
+        if(path.match(/(\/?@|\/channel\/).+/)) {
+          const chanId = path.split("/").pop()?.replace(/@/g, "");
+          if(!chanId)
+            return error("Couldn't extract channel ID from URL");
+
+          document.querySelectorAll<HTMLElement>(".bytm-auto-like-toggle-btn").forEach((btn) => clearNode(btn));
+
+          addSelectorListener<0, "yt">("ytChannelHeader", "#channel-header-container", {
+            listener(headerCont) {
+              const titleCont = headerCont.querySelector<HTMLElement>("ytd-channel-name #container");
+              if(!titleCont)
+                return;
+
+              const chanName = titleCont.querySelector<HTMLElement>("yt-formatted-string")?.textContent ?? null;
+
+              const buttonsCont = headerCont.querySelector<HTMLElement>("#inner-header-container #buttons");
+              if(buttonsCont) {
+                addSelectorListener<0, "yt">("ytChannelHeader", "#channel-header-container #other-buttons", {
+                  listener(otherBtns) {
+                    addAutoLikeToggleBtn(otherBtns, chanId, chanName, ["left-margin"]);
+                  }
+                });
+              }
+              else if(titleCont)
+                addAutoLikeToggleBtn(titleCont, chanId, chanName);
+            }
+          });
+        }
+      });
     }
+
+    log("Initialized auto-like channels feature");
   }
   catch(err) {
     error("Error while auto-liking channel:", err);
   }
 }
 
-async function addAutoLikeToggleBtn(siblingEl: HTMLElement, channelId: string, channelName: string | null) {
-  const chan = autoLikeChannelsStore.getData().channels.find((ch) => ch.id === channelId);
+async function addAutoLikeToggleBtn(siblingEl: HTMLElement, channelId: string, channelName: string | null, extraClasses?: string[]) {
+  const chan = autoLikeStore.getData().channels.find((ch) => ch.id === channelId);
 
   const buttonEl = await createLongBtn({
     resourceName: `icon-auto_like${chan?.enabled ? "_enabled" : ""}`,
@@ -261,28 +337,28 @@ async function addAutoLikeToggleBtn(siblingEl: HTMLElement, channelId: string, c
       if(imgEl && imgHtml)
         imgEl.innerHTML = imgHtml;
 
-      showIconToast(
-        toggled ? t("auto_like_enabled_toast") : t("auto_like_disabled_toast"),
-        `icon-auto_like${toggled ? "_enabled" : ""}`,
-      );
+      showIconToast({
+        message: toggled ? t("auto_like_enabled_toast") : t("auto_like_disabled_toast"),
+        icon: `icon-auto_like${toggled ? "_enabled" : ""}`,
+      });
 
-      if(autoLikeChannelsStore.getData().channels.find((ch) => ch.id === chanId) === undefined) {
-        await autoLikeChannelsStore.setData({
+      if(autoLikeStore.getData().channels.find((ch) => ch.id === chanId) === undefined) {
+        await autoLikeStore.setData({
           channels: [
-            ...autoLikeChannelsStore.getData().channels,
+            ...autoLikeStore.getData().channels,
             { id: chanId, name: channelName ?? "", enabled: toggled },
           ],
         });
       }
       else {
-        await autoLikeChannelsStore.setData({
-          channels: autoLikeChannelsStore.getData().channels
+        await autoLikeStore.setData({
+          channels: autoLikeStore.getData().channels
             .map((ch) => ch.id === chanId ? { ...ch, enabled: toggled } : ch),
         });
       }
     }
   });
-  buttonEl.classList.add("bytm-auto-like-toggle-btn");
+  buttonEl.classList.add(...["bytm-auto-like-toggle-btn", ...(extraClasses ?? [])]);
   buttonEl.dataset.channelId = channelId;
 
   siblingEl.insertAdjacentElement("afterend", buttonEl);

+ 6 - 4
src/index.ts

@@ -115,6 +115,8 @@ async function onDomLoad() {
   const features = getFeatures();
   const ftInit = [] as [string, Promise<void>][];
 
+  document.body.classList.add(`bytm-dom-${domain}`);
+
   try {
     initObservers();
 
@@ -132,10 +134,6 @@ async function onDomLoad() {
 
   try {
     if(domain === "ytm") {
-      //#region (ytm) misc
-
-      ftInit.push(["initSiteEvents", initSiteEvents()]);
-
       //#region (ytm) welcome dlg
 
       if(typeof await GM.getValue("bytm-installed") !== "string") {
@@ -215,6 +213,10 @@ async function onDomLoad() {
     }
 
     if(["ytm", "yt"].includes(domain)) {
+      //#region general
+
+      ftInit.push(["initSiteEvents", initSiteEvents()]);
+
       //#region (ytm+yt) layout
 
       if(features.disableDarkReaderSites !== "none")

+ 66 - 15
src/observers.ts

@@ -9,28 +9,31 @@ export type ObserverName = SharedObserverName | YTMObserverName | YTObserverName
 /** Observer names available to each site */
 export type ObserverNameByDomain<TDomain extends Domain> = SharedObserverName | (TDomain extends "ytm" ? YTMObserverName : YTObserverName);
 
-// both YTM and YT
+// Shared between YTM and YT
 export type SharedObserverName =
-  | "body";
+  | "body"; // the entire <body> element
 
 // YTM only
 export type YTMObserverName =
-  | "browseResponse"
-  | "navBar"
-  | "mainPanel"
-  | "sideBar"
-  | "sideBarMini"
-  | "sidePanel"
-  | "playerBar"
-  | "playerBarInfo"
-  | "playerBarMiddleButtons"
-  | "playerBarRightControls"
-  | "popupContainer";
+  | "browseResponse"         // the /channel/UC... page
+  | "navBar"                 // the navigation / title bar at the top of the page
+  | "mainPanel"              // the main content panel - includes things like the video element
+  | "sideBar"                // the sidebar on the left side of the page
+  | "sideBarMini"            // the minimized sidebar on the left side of the page
+  | "sidePanel"              // the side panel on the right side of the /watch page
+  | "playerBar"              // media controls bar at the bottom of the page
+  | "playerBarInfo"          // song title, artist, album, etc. inside the player bar
+  | "playerBarMiddleButtons" // the buttons inside the player bar (like, dislike, lyrics, etc.)
+  | "playerBarRightControls" // the controls on the right side of the player bar (volume, repeat, shuffle, etc.)
+  | "popupContainer";        // the container for popups (e.g. the queue popup)
 
 // YT only
 export type YTObserverName =
-  // | "ytMasthead" // the title bar
-  | "ytGuide"; // the left sidebar menu
+  | "ytGuide"         // the left sidebar menu
+  | "ytdBrowse"       // channel pages for example
+  | "ytChannelHeader" // header of a channel page
+  | "watchFlexy"      // the main content of the /watch page
+  | "watchMetadata";  // the metadata section of the /watch page
 
 /** Options that are applied to every SelectorObserver instance */
 const defaultObserverOptions: SelectorObserverOptions = {
@@ -214,6 +217,54 @@ export function initObservers() {
         listener: () => globservers.ytGuide.enable(),
       });
 
+      //#region ytdBrowse
+      // -> channel pages for example
+      const ytdBrowseSelector = "ytd-app ytd-page-manager ytd-browse";
+      globservers.ytdBrowse = new SelectorObserver(ytdBrowseSelector, {
+        ...defaultObserverOptions,
+        subtree: true,
+      });
+
+      globservers.body.addListener(ytdBrowseSelector, {
+        listener: () => globservers.ytdBrowse.enable(),
+      });
+
+      //#region ytChannelHeader
+      // -> header of a channel page
+      const ytChannelHeaderSelector = "#header tp-yt-app-header #channel-header";
+      globservers.ytChannelHeader = new SelectorObserver(ytChannelHeaderSelector, {
+        ...defaultObserverOptions,
+        subtree: true,
+      });
+
+      globservers.ytdBrowse.addListener(ytChannelHeaderSelector, {
+        listener: () => globservers.ytChannelHeader.enable(),
+      });
+
+      //#region watchFlexy
+      // -> the main content of the /watch page
+      const watchFlexySelector = "ytd-app ytd-watch-flexy";
+      globservers.watchFlexy = new SelectorObserver(watchFlexySelector, {
+        ...defaultObserverOptions,
+        subtree: true,
+      });
+
+      globservers.body.addListener(watchFlexySelector, {
+        listener: () => globservers.watchFlexy.enable(),
+      });
+
+      //#region watchMetadata
+      // -> the metadata section of the /watch page (title, channel, views, description, buttons, etc. but not comments)
+      const watchMetadataSelector = "#columns #primary-inner ytd-watch-metadata";
+      globservers.watchMetadata = new SelectorObserver(watchMetadataSelector, {
+        ...defaultObserverOptions,
+        subtree: true,
+      });
+
+      globservers.watchFlexy.addListener(watchMetadataSelector, {
+        listener: () => globservers.watchMetadata.enable(),
+      });
+
       // //#region ytMasthead
       // -> the masthead (title bar) at the top of the page
       // const mastheadSelector = "#content ytd-masthead#masthead";

+ 77 - 75
src/siteEvents.ts

@@ -1,5 +1,5 @@
 import { createNanoEvents } from "nanoevents";
-import { error, info } from "./utils";
+import { error, getDomain, info } from "./utils";
 import { FeatureConfig } from "./types";
 import { emitInterface } from "./interface";
 import { addSelectorListener } from "./observers";
@@ -85,80 +85,82 @@ let lastFullscreen: boolean;
 /** Creates MutationObservers that check if parts of the site have changed, then emit an event on the `siteEvents` instance. */
 export async function initSiteEvents() {
   try {
-    //#region queue
-    // the queue container always exists so it doesn't need an extra init function
-    const queueObs = new MutationObserver(([ { addedNodes, removedNodes, target } ]) => {
-      if(addedNodes.length > 0 || removedNodes.length > 0) {
-        info(`Detected queue change - added nodes: ${[...addedNodes.values()].length} - removed nodes: ${[...removedNodes.values()].length}`);
-        emitSiteEvent("queueChanged", target as HTMLElement);
-      }
-    });
-
-    // only observe added or removed elements
-    addSelectorListener("sidePanel", "#contents.ytmusic-player-queue", {
-      listener: (el) => {
-        queueObs.observe(el, {
-          childList: true,
-        });
-      },
-    });
-
-    const autoplayObs = new MutationObserver(([ { addedNodes, removedNodes, target } ]) => {
-      if(addedNodes.length > 0 || removedNodes.length > 0) {
-        info(`Detected autoplay queue change - added nodes: ${[...addedNodes.values()].length} - removed nodes: ${[...removedNodes.values()].length}`);
-        emitSiteEvent("autoplayQueueChanged", target as HTMLElement);
-      }
-    });
-
-    addSelectorListener("sidePanel", "ytmusic-player-queue #automix-contents", {
-      listener: (el) => {
-        autoplayObs.observe(el, {
-          childList: true,
-        });
-      },
-    });
-
-    //#region player bar
-
-    let lastTitle: string | null = null;
-
-    addSelectorListener("playerBarInfo", "yt-formatted-string.title", {
-      continuous: true,
-      listener: (titleElem) => {
-        const oldTitle = lastTitle;
-        const newTitle = titleElem.textContent;
-        if(newTitle === lastTitle || !newTitle)
-          return;
-        lastTitle = newTitle;
-        info(`Detected song change - old title: "${oldTitle}" - new title: "${newTitle}"`);
-        emitSiteEvent("songTitleChanged", newTitle, oldTitle);
-      },
-    });
-
-    info("Successfully initialized SiteEvents observers");
-
-    observers = observers.concat([
-      queueObs,
-      autoplayObs,
-    ]);
-
-    //#region player
-
-    const playerFullscreenObs = new MutationObserver(([{ target }]) => {
-      const isFullscreen = (target as HTMLElement).getAttribute("player-ui-state")?.toUpperCase() === "FULLSCREEN";
-      if(lastFullscreen !== isFullscreen || typeof lastFullscreen === "undefined") {
-        emitSiteEvent("fullscreenToggled", isFullscreen);
-        lastFullscreen = isFullscreen;
-      }
-    });
-
-    addSelectorListener("mainPanel", "ytmusic-player#player", {
-      listener: (el) => {
-        playerFullscreenObs.observe(el, {
-          attributeFilter: ["player-ui-state"],
-        });
-      },
-    });
+    if(getDomain() === "ytm") {
+      //#region queue
+      // the queue container always exists so it doesn't need an extra init function
+      const queueObs = new MutationObserver(([ { addedNodes, removedNodes, target } ]) => {
+        if(addedNodes.length > 0 || removedNodes.length > 0) {
+          info(`Detected queue change - added nodes: ${[...addedNodes.values()].length} - removed nodes: ${[...removedNodes.values()].length}`);
+          emitSiteEvent("queueChanged", target as HTMLElement);
+        }
+      });
+
+      // only observe added or removed elements
+      addSelectorListener("sidePanel", "#contents.ytmusic-player-queue", {
+        listener: (el) => {
+          queueObs.observe(el, {
+            childList: true,
+          });
+        },
+      });
+
+      const autoplayObs = new MutationObserver(([ { addedNodes, removedNodes, target } ]) => {
+        if(addedNodes.length > 0 || removedNodes.length > 0) {
+          info(`Detected autoplay queue change - added nodes: ${[...addedNodes.values()].length} - removed nodes: ${[...removedNodes.values()].length}`);
+          emitSiteEvent("autoplayQueueChanged", target as HTMLElement);
+        }
+      });
+
+      addSelectorListener("sidePanel", "ytmusic-player-queue #automix-contents", {
+        listener: (el) => {
+          autoplayObs.observe(el, {
+            childList: true,
+          });
+        },
+      });
+
+      //#region player bar
+
+      let lastTitle: string | null = null;
+
+      addSelectorListener("playerBarInfo", "yt-formatted-string.title", {
+        continuous: true,
+        listener: (titleElem) => {
+          const oldTitle = lastTitle;
+          const newTitle = titleElem.textContent;
+          if(newTitle === lastTitle || !newTitle)
+            return;
+          lastTitle = newTitle;
+          info(`Detected song change - old title: "${oldTitle}" - new title: "${newTitle}"`);
+          emitSiteEvent("songTitleChanged", newTitle, oldTitle);
+        },
+      });
+
+      info("Successfully initialized SiteEvents observers");
+
+      observers = observers.concat([
+        queueObs,
+        autoplayObs,
+      ]);
+
+      //#region player
+
+      const playerFullscreenObs = new MutationObserver(([{ target }]) => {
+        const isFullscreen = (target as HTMLElement).getAttribute("player-ui-state")?.toUpperCase() === "FULLSCREEN";
+        if(lastFullscreen !== isFullscreen || typeof lastFullscreen === "undefined") {
+          emitSiteEvent("fullscreenToggled", isFullscreen);
+          lastFullscreen = isFullscreen;
+        }
+      });
+
+      addSelectorListener("mainPanel", "ytmusic-player#player", {
+        listener: (el) => {
+          playerFullscreenObs.observe(el, {
+            attributeFilter: ["player-ui-state"],
+          });
+        },
+      });
+    }
 
     //#region other
 

+ 7 - 3
src/types.ts

@@ -421,10 +421,14 @@ export interface FeatureConfig {
   anchorImprovements: boolean;
   /** Whether to auto-like all played videos of configured channels */
   autoLikeChannels: boolean;
+  /** Whether to show toggle buttons on the channel page to enable/disable auto-liking for that channel */
+  autoLikeChannelToggleBtn: boolean;
+  /** Whether to show a toggle button in the media controls to enable/disable auto-liking for those channel(s) */
+  autoLikePlayerBarToggleBtn: boolean;
+  /** How long to wait after a video has started playing to auto-like it */
+  autoLikeTimeout: number;
   /** Opens the auto-like channels management dialog */
-  openAutoLikeChannelsDialog: undefined;
-  /** Whether to show toggle buttons in the media controls to enable/disable auto-liking for that channel */
-  autoLikeChannelToggleButtons: boolean;
+  autoLikeOpenMgmtDialog: undefined;
 
   //#region lyrics
   /** Add a button to the media controls to open the current song's lyrics on genius.com in a new tab */