Skip to content

Commit 78654d4

Browse files
authored
[iptv-checker]: Bump to v1.26.1582047 — Discord webhook + atomic progress-write fixes (#114)
[iptv-checker]: Bump to v1.26.1582047 — fix Discord webhook delivery (#20) and atomic progress writes (#21)
1 parent bc522f1 commit 78654d4

2 files changed

Lines changed: 43 additions & 19 deletions

File tree

plugins/iptv-checker/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "IPTV Checker",
3-
"version": "1.26.1421301",
3+
"version": "1.26.1582047",
44
"description": "A Dispatcharr Plugin that goes through a playlist to check IPTV channels",
55
"author": "PiratesIRC",
66
"logo": "logo.png",

plugins/iptv-checker/plugin.py

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ class Plugin:
282282

283283
# Explicitly set the plugin key
284284
key = "iptv_checker"
285-
version = "1.26.1421301"
285+
version = "1.26.1582047"
286286

287287
# Fields and actions are defined in plugin.json (single source of truth)
288288
def __init__(self):
@@ -734,12 +734,17 @@ def _load_progress(self):
734734
return {"current": 0, "total": 0, "status": "idle", "start_time": None}
735735

736736
def _save_progress(self):
737-
"""Save check progress to persistent storage"""
738-
try:
739-
with open(self.progress_file, 'w') as f:
740-
json.dump(self.check_progress, f)
741-
except Exception as e:
742-
LOGGER.error(f"Failed to save progress file: {e}")
737+
"""Save check progress to persistent storage.
738+
739+
Uses the atomic tmp-file + os.replace helper rather than a plain
740+
open(path, 'w'). A direct write fails with EACCES when an existing
741+
progress file is owned by root and not group-writable (e.g. TrueNAS
742+
SCALE, where the app runs as uid 568 — see issue #21). The atomic
743+
path writes a fresh temp file owned by the current user and renames
744+
it over the target, which only requires write permission on the
745+
parent directory, so it succeeds regardless of the old file's owner.
746+
"""
747+
self._save_json_file(self.progress_file, self.check_progress)
743748

744749
def _load_json_file(self, filepath):
745750
"""Safely load a JSON file, returning None if corrupted or missing."""
@@ -1581,20 +1586,39 @@ def _fire_webhook(self, settings, logger):
15811586
dead = sum(1 for r in results if r.get('status') == 'Dead')
15821587
skipped = sum(1 for r in results if r.get('status') == 'Skipped')
15831588

1584-
payload = json.dumps({
1585-
"plugin": self.key,
1586-
"event": "check_complete",
1587-
"total": len(results),
1588-
"alive": alive,
1589-
"dead": dead,
1590-
"skipped": skipped,
1591-
"timestamp": datetime.utcnow().isoformat() + "Z",
1592-
}).encode('utf-8')
1593-
1589+
# Discord webhooks reject the plugin's custom JSON shape (they only render
1590+
# {"content": ...} / {"embeds": [...]}). Detect a Discord host and send a
1591+
# native readable summary instead. Everything else keeps the original
1592+
# machine-readable payload for backward compatibility with existing consumers.
1593+
host = (urllib.parse.urlparse(webhook_url).hostname or '').lower()
1594+
is_discord = host in ('discord.com', 'discordapp.com') or host.endswith('.discord.com')
1595+
1596+
if is_discord:
1597+
content = (
1598+
f"**IPTV Checker — check complete**\n"
1599+
f"Total: {len(results)} • ✅ Alive: {alive} • ❌ Dead: {dead} • ⏭️ Skipped: {skipped}"
1600+
)
1601+
payload = json.dumps({"content": content}).encode('utf-8')
1602+
else:
1603+
payload = json.dumps({
1604+
"plugin": self.key,
1605+
"event": "check_complete",
1606+
"total": len(results),
1607+
"alive": alive,
1608+
"dead": dead,
1609+
"skipped": skipped,
1610+
"timestamp": datetime.utcnow().isoformat() + "Z",
1611+
}).encode('utf-8')
1612+
1613+
# Always set an explicit User-Agent. Discord's Cloudflare edge 403s the
1614+
# default "Python-urllib/3.x" UA, which silently dropped every webhook.
15941615
req = urllib.request.Request(
15951616
webhook_url,
15961617
data=payload,
1597-
headers={"Content-Type": "application/json"},
1618+
headers={
1619+
"Content-Type": "application/json",
1620+
"User-Agent": f"Dispatcharr-IPTV-Checker/{self.version}",
1621+
},
15981622
method="POST",
15991623
)
16001624

0 commit comments

Comments
 (0)