Add emojis, fix visibility logic, and add WebSocket notifications
Changes: - Add tasteful emojis to plugin GUI fields and action labels - Fix bug: iterate sorted_channels instead of group_channels (line 1411) - Fix visibility logic: enable channels with >= 1 streams instead of 0-1 * Channels with >= 1 streams are now enabled (highest priority only) * Channels with 0 streams are disabled * Duplicate channels (lower priority in group) are disabled * Attached channels remain disabled - Add real-time WebSocket progress notifications for: * Preview Changes action (stream_mapparr_preview) * Add Streams to Channels action (stream_mapparr_add) * Manage Channel Visibility action (stream_mapparr_visibility) - Update action descriptions to reflect corrected behavior This resolves the contradiction where add_streams_to_channels would create channels with multiple streams, and manage_channel_visibility would immediately disable them.
This commit is contained in:
@@ -18,6 +18,9 @@ from apps.channels.models import Channel, Stream, ChannelStream, ChannelProfileM
|
|||||||
# Import fuzzy matcher
|
# Import fuzzy matcher
|
||||||
from .fuzzy_matcher import FuzzyMatcher
|
from .fuzzy_matcher import FuzzyMatcher
|
||||||
|
|
||||||
|
# Import WebSocket update function
|
||||||
|
from core.utils import send_websocket_update
|
||||||
|
|
||||||
# Setup logging using Dispatcharr's format
|
# Setup logging using Dispatcharr's format
|
||||||
LOGGER = logging.getLogger("plugins.stream_mapparr")
|
LOGGER = logging.getLogger("plugins.stream_mapparr")
|
||||||
if not LOGGER.handlers:
|
if not LOGGER.handlers:
|
||||||
@@ -32,27 +35,27 @@ class Plugin:
|
|||||||
|
|
||||||
name = "Stream-Mapparr"
|
name = "Stream-Mapparr"
|
||||||
version = "0.5.0a"
|
version = "0.5.0a"
|
||||||
description = "Automatically add matching streams to channels based on name similarity and quality precedence with enhanced fuzzy matching"
|
description = "🎯 Automatically add matching streams to channels based on name similarity and quality precedence with enhanced fuzzy matching"
|
||||||
|
|
||||||
# Settings rendered by UI
|
# Settings rendered by UI
|
||||||
fields = [
|
fields = [
|
||||||
{
|
{
|
||||||
"id": "overwrite_streams",
|
"id": "overwrite_streams",
|
||||||
"label": "Overwrite Existing Streams",
|
"label": "🔄 Overwrite Existing Streams",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": True,
|
"default": True,
|
||||||
"help_text": "If enabled, all existing streams will be removed and replaced with matched streams. If disabled, only new streams will be added (existing streams preserved).",
|
"help_text": "If enabled, all existing streams will be removed and replaced with matched streams. If disabled, only new streams will be added (existing streams preserved).",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "fuzzy_match_threshold",
|
"id": "fuzzy_match_threshold",
|
||||||
"label": "Fuzzy Match Threshold",
|
"label": "🎯 Fuzzy Match Threshold",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"default": 85,
|
"default": 85,
|
||||||
"help_text": "Minimum similarity score (0-100) for fuzzy matching. Higher values require closer matches. Default: 85",
|
"help_text": "Minimum similarity score (0-100) for fuzzy matching. Higher values require closer matches. Default: 85",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "dispatcharr_url",
|
"id": "dispatcharr_url",
|
||||||
"label": "Dispatcharr URL",
|
"label": "🌐 Dispatcharr URL",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "",
|
"default": "",
|
||||||
"placeholder": "http://192.168.1.10:9191",
|
"placeholder": "http://192.168.1.10:9191",
|
||||||
@@ -60,20 +63,20 @@ class Plugin:
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "dispatcharr_username",
|
"id": "dispatcharr_username",
|
||||||
"label": "Dispatcharr Admin Username",
|
"label": "👤 Dispatcharr Admin Username",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"help_text": "Your admin username for the Dispatcharr UI. Required for API access.",
|
"help_text": "Your admin username for the Dispatcharr UI. Required for API access.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "dispatcharr_password",
|
"id": "dispatcharr_password",
|
||||||
"label": "Dispatcharr Admin Password",
|
"label": "🔑 Dispatcharr Admin Password",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"input_type": "password",
|
"input_type": "password",
|
||||||
"help_text": "Your admin password for the Dispatcharr UI. Required for API access.",
|
"help_text": "Your admin password for the Dispatcharr UI. Required for API access.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "profile_name",
|
"id": "profile_name",
|
||||||
"label": "Profile Name",
|
"label": "📋 Profile Name",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "",
|
"default": "",
|
||||||
"placeholder": "Sports",
|
"placeholder": "Sports",
|
||||||
@@ -81,7 +84,7 @@ class Plugin:
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "selected_groups",
|
"id": "selected_groups",
|
||||||
"label": "Channel Groups (comma-separated)",
|
"label": "📁 Channel Groups (comma-separated)",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "",
|
"default": "",
|
||||||
"placeholder": "Sports, News, Entertainment",
|
"placeholder": "Sports, News, Entertainment",
|
||||||
@@ -89,7 +92,7 @@ class Plugin:
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "ignore_tags",
|
"id": "ignore_tags",
|
||||||
"label": "Ignore Tags (comma-separated)",
|
"label": "🏷️ Ignore Tags (comma-separated)",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "",
|
"default": "",
|
||||||
"placeholder": "4K, [4K], [Dead]",
|
"placeholder": "4K, [4K], [Dead]",
|
||||||
@@ -97,7 +100,7 @@ class Plugin:
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "visible_channel_limit",
|
"id": "visible_channel_limit",
|
||||||
"label": "Visible Channel Limit",
|
"label": "👁️ Visible Channel Limit",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"default": 1,
|
"default": 1,
|
||||||
"help_text": "Number of channels that will be visible and have streams added. Channels are prioritized by quality tags, then by channel number.",
|
"help_text": "Number of channels that will be visible and have streams added. Channels are prioritized by quality tags, then by channel number.",
|
||||||
@@ -108,17 +111,17 @@ class Plugin:
|
|||||||
actions = [
|
actions = [
|
||||||
{
|
{
|
||||||
"id": "load_process_channels",
|
"id": "load_process_channels",
|
||||||
"label": "Load/Process Channels",
|
"label": "📥 Load/Process Channels",
|
||||||
"description": "Validate settings and load channels from the specified profile and groups",
|
"description": "Validate settings and load channels from the specified profile and groups",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "preview_changes",
|
"id": "preview_changes",
|
||||||
"label": "Preview Changes (Dry Run)",
|
"label": "👀 Preview Changes (Dry Run)",
|
||||||
"description": "Preview which streams will be added to channels without making changes",
|
"description": "Preview which streams will be added to channels without making changes",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "add_streams_to_channels",
|
"id": "add_streams_to_channels",
|
||||||
"label": "Add Stream(s) to Channels",
|
"label": "✅ Add Stream(s) to Channels",
|
||||||
"description": "Add matching streams to channels and replace existing stream assignments",
|
"description": "Add matching streams to channels and replace existing stream assignments",
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"required": True,
|
"required": True,
|
||||||
@@ -128,17 +131,17 @@ class Plugin:
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "manage_channel_visibility",
|
"id": "manage_channel_visibility",
|
||||||
"label": "Manage Channel Visibility",
|
"label": "👁️ Manage Channel Visibility",
|
||||||
"description": "Disable all channels, then enable only channels with 0 or 1 stream (excluding channels attached to others)",
|
"description": "Disable all channels, then enable only channels with 1 or more streams (excluding channels attached to others)",
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"required": True,
|
"required": True,
|
||||||
"title": "Manage Channel Visibility?",
|
"title": "Manage Channel Visibility?",
|
||||||
"message": "This will disable ALL channels in the profile, then enable only channels with 0 or 1 stream that are not attached to other channels. Continue?"
|
"message": "This will disable ALL channels in the profile, then enable only channels with 1 or more streams that are not attached to other channels. Continue?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clear_csv_exports",
|
"id": "clear_csv_exports",
|
||||||
"label": "Clear CSV Exports",
|
"label": "🗑️ Clear CSV Exports",
|
||||||
"description": "Delete all CSV export files created by this plugin",
|
"description": "Delete all CSV export files created by this plugin",
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"required": True,
|
"required": True,
|
||||||
@@ -909,6 +912,16 @@ class Plugin:
|
|||||||
logger.info(f"[Stream-Mapparr] Previewing changes for {len(channels)} channels with {len(streams)} available streams")
|
logger.info(f"[Stream-Mapparr] Previewing changes for {len(channels)} channels with {len(streams)} available streams")
|
||||||
logger.info(f"[Stream-Mapparr] Visible channel limit: {visible_channel_limit}")
|
logger.info(f"[Stream-Mapparr] Visible channel limit: {visible_channel_limit}")
|
||||||
|
|
||||||
|
# Send initial WebSocket notification
|
||||||
|
send_websocket_update('updates', 'update', {
|
||||||
|
'type': 'stream_mapparr_preview',
|
||||||
|
'stage': 'starting',
|
||||||
|
'total': len(channels),
|
||||||
|
'matched': 0,
|
||||||
|
'progress_percent': 0,
|
||||||
|
'message': f'Starting preview for {len(channels)} channels...'
|
||||||
|
})
|
||||||
|
|
||||||
# Group channels by their cleaned name for matching
|
# Group channels by their cleaned name for matching
|
||||||
channel_groups = {}
|
channel_groups = {}
|
||||||
ignore_tags = processed_data.get('ignore_tags', [])
|
ignore_tags = processed_data.get('ignore_tags', [])
|
||||||
@@ -943,6 +956,18 @@ class Plugin:
|
|||||||
|
|
||||||
logger.info(f"[Stream-Mapparr] [{progress_pct}%] Processing channel group: {group_key} ({len(group_channels)} channels)")
|
logger.info(f"[Stream-Mapparr] [{progress_pct}%] Processing channel group: {group_key} ({len(group_channels)} channels)")
|
||||||
|
|
||||||
|
# Send progress WebSocket notification
|
||||||
|
send_websocket_update('updates', 'update', {
|
||||||
|
'type': 'stream_mapparr_preview',
|
||||||
|
'stage': 'matching',
|
||||||
|
'total': total_groups,
|
||||||
|
'current': current_group,
|
||||||
|
'matched': total_channels_with_matches,
|
||||||
|
'progress_percent': progress_pct,
|
||||||
|
'current_group': group_key,
|
||||||
|
'message': f'Processing {current_group}/{total_groups} channel groups ({progress_pct}%)...'
|
||||||
|
})
|
||||||
|
|
||||||
# Sort channels in this group by priority
|
# Sort channels in this group by priority
|
||||||
sorted_channels = self._sort_channels_by_priority(group_channels)
|
sorted_channels = self._sort_channels_by_priority(group_channels)
|
||||||
|
|
||||||
@@ -993,6 +1018,16 @@ class Plugin:
|
|||||||
|
|
||||||
logger.info(f"[Stream-Mapparr] [100%] Preview processing complete")
|
logger.info(f"[Stream-Mapparr] [100%] Preview processing complete")
|
||||||
|
|
||||||
|
# Send completion WebSocket notification
|
||||||
|
send_websocket_update('updates', 'update', {
|
||||||
|
'type': 'stream_mapparr_preview',
|
||||||
|
'stage': 'completed',
|
||||||
|
'total': len(channels),
|
||||||
|
'matched': total_channels_with_matches,
|
||||||
|
'progress_percent': 100,
|
||||||
|
'message': f'Preview complete: {total_channels_with_matches} channels matched'
|
||||||
|
})
|
||||||
|
|
||||||
# Export to CSV
|
# Export to CSV
|
||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
filename = f"stream_mapparr_preview_{timestamp}.csv"
|
filename = f"stream_mapparr_preview_{timestamp}.csv"
|
||||||
@@ -1102,6 +1137,16 @@ class Plugin:
|
|||||||
logger.info(f"[Stream-Mapparr] Visible channel limit: {visible_channel_limit}")
|
logger.info(f"[Stream-Mapparr] Visible channel limit: {visible_channel_limit}")
|
||||||
logger.info(f"[Stream-Mapparr] Overwrite existing streams: {overwrite_streams}")
|
logger.info(f"[Stream-Mapparr] Overwrite existing streams: {overwrite_streams}")
|
||||||
|
|
||||||
|
# Send initial WebSocket notification
|
||||||
|
send_websocket_update('updates', 'update', {
|
||||||
|
'type': 'stream_mapparr_add',
|
||||||
|
'stage': 'starting',
|
||||||
|
'total': len(channels),
|
||||||
|
'updated': 0,
|
||||||
|
'progress_percent': 0,
|
||||||
|
'message': f'Starting to add streams to {len(channels)} channels...'
|
||||||
|
})
|
||||||
|
|
||||||
# Group channels by their cleaned name
|
# Group channels by their cleaned name
|
||||||
channel_groups = {}
|
channel_groups = {}
|
||||||
for channel in channels:
|
for channel in channels:
|
||||||
@@ -1136,6 +1181,18 @@ class Plugin:
|
|||||||
|
|
||||||
logger.info(f"[Stream-Mapparr] [{progress_pct}%] Processing channel group: {group_key} ({len(group_channels)} channels)")
|
logger.info(f"[Stream-Mapparr] [{progress_pct}%] Processing channel group: {group_key} ({len(group_channels)} channels)")
|
||||||
|
|
||||||
|
# Send progress WebSocket notification
|
||||||
|
send_websocket_update('updates', 'update', {
|
||||||
|
'type': 'stream_mapparr_add',
|
||||||
|
'stage': 'processing',
|
||||||
|
'total': total_groups,
|
||||||
|
'current': current_group,
|
||||||
|
'updated': channels_updated,
|
||||||
|
'progress_percent': progress_pct,
|
||||||
|
'current_group': group_key,
|
||||||
|
'message': f'Processing {current_group}/{total_groups} channel groups ({progress_pct}%)...'
|
||||||
|
})
|
||||||
|
|
||||||
# Sort channels in this group by priority
|
# Sort channels in this group by priority
|
||||||
sorted_channels = self._sort_channels_by_priority(group_channels)
|
sorted_channels = self._sort_channels_by_priority(group_channels)
|
||||||
|
|
||||||
@@ -1228,6 +1285,17 @@ class Plugin:
|
|||||||
|
|
||||||
logger.info(f"[Stream-Mapparr] [100%] Processing complete")
|
logger.info(f"[Stream-Mapparr] [100%] Processing complete")
|
||||||
|
|
||||||
|
# Send completion WebSocket notification
|
||||||
|
send_websocket_update('updates', 'update', {
|
||||||
|
'type': 'stream_mapparr_add',
|
||||||
|
'stage': 'completed',
|
||||||
|
'total': len(channels),
|
||||||
|
'updated': channels_updated,
|
||||||
|
'streams_added': total_streams_added,
|
||||||
|
'progress_percent': 100,
|
||||||
|
'message': f'Complete: {channels_updated} channels updated with {total_streams_added} streams'
|
||||||
|
})
|
||||||
|
|
||||||
# Trigger frontend refresh
|
# Trigger frontend refresh
|
||||||
self._trigger_frontend_refresh(settings, logger)
|
self._trigger_frontend_refresh(settings, logger)
|
||||||
|
|
||||||
@@ -1311,8 +1379,24 @@ class Plugin:
|
|||||||
logger.info(f"[Stream-Mapparr] Managing visibility for {len(channels)} channels")
|
logger.info(f"[Stream-Mapparr] Managing visibility for {len(channels)} channels")
|
||||||
logger.info(f"[Stream-Mapparr] Visible channel limit: {visible_channel_limit}")
|
logger.info(f"[Stream-Mapparr] Visible channel limit: {visible_channel_limit}")
|
||||||
|
|
||||||
|
# Send initial WebSocket notification
|
||||||
|
send_websocket_update('updates', 'update', {
|
||||||
|
'type': 'stream_mapparr_visibility',
|
||||||
|
'stage': 'starting',
|
||||||
|
'total': len(channels),
|
||||||
|
'progress_percent': 0,
|
||||||
|
'message': f'Starting visibility management for {len(channels)} channels...'
|
||||||
|
})
|
||||||
|
|
||||||
# Step 1: Get stream counts for all channels
|
# Step 1: Get stream counts for all channels
|
||||||
logger.info("[Stream-Mapparr] Step 1: Counting streams for each channel...")
|
logger.info("[Stream-Mapparr] Step 1: Counting streams for each channel...")
|
||||||
|
send_websocket_update('updates', 'update', {
|
||||||
|
'type': 'stream_mapparr_visibility',
|
||||||
|
'stage': 'counting',
|
||||||
|
'total': len(channels),
|
||||||
|
'progress_percent': 10,
|
||||||
|
'message': 'Counting streams for each channel...'
|
||||||
|
})
|
||||||
channel_stream_counts = {}
|
channel_stream_counts = {}
|
||||||
|
|
||||||
for channel in channels:
|
for channel in channels:
|
||||||
@@ -1326,6 +1410,13 @@ class Plugin:
|
|||||||
|
|
||||||
# Step 2: Find channels that are attached to other channels
|
# Step 2: Find channels that are attached to other channels
|
||||||
logger.info("[Stream-Mapparr] Step 2: Identifying attached channels...")
|
logger.info("[Stream-Mapparr] Step 2: Identifying attached channels...")
|
||||||
|
send_websocket_update('updates', 'update', {
|
||||||
|
'type': 'stream_mapparr_visibility',
|
||||||
|
'stage': 'identifying',
|
||||||
|
'total': len(channels),
|
||||||
|
'progress_percent': 25,
|
||||||
|
'message': 'Identifying attached channels...'
|
||||||
|
})
|
||||||
channels_attached_to_others = set()
|
channels_attached_to_others = set()
|
||||||
|
|
||||||
for channel in channels:
|
for channel in channels:
|
||||||
@@ -1336,6 +1427,13 @@ class Plugin:
|
|||||||
|
|
||||||
# Step 3: Disable all channels first
|
# Step 3: Disable all channels first
|
||||||
logger.info(f"[Stream-Mapparr] Step 3: Disabling all {len(channels)} channels...")
|
logger.info(f"[Stream-Mapparr] Step 3: Disabling all {len(channels)} channels...")
|
||||||
|
send_websocket_update('updates', 'update', {
|
||||||
|
'type': 'stream_mapparr_visibility',
|
||||||
|
'stage': 'disabling',
|
||||||
|
'total': len(channels),
|
||||||
|
'progress_percent': 40,
|
||||||
|
'message': f'Disabling all {len(channels)} channels...'
|
||||||
|
})
|
||||||
try:
|
try:
|
||||||
bulk_disable_payload = [
|
bulk_disable_payload = [
|
||||||
{"channel_id": channel['id'], "enabled": False}
|
{"channel_id": channel['id'], "enabled": False}
|
||||||
@@ -1370,6 +1468,13 @@ class Plugin:
|
|||||||
|
|
||||||
# Step 3.5: Group channels and apply visible channel limit
|
# Step 3.5: Group channels and apply visible channel limit
|
||||||
logger.info("[Stream-Mapparr] Step 3.5: Grouping channels and applying visibility limit...")
|
logger.info("[Stream-Mapparr] Step 3.5: Grouping channels and applying visibility limit...")
|
||||||
|
send_websocket_update('updates', 'update', {
|
||||||
|
'type': 'stream_mapparr_visibility',
|
||||||
|
'stage': 'grouping',
|
||||||
|
'total': len(channels),
|
||||||
|
'progress_percent': 60,
|
||||||
|
'message': 'Grouping channels and applying visibility rules...'
|
||||||
|
})
|
||||||
|
|
||||||
# Group channels by their cleaned name
|
# Group channels by their cleaned name
|
||||||
channel_groups = {}
|
channel_groups = {}
|
||||||
@@ -1408,7 +1513,7 @@ class Plugin:
|
|||||||
# If there are eligible channels, enable only the highest priority one
|
# If there are eligible channels, enable only the highest priority one
|
||||||
enabled_in_group = False
|
enabled_in_group = False
|
||||||
|
|
||||||
for ch in group_channels:
|
for ch in sorted_channels:
|
||||||
channel_id = ch['id']
|
channel_id = ch['id']
|
||||||
channel_name = ch['name']
|
channel_name = ch['name']
|
||||||
|
|
||||||
@@ -1426,17 +1531,18 @@ class Plugin:
|
|||||||
if is_attached:
|
if is_attached:
|
||||||
reason = 'Attached to another channel'
|
reason = 'Attached to another channel'
|
||||||
should_enable = False
|
should_enable = False
|
||||||
elif stream_count >= 2:
|
elif not enabled_in_group and stream_count >= 1:
|
||||||
reason = f'{stream_count} streams (too many)'
|
# This is the highest priority, non-attached channel WITH streams
|
||||||
should_enable = False
|
reason = f'{stream_count} stream{"s" if stream_count != 1 else ""}'
|
||||||
elif not enabled_in_group and (stream_count == 0 or stream_count == 1):
|
|
||||||
# This is the highest priority channel with 0-1 streams
|
|
||||||
reason = f'{stream_count} stream{"" if stream_count == 1 else "s"}'
|
|
||||||
should_enable = True
|
should_enable = True
|
||||||
enabled_in_group = True
|
enabled_in_group = True
|
||||||
|
elif stream_count == 0:
|
||||||
|
# This channel has no streams
|
||||||
|
reason = 'No streams found'
|
||||||
|
should_enable = False
|
||||||
else:
|
else:
|
||||||
# Another channel in this group is already enabled
|
# This is a duplicate (a lower-priority channel in the group)
|
||||||
reason = 'Duplicate - higher priority channel in group already enabled'
|
reason = 'Duplicate - higher priority channel enabled'
|
||||||
should_enable = False
|
should_enable = False
|
||||||
|
|
||||||
channel_stream_counts[channel_id] = {
|
channel_stream_counts[channel_id] = {
|
||||||
@@ -1453,6 +1559,14 @@ class Plugin:
|
|||||||
|
|
||||||
# Step 4: Enable selected channels
|
# Step 4: Enable selected channels
|
||||||
logger.info(f"[Stream-Mapparr] Step 4: Enabling {len(channels_to_enable)} channels...")
|
logger.info(f"[Stream-Mapparr] Step 4: Enabling {len(channels_to_enable)} channels...")
|
||||||
|
send_websocket_update('updates', 'update', {
|
||||||
|
'type': 'stream_mapparr_visibility',
|
||||||
|
'stage': 'enabling',
|
||||||
|
'total': len(channels),
|
||||||
|
'to_enable': len(channels_to_enable),
|
||||||
|
'progress_percent': 80,
|
||||||
|
'message': f'Enabling {len(channels_to_enable)} channels...'
|
||||||
|
})
|
||||||
channels_enabled = 0
|
channels_enabled = 0
|
||||||
|
|
||||||
if channels_to_enable:
|
if channels_to_enable:
|
||||||
@@ -1490,6 +1604,17 @@ class Plugin:
|
|||||||
except Exception as e2:
|
except Exception as e2:
|
||||||
logger.error(f"[Stream-Mapparr] Failed to enable channel {channel_id}: {e2}")
|
logger.error(f"[Stream-Mapparr] Failed to enable channel {channel_id}: {e2}")
|
||||||
|
|
||||||
|
# Send completion WebSocket notification
|
||||||
|
send_websocket_update('updates', 'update', {
|
||||||
|
'type': 'stream_mapparr_visibility',
|
||||||
|
'stage': 'completed',
|
||||||
|
'total': len(channels),
|
||||||
|
'enabled': channels_enabled,
|
||||||
|
'disabled': len(channels) - channels_enabled,
|
||||||
|
'progress_percent': 100,
|
||||||
|
'message': f'Complete: {channels_enabled} channels enabled, {len(channels) - channels_enabled} disabled'
|
||||||
|
})
|
||||||
|
|
||||||
# Trigger frontend refresh
|
# Trigger frontend refresh
|
||||||
self._trigger_frontend_refresh(settings, logger)
|
self._trigger_frontend_refresh(settings, logger)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user