Add GitHub version checker to Stream-Mapparr plugin

Implements automatic version checking that displays update status on the plugin settings page.

Features:
- Fetches latest release version from GitHub using stdlib (urllib.request)
- Displays version status at top of settings page (Update Available / Up to Date)
- Caches version check results for 24 hours to minimize API calls
- Re-checks automatically when plugin version changes
- Fails gracefully if network unavailable or GitHub API errors occur

Technical Details:
- Uses only Python standard library (no requests dependency)
- Version check triggered via fields property (runs when settings opened)
- Cache stored in /data/stream_mapparr_version_check.json
- Timeout set to 5 seconds for API requests
- All errors logged at debug level to avoid cluttering logs
This commit is contained in:
Claude
2025-11-11 01:40:24 +00:00
parent a71e8f0382
commit 0853013665

View File

@@ -9,7 +9,9 @@ import csv
import os import os
import re import re
import requests import requests
from datetime import datetime import urllib.request
import urllib.error
from datetime import datetime, timedelta
from django.utils import timezone from django.utils import timezone
# Django model imports # Django model imports
@@ -37,8 +39,20 @@ class Plugin:
@property @property
def fields(self): def fields(self):
"""Dynamically generate settings fields including channel database selection.""" """Dynamically generate settings fields including channel database selection."""
# Check for version updates (with caching)
version_info = {'message': f"Current version: {self.version}", 'status': 'unknown'}
try:
version_info = self._check_version_update()
except Exception as e:
LOGGER.debug(f"[Stream-Mapparr] Error checking version update: {e}")
# Static fields that are always present # Static fields that are always present
static_fields = [ static_fields = [
{
"id": "version_status",
"type": "info",
"label": version_info['message'],
},
{ {
"id": "overwrite_streams", "id": "overwrite_streams",
"label": "🔄 Overwrite Existing Streams", "label": "🔄 Overwrite Existing Streams",
@@ -240,6 +254,7 @@ class Plugin:
def __init__(self): def __init__(self):
self.processed_data_file = "/data/stream_mapparr_processed.json" self.processed_data_file = "/data/stream_mapparr_processed.json"
self.version_check_cache_file = "/data/stream_mapparr_version_check.json"
self.loaded_channels = [] self.loaded_channels = []
self.loaded_streams = [] self.loaded_streams = []
self.channel_stream_matches = [] self.channel_stream_matches = []
@@ -247,6 +262,158 @@ class Plugin:
LOGGER.info(f"[Stream-Mapparr] {self.name} Plugin v{self.version} initialized") LOGGER.info(f"[Stream-Mapparr] {self.name} Plugin v{self.version} initialized")
def _get_latest_version(self, owner, repo):
"""
Fetches the latest release tag name from GitHub using only Python's standard library.
Args:
owner (str): GitHub repository owner
repo (str): GitHub repository name
Returns:
str: Latest version tag or error message
"""
url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
# Add a user-agent to avoid potential 403 Forbidden errors
headers = {
'User-Agent': 'Dispatcharr-Plugin-Version-Checker'
}
try:
# Create a request object with headers
req = urllib.request.Request(url, headers=headers)
# Make the request and open the URL with a timeout
with urllib.request.urlopen(req, timeout=5) as response:
# Read the response and decode it as UTF-8
data = response.read().decode('utf-8')
# Parse the JSON string
json_data = json.loads(data)
# Get the tag name
latest_version = json_data.get("tag_name")
if latest_version:
return latest_version
else:
return None
except urllib.error.HTTPError as http_err:
if http_err.code == 404:
LOGGER.debug(f"[Stream-Mapparr] GitHub repo not found or has no releases: {http_err}")
return None
else:
LOGGER.debug(f"[Stream-Mapparr] HTTP error checking version: {http_err.code}")
return None
except Exception as e:
# Catch other errors like timeouts
LOGGER.debug(f"[Stream-Mapparr] Error checking version: {str(e)}")
return None
def _check_version_update(self):
"""
Check if a new version is available on GitHub.
Uses caching to limit checks to once per day or when the plugin version changes.
Returns:
dict: Contains 'message' and 'status' keys for display
"""
current_version = self.version
github_owner = "PiratesIRC"
github_repo = "Stream-Mapparr"
# Default response
result = {
'message': f"Current version: {current_version}",
'status': 'unknown'
}
try:
# Load cache if it exists
cache_data = {}
should_check = True
if os.path.exists(self.version_check_cache_file):
try:
with open(self.version_check_cache_file, 'r', encoding='utf-8') as f:
cache_data = json.load(f)
# Check if we need to recheck:
# 1. Plugin version changed (upgrade/downgrade)
# 2. More than 24 hours since last check
cached_plugin_version = cache_data.get('plugin_version')
last_check_str = cache_data.get('last_check')
if cached_plugin_version == current_version and last_check_str:
# Parse the last check time
last_check = datetime.fromisoformat(last_check_str)
time_diff = datetime.now() - last_check
# If less than 24 hours, use cached data
if time_diff < timedelta(hours=24):
should_check = False
latest_version = cache_data.get('latest_version')
# Compare versions
if latest_version and latest_version != current_version:
result = {
'message': f"🎉 Update available! Current: {current_version} → Latest: {latest_version}",
'status': 'update_available'
}
else:
result = {
'message': f"✅ You are up to date (v{current_version})",
'status': 'up_to_date'
}
except Exception as e:
LOGGER.debug(f"[Stream-Mapparr] Error reading version cache: {e}")
should_check = True
# Perform the check if needed
if should_check:
latest_version = self._get_latest_version(github_owner, github_repo)
# Update cache
cache_data = {
'plugin_version': current_version,
'latest_version': latest_version,
'last_check': datetime.now().isoformat()
}
try:
with open(self.version_check_cache_file, 'w', encoding='utf-8') as f:
json.dump(cache_data, f, indent=2)
except Exception as e:
LOGGER.debug(f"[Stream-Mapparr] Error writing version cache: {e}")
# Compare versions
if latest_version and latest_version != current_version:
result = {
'message': f"🎉 Update available! Current: {current_version} → Latest: {latest_version}",
'status': 'update_available'
}
elif latest_version:
result = {
'message': f"✅ You are up to date (v{current_version})",
'status': 'up_to_date'
}
else:
result = {
'message': f"Current version: {current_version} (unable to check for updates)",
'status': 'error'
}
except Exception as e:
LOGGER.debug(f"[Stream-Mapparr] Error in version check: {e}")
result = {
'message': f"Current version: {current_version} (update check failed)",
'status': 'error'
}
return result
def _get_channel_databases(self): def _get_channel_databases(self):
""" """
Scan for channel database files and return metadata for each. Scan for channel database files and return metadata for each.