Procházet zdrojové kódy

feat: almost finished auto-like

Sv443 před 11 měsíci
rodič
revize
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:
 ### Translation progress:
 |   | Locale | Translated keys | Based on |
 |   | 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>
 <sub>
 ✅ - Fully translated
 ✅ - Fully translated
@@ -45,7 +45,7 @@ This means to figure out which keys are untranslated, you will need to manually
 
 
 ### Missing keys:
 ### 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 |
 | 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` | `Auto-like` |
 | `auto_like_button_tooltip_enabled` | `Click to disable auto-liking. Shift-click to open the management dialog.` |
 | `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.` |
 | `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_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_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.` |
 | `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_enabled_toast` | `Auto-liking enabled` |
 | `auto_like_disabled_toast` | `Auto-liking disabled` |
 | `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_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_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_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.` |
 | `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` |
 | `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>
 <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 |
 | 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` | `Auto-like` |
 | `auto_like_button_tooltip_enabled` | `Click to disable auto-liking. Shift-click to open the management dialog.` |
 | `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.` |
 | `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_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_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.` |
 | `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_enabled_toast` | `Auto-liking enabled` |
 | `auto_like_disabled_toast` | `Auto-liking disabled` |
 | `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_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_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_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.` |
 | `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` |
 | `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>
 <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 |
 | 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` | `Auto-like` |
 | `auto_like_button_tooltip_enabled` | `Click to disable auto-liking. Shift-click to open the management dialog.` |
 | `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.` |
 | `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_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_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.` |
 | `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_enabled_toast` | `Auto-liking enabled` |
 | `auto_like_disabled_toast` | `Auto-liking disabled` |
 | `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_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_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_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.` |
 | `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` |
 | `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>
 <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 |
 | 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` | `Auto-like` |
 | `auto_like_button_tooltip_enabled` | `Click to disable auto-liking. Shift-click to open the management dialog.` |
 | `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.` |
 | `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_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_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.` |
 | `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_enabled_toast` | `Auto-liking enabled` |
 | `auto_like_disabled_toast` | `Auto-liking disabled` |
 | `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_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_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_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.` |
 | `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` |
 | `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>
 <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 |
 | 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` | `Auto-like` |
 | `auto_like_button_tooltip_enabled` | `Click to disable auto-liking. Shift-click to open the management dialog.` |
 | `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.` |
 | `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_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_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.` |
 | `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_enabled_toast` | `Auto-liking enabled` |
 | `auto_like_disabled_toast` | `Auto-liking disabled` |
 | `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_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_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_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.` |
 | `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` |
 | `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>
 <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 |
 | 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` | `Auto-like` |
 | `auto_like_button_tooltip_enabled` | `Click to disable auto-liking. Shift-click to open the management dialog.` |
 | `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.` |
 | `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_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_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.` |
 | `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_enabled_toast` | `Auto-liking enabled` |
 | `auto_like_disabled_toast` | `Auto-liking disabled` |
 | `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_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_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_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.` |
 | `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` |
 | `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>
 <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 |
 | 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` | `Auto-like` |
 | `auto_like_button_tooltip_enabled` | `Click to disable auto-liking. Shift-click to open the management dialog.` |
 | `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.` |
 | `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_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_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.` |
 | `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_enabled_toast` | `Auto-liking enabled` |
 | `auto_like_disabled_toast` | `Auto-liking disabled` |
 | `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_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_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_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.` |
 | `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` |
 | `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": "Auto-like",
     "auto_like_button_tooltip_enabled": "Click to disable auto-liking. Shift-click to open the management dialog.",
     "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.",
     "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_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_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.",
     "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_enabled_toast": "Auto-liking enabled",
     "auto_like_disabled_toast": "Auto-liking disabled",
     "auto_like_disabled_toast": "Auto-liking disabled",
+    "auto_liked_video": "Auto-liked the video",
 
 
     "unit_entries-1": "entry",
     "unit_entries-1": "entry",
     "unit_entries-n": "entries",
     "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_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_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_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_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",
     "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 "./circularButton";
 export * from "./hotkeyInput";
 export * from "./hotkeyInput";
 export * from "./longButton";
 export * from "./longButton";
+export * from "./ripple";
 export * from "./toast";
 export * from "./toast";
 export * from "./toggleInput";
 export * from "./toggleInput";

+ 15 - 12
src/components/toast.css

@@ -1,12 +1,15 @@
-#bytm-toast {
+:root {
   --bytm-toast-bg-color: #323232;
   --bytm-toast-bg-color: #323232;
   --bytm-toast-text-color: #fff;
   --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-transition-time: 0.5s;
+  --bytm-toast-transform-distance: 175px;
+}
 
 
+#bytm-toast {
   position: fixed;
   position: fixed;
-  z-index: 100;
+  z-index: 1042069;
   opacity: 0.000001;
   opacity: 0.000001;
   pointer-events: none;
   pointer-events: none;
   padding: 10px 20px;
   padding: 10px 20px;
@@ -25,26 +28,26 @@
 }
 }
 
 
 #bytm-toast.pos-tl {
 #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));
   transform: translate(0, calc(var(--bytm-toast-transform-distance) * -1));
 }
 }
 
 
 #bytm-toast.pos-tr {
 #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));
   transform: translate(0, calc(var(--bytm-toast-transform-distance) * -1));
 }
 }
 
 
 #bytm-toast.pos-br {
 #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));
   transform: translate(0, var(--bytm-toast-transform-distance));
 }
 }
 
 
 #bytm-toast.pos-bl {
 #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));
   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 { pauseFor } from "@sv443-network/userutils";
+import { resourceToHTMLString } from "../utils";
 import type { ResourceKey } from "../types";
 import type { ResourceKey } from "../types";
 import "./toast.css";
 import "./toast.css";
-import { resourceToHTMLString } from "src/utils";
 
 
 type ToastPos = "tl" | "tr" | "bl" | "br";
 type ToastPos = "tl" | "tr" | "bl" | "br";
 
 
 type ToastProps = {
 type ToastProps = {
-  duration: number;
-  position: ToastPos;
+  duration?: number;
+  position?: ToastPos;
 } & (
 } & (
   | {
   | {
     message: string;
     message: string;
@@ -18,10 +18,19 @@ type ToastProps = {
   }
   }
 );
 );
 
 
+type IconToastProps = ToastProps & {
+  icon: ResourceKey;
+};
+
 let timeout: NodeJS.Timeout | undefined;
 let timeout: NodeJS.Timeout | undefined;
 
 
 /** Shows a toast message with an icon */
 /** 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");
   const toastWrapper = document.createElement("div");
   toastWrapper.classList.add("bytm-toast-flex-wrapper");
   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");
   const toastMessage = document.createElement("div");
   toastMessage.classList.add("bytm-toast-message");
   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(toastIcon);
   toastWrapper.appendChild(toastMessage);
   toastWrapper.appendChild(toastMessage);
@@ -42,7 +54,7 @@ export async function showIconToast(message: string, icon: ResourceKey, duration
     duration,
     duration,
     position,
     position,
     element: toastWrapper,
     element: toastWrapper,
-    title: message,
+    title: "message" in rest ? rest.message : rest.title,
   });
   });
 }
 }
 
 
@@ -62,6 +74,8 @@ export async function showToast({
   toastElem.ariaLive = "assertive";
   toastElem.ariaLive = "assertive";
   toastElem.ariaAtomic = "true";
   toastElem.ariaAtomic = "true";
 
 
+  toastElem.addEventListener("click", async () => await closeToast(), { once: true });
+
   if("message" in rest)
   if("message" in rest)
     toastElem.title = toastElem.ariaLabel = toastElem.textContent = rest.message;
     toastElem.title = toastElem.ariaLabel = toastElem.textContent = rest.message;
   else {
   else {

+ 3 - 3
src/config.ts

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

+ 23 - 14
src/dialogs/autoLikeChannels.ts

@@ -1,6 +1,6 @@
 import { getDomain, onInteraction, t } from "../utils";
 import { getDomain, onInteraction, t } from "../utils";
 import { BytmDialog, createCircularBtn, createToggleInput } from "../components";
 import { BytmDialog, createCircularBtn, createToggleInput } from "../components";
-import { autoLikeChannelsStore } from "../features";
+import { autoLikeStore } from "../features";
 import { debounce } from "@sv443-network/userutils";
 import { debounce } from "@sv443-network/userutils";
 
 
 let autoLikeChannelsDialog: BytmDialog | null = null;
 let autoLikeChannelsDialog: BytmDialog | null = null;
@@ -32,7 +32,7 @@ export function initAutoLikeChannelsStore() {
   if(isLoaded)
   if(isLoaded)
     return;
     return;
   isLoaded = true;
   isLoaded = true;
-  return autoLikeChannelsStore.loadData();
+  return autoLikeStore.loadData();
 }
 }
 
 
 async function renderHeader() {
 async function renderHeader() {
@@ -56,6 +56,7 @@ async function renderBody() {
   contElem.appendChild(descriptionEl);
   contElem.appendChild(descriptionEl);
 
 
   const addNewWrapper = document.createElement("div");
   const addNewWrapper = document.createElement("div");
+  addNewWrapper.id = "bytm-auto-like-channels-add-new-wrapper";
 
 
   const addNewEl = document.createElement("span");
   const addNewEl = document.createElement("span");
   addNewEl.id = "bytm-auto-like-channels-add-new";
   addNewEl.id = "bytm-auto-like-channels-add-new";
@@ -68,16 +69,24 @@ async function renderBody() {
   addNewWrapper.appendChild(addNewEl);
   addNewWrapper.appendChild(addNewEl);
 
 
   onInteraction(addNewEl, async () => {
   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;
       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"));
       return alert(t("add_auto_like_channel_invalid_id"));
 
 
     let overwriteName = false;
     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")))
       if(!confirm(t("add_auto_like_channel_already_exists_prompt_new_name")))
         return;
         return;
       overwriteName = true;
       overwriteName = true;
@@ -87,15 +96,15 @@ async function renderBody() {
     if(!name || name.length === 0)
     if(!name || name.length === 0)
       return;
       return;
 
 
-    await autoLikeChannelsStore.setData(
+    await autoLikeStore.setData(
       overwriteName
       overwriteName
         ? {
         ? {
-          channels: autoLikeChannelsStore.getData().channels
+          channels: autoLikeStore.getData().channels
             .map((ch) => ch.id === id ? { ...ch, name } : ch),
             .map((ch) => ch.id === id ? { ...ch, name } : ch),
         }
         }
         : {
         : {
           channels: [
           channels: [
-            ...autoLikeChannelsStore.getData().channels,
+            ...autoLikeStore.getData().channels,
             { id, name, enabled: true },
             { id, name, enabled: true },
           ],
           ],
         }
         }
@@ -114,20 +123,20 @@ async function renderBody() {
   const channelListCont = document.createElement("div");
   const channelListCont = document.createElement("div");
   channelListCont.id = "bytm-auto-like-channels-list";
   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(
   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),
         .map((ch) => ch.id === id ? { ...ch, enabled } : ch),
     }),
     }),
     250,
     250,
     "rising"
     "rising"
   );
   );
 
 
-  const sortedChannels = autoLikeChannelsStore
+  const sortedChannels = autoLikeStore
     .getData().channels
     .getData().channels
     .sort((a, b) => a.name.localeCompare(b.name));
     .sort((a, b) => a.name.localeCompare(b.name));
 
 

+ 23 - 3
src/features/index.ts

@@ -449,16 +449,36 @@ export const featInfo = {
   autoLikeChannels: {
   autoLikeChannels: {
     type: "toggle",
     type: "toggle",
     category: "input",
     category: "input",
-    default: false,
+    default: true,
     textAdornment: adornments.reloadRequired,
     textAdornment: adornments.reloadRequired,
   },
   },
-  autoLikeChannelToggleButtons: {
+  autoLikeChannelToggleBtn: {
     type: "toggle",
     type: "toggle",
     category: "input",
     category: "input",
     default: true,
     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,
     textAdornment: adornments.reloadRequired,
   },
   },
-  openAutoLikeChannelsDialog: {
+  autoLikeOpenMgmtDialog: {
     type: "button",
     type: "button",
     category: "input",
     category: "input",
     click: () => getAutoLikeChannelsDialog().then(d => d.open()),
     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-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 {
 .bytm-auto-like-channel-row-left-cont {
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
@@ -23,10 +32,9 @@
   margin-bottom: 15px;
   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 {
 .bytm-auto-like-channel-row:hover {
@@ -65,6 +73,11 @@
   animation: none;
   animation: none;
 }
 }
 
 
+.bytm-auto-like-toggle-btn.left-margin {
+  margin: 0;
+  margin-left: 8px;
+}
+
 .bytm-auto-like-toggle-btn:active {
 .bytm-auto-like-toggle-btn:active {
   background-color: rgba(255, 255, 255, 0.1);
   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 { disableBeforeUnload } from "./behavior";
 import { siteEvents } from "../siteEvents";
 import { siteEvents } from "../siteEvents";
 import { featInfo } from "./index";
 import { featInfo } from "./index";
-import { getFeatures } from "../config";
+import { getFeature } from "../config";
 import { compressionFormat } from "../constants";
 import { compressionFormat } from "../constants";
 import { addSelectorListener } from "../observers";
 import { addSelectorListener } from "../observers";
 import { createLongBtn, showIconToast } from "../components";
 import { createLongBtn, showIconToast } from "../components";
@@ -18,7 +18,7 @@ export const inputIgnoreTagNames = ["INPUT", "TEXTAREA", "SELECT", "BUTTON", "A"
 
 
 export async function initArrowKeySkip() {
 export async function initArrowKeySkip() {
   document.addEventListener("keydown", (evt) => {
   document.addEventListener("keydown", (evt) => {
-    if(!getFeatures().arrowKeySupport)
+    if(!getFeature("arrowKeySupport"))
       return;
       return;
 
 
     if(!["ArrowLeft", "ArrowRight"].includes(evt.code))
     if(!["ArrowLeft", "ArrowRight"].includes(evt.code))
@@ -36,7 +36,7 @@ export async function initArrowKeySkip() {
     evt.preventDefault();
     evt.preventDefault();
     evt.stopImmediatePropagation();
     evt.stopImmediatePropagation();
 
 
-    let skipBy = getFeatures().arrowKeySkipBy ?? featInfo.arrowKeySkipBy.default;
+    let skipBy = getFeature("arrowKeySkipBy") ?? featInfo.arrowKeySkipBy.default;
     if(evt.code === "ArrowLeft")
     if(evt.code === "ArrowLeft")
       skipBy *= -1;
       skipBy *= -1;
 
 
@@ -59,14 +59,14 @@ let siteSwitchEnabled = true;
 /** Initializes the site switch feature */
 /** Initializes the site switch feature */
 export async function initSiteSwitch(domain: Domain) {
 export async function initSiteSwitch(domain: Domain) {
   document.addEventListener("keydown", (e) => {
   document.addEventListener("keydown", (e) => {
-    if(!getFeatures().switchBetweenSites)
+    if(!getFeature("switchBetweenSites"))
       return;
       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)
     if(siteSwitchEnabled && e.code === hk.code && e.shiftKey === hk.shift && e.ctrlKey === hk.ctrl && e.altKey === hk.alt)
       switchSite(domain === "yt" ? "ytm" : "yt");
       switchSite(domain === "yt" ? "ytm" : "yt");
   });
   });
   siteEvents.on("hotkeyInputActive", (state) => {
   siteEvents.on("hotkeyInputActive", (state) => {
-    if(!getFeatures().switchBetweenSites)
+    if(!getFeature("switchBetweenSites"))
       return;
       return;
     siteSwitchEnabled = !state;
     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) */
 /** Adds the ability to skip to a certain time in the video by pressing a number key (0-9) */
 export async function initNumKeysSkip() {
 export async function initNumKeysSkip() {
   document.addEventListener("keydown", (e) => {
   document.addEventListener("keydown", (e) => {
-    if(!getFeatures().numKeysSkipToTime)
+    if(!getFeature("numKeysSkipToTime"))
       return;
       return;
     if(!e.key.trim().match(/^[0-9]$/))
     if(!e.key.trim().match(/^[0-9]$/))
       return;
       return;
@@ -157,10 +157,14 @@ export async function initNumKeysSkip() {
 
 
 let canCompress = false;
 let canCompress = false;
 
 
-export const autoLikeChannelsStore = new DataStore<{
+/** DataStore instance for all auto-liked channels */
+export const autoLikeStore = new DataStore<{
   channels: {
   channels: {
+    /** 24-character channel ID or user ID without the @ */
     id: string;
     id: string;
+    /** Channel name (for display purposes only) */
     name: string;
     name: string;
+    /** Whether the channel should be auto-liked */
     enabled: boolean;
     enabled: boolean;
   }[];
   }[];
 }>({
 }>({
@@ -180,7 +184,6 @@ export async function initAutoLikeChannels() {
     await initAutoLikeChannelsStore();
     await initAutoLikeChannelsStore();
     if(getDomain() === "ytm") {
     if(getDomain() === "ytm") {
       let timeout: NodeJS.Timeout;
       let timeout: NodeJS.Timeout;
-      // TODO:FIXME: needs actual fix instead of timeout
       siteEvents.on("songTitleChanged", () => {
       siteEvents.on("songTitleChanged", () => {
         timeout && clearTimeout(timeout);
         timeout && clearTimeout(timeout);
         timeout = setTimeout(() => {
         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 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 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)
           if(!likeChan || !likeChan.enabled)
             return;
             return;
@@ -206,38 +209,111 @@ export async function initAutoLikeChannels() {
             likeBtn.click();
             likeBtn.click();
             log(`Auto-liked channel '${likeChan.name}' (ID: '${likeChan.id}')`);
             log(`Auto-liked channel '${likeChan.name}' (ID: '${likeChan.id}')`);
           }
           }
-        }, 5_000);
+        }, (getFeature("autoLikeTimeout") ?? 5) * 1000);
       });
       });
 
 
       siteEvents.on("pathChanged", (path) => {
       siteEvents.on("pathChanged", (path) => {
-        if(path.match(/\/channel\/.+/)) {
+        if(getFeature("autoLikeChannelToggleBtn") && path.match(/\/channel\/.+/)) {
           const chanId = path.split("/").pop();
           const chanId = path.split("/").pop();
           if(!chanId)
           if(!chanId)
             return error("Couldn't extract channel ID from URL");
             return error("Couldn't extract channel ID from URL");
 
 
           document.querySelectorAll<HTMLElement>(".bytm-auto-like-toggle-btn").forEach((btn) => clearNode(btn));
           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") {
     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) {
   catch(err) {
     error("Error while auto-liking channel:", 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({
   const buttonEl = await createLongBtn({
     resourceName: `icon-auto_like${chan?.enabled ? "_enabled" : ""}`,
     resourceName: `icon-auto_like${chan?.enabled ? "_enabled" : ""}`,
@@ -261,28 +337,28 @@ async function addAutoLikeToggleBtn(siblingEl: HTMLElement, channelId: string, c
       if(imgEl && imgHtml)
       if(imgEl && imgHtml)
         imgEl.innerHTML = 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: [
           channels: [
-            ...autoLikeChannelsStore.getData().channels,
+            ...autoLikeStore.getData().channels,
             { id: chanId, name: channelName ?? "", enabled: toggled },
             { id: chanId, name: channelName ?? "", enabled: toggled },
           ],
           ],
         });
         });
       }
       }
       else {
       else {
-        await autoLikeChannelsStore.setData({
-          channels: autoLikeChannelsStore.getData().channels
+        await autoLikeStore.setData({
+          channels: autoLikeStore.getData().channels
             .map((ch) => ch.id === chanId ? { ...ch, enabled: toggled } : ch),
             .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;
   buttonEl.dataset.channelId = channelId;
 
 
   siblingEl.insertAdjacentElement("afterend", buttonEl);
   siblingEl.insertAdjacentElement("afterend", buttonEl);

+ 6 - 4
src/index.ts

@@ -115,6 +115,8 @@ async function onDomLoad() {
   const features = getFeatures();
   const features = getFeatures();
   const ftInit = [] as [string, Promise<void>][];
   const ftInit = [] as [string, Promise<void>][];
 
 
+  document.body.classList.add(`bytm-dom-${domain}`);
+
   try {
   try {
     initObservers();
     initObservers();
 
 
@@ -132,10 +134,6 @@ async function onDomLoad() {
 
 
   try {
   try {
     if(domain === "ytm") {
     if(domain === "ytm") {
-      //#region (ytm) misc
-
-      ftInit.push(["initSiteEvents", initSiteEvents()]);
-
       //#region (ytm) welcome dlg
       //#region (ytm) welcome dlg
 
 
       if(typeof await GM.getValue("bytm-installed") !== "string") {
       if(typeof await GM.getValue("bytm-installed") !== "string") {
@@ -215,6 +213,10 @@ async function onDomLoad() {
     }
     }
 
 
     if(["ytm", "yt"].includes(domain)) {
     if(["ytm", "yt"].includes(domain)) {
+      //#region general
+
+      ftInit.push(["initSiteEvents", initSiteEvents()]);
+
       //#region (ytm+yt) layout
       //#region (ytm+yt) layout
 
 
       if(features.disableDarkReaderSites !== "none")
       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 */
 /** Observer names available to each site */
 export type ObserverNameByDomain<TDomain extends Domain> = SharedObserverName | (TDomain extends "ytm" ? YTMObserverName : YTObserverName);
 export type ObserverNameByDomain<TDomain extends Domain> = SharedObserverName | (TDomain extends "ytm" ? YTMObserverName : YTObserverName);
 
 
-// both YTM and YT
+// Shared between YTM and YT
 export type SharedObserverName =
 export type SharedObserverName =
-  | "body";
+  | "body"; // the entire <body> element
 
 
 // YTM only
 // YTM only
 export type YTMObserverName =
 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
 // YT only
 export type YTObserverName =
 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 */
 /** Options that are applied to every SelectorObserver instance */
 const defaultObserverOptions: SelectorObserverOptions = {
 const defaultObserverOptions: SelectorObserverOptions = {
@@ -214,6 +217,54 @@ export function initObservers() {
         listener: () => globservers.ytGuide.enable(),
         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
       // //#region ytMasthead
       // -> the masthead (title bar) at the top of the page
       // -> the masthead (title bar) at the top of the page
       // const mastheadSelector = "#content ytd-masthead#masthead";
       // const mastheadSelector = "#content ytd-masthead#masthead";

+ 77 - 75
src/siteEvents.ts

@@ -1,5 +1,5 @@
 import { createNanoEvents } from "nanoevents";
 import { createNanoEvents } from "nanoevents";
-import { error, info } from "./utils";
+import { error, getDomain, info } from "./utils";
 import { FeatureConfig } from "./types";
 import { FeatureConfig } from "./types";
 import { emitInterface } from "./interface";
 import { emitInterface } from "./interface";
 import { addSelectorListener } from "./observers";
 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. */
 /** Creates MutationObservers that check if parts of the site have changed, then emit an event on the `siteEvents` instance. */
 export async function initSiteEvents() {
 export async function initSiteEvents() {
   try {
   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
     //#region other
 
 

+ 7 - 3
src/types.ts

@@ -421,10 +421,14 @@ export interface FeatureConfig {
   anchorImprovements: boolean;
   anchorImprovements: boolean;
   /** Whether to auto-like all played videos of configured channels */
   /** Whether to auto-like all played videos of configured channels */
   autoLikeChannels: boolean;
   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 */
   /** 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
   //#region lyrics
   /** Add a button to the media controls to open the current song's lyrics on genius.com in a new tab */
   /** Add a button to the media controls to open the current song's lyrics on genius.com in a new tab */