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 re
import requests
from datetime import datetime
import urllib.request
import urllib.error
from datetime import datetime, timedelta
from django.utils import timezone
# Django model imports
@@ -37,8 +39,20 @@ class Plugin:
@property
def fields(self):
"""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 = [
{
"id": "version_status",
"type": "info",
"label": version_info['message'],
},
{
"id": "overwrite_streams",
"label": "🔄 Overwrite Existing Streams",
@@ -240,6 +254,7 @@ class Plugin:
def __init__(self):
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_streams = []
self.channel_stream_matches = []
@@ -247,6 +262,158 @@ class Plugin:
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):
"""
Scan for channel database files and return metadata for each.