sunrise2mm.py: after a successful upload, scan the channel and delete any
post older than MM_SUNRISE_RETENTION_DAYS that is not pinned and has a file
attachment. Pinned posts are always preserved regardless of age.
sky-cam.conf: add MM_SUNRISE_RETENTION_DAYS=8 (independent of the local
disk SUNRISE_RETENTION_DAYS so the two retentions can differ).
Also modernises sunrise2mm.py: uses the same ${VAR:-default} aware
read_config as sunrise.py, consistent error handling, cleaner structure.
https://claude.ai/code/session_01C4jbd3waXG3eKZYbGUjLUQ
164 lines
4.8 KiB
Python
Executable File
164 lines
4.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import os
|
|
import pathlib
|
|
import re
|
|
import subprocess
|
|
from datetime import datetime
|
|
|
|
import requests
|
|
|
|
_here = pathlib.Path(__file__).resolve().parent
|
|
|
|
|
|
def read_config(path):
|
|
conf = {}
|
|
try:
|
|
with open(path) as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if not line or line.startswith('#') or '=' not in line:
|
|
continue
|
|
k, v = line.split('=', 1)
|
|
k = k.strip()
|
|
v = v.strip().strip('"').strip("'")
|
|
m = re.match(r'^\$\{[^}]+:-([^}]*)\}$', v)
|
|
if m:
|
|
v = m.group(1).strip('"').strip("'")
|
|
conf[k] = v
|
|
except FileNotFoundError:
|
|
pass
|
|
return conf
|
|
|
|
|
|
config = read_config(_here / 'sky-cam.conf')
|
|
config.update(read_config(_here / '.env'))
|
|
|
|
mattermost_url = config.get('mattermost_url', '').rstrip('/')
|
|
access_token = config.get('access_token', '')
|
|
channel_id = config.get('channel_id', '')
|
|
|
|
if not all([mattermost_url, access_token, channel_id]):
|
|
print("Error: mattermost_url / access_token / channel_id not set in .env")
|
|
raise SystemExit(1)
|
|
|
|
headers = {'Authorization': f'Bearer {access_token}'}
|
|
|
|
today_date_str = datetime.now().strftime('%Y-%m-%d')
|
|
today_month_str = datetime.now().strftime('%Y-%m')
|
|
|
|
movies_dir = config.get('MOVIES_DIR', '')
|
|
sunrise_cam = config.get('SUNRISE_CAM', 'east')
|
|
output_dir = os.path.join(movies_dir, sunrise_cam, today_month_str, 'sunrise-only')
|
|
video_file = os.path.join(output_dir, f'{today_date_str}-daily-sunrise.mp4')
|
|
|
|
print(f"Looking for video file: {video_file}")
|
|
|
|
if not os.path.isfile(video_file):
|
|
print(f"No video file found at {video_file}.")
|
|
subprocess.run(
|
|
[str(_here / 'notify.sh'),
|
|
f'FAILED: sunrise upload {today_date_str}',
|
|
f'Video file not found: {video_file}'],
|
|
check=False
|
|
)
|
|
raise SystemExit(1)
|
|
|
|
|
|
# ── Upload ────────────────────────────────────────────────────────────────────
|
|
with open(video_file, 'rb') as f:
|
|
resp = requests.post(
|
|
f'{mattermost_url}/api/v4/files',
|
|
headers=headers,
|
|
files={'files': f},
|
|
data={'channel_id': channel_id}
|
|
)
|
|
|
|
if resp.status_code != 201:
|
|
print(f'Failed to upload: {resp.text}')
|
|
subprocess.run(
|
|
[str(_here / 'notify.sh'),
|
|
f'FAILED: sunrise upload {today_date_str}',
|
|
f'Mattermost file upload failed: {resp.status_code}'],
|
|
check=False
|
|
)
|
|
raise SystemExit(1)
|
|
|
|
print(f'Uploaded {video_file}')
|
|
file_id = resp.json()['file_infos'][0]['id']
|
|
|
|
post_resp = requests.post(
|
|
f'{mattermost_url}/api/v4/posts',
|
|
headers=headers,
|
|
json={
|
|
'channel_id': channel_id,
|
|
'message': f'Sunrise {today_date_str}',
|
|
'file_ids': [file_id],
|
|
}
|
|
)
|
|
|
|
if post_resp.status_code == 201:
|
|
print('Message posted successfully.')
|
|
subprocess.run(
|
|
[str(_here / 'notify.sh'),
|
|
f'Sunrise uploaded: {today_date_str}',
|
|
'Video posted to Mattermost successfully'],
|
|
check=False
|
|
)
|
|
else:
|
|
print(f'Failed to post message: {post_resp.text}')
|
|
subprocess.run(
|
|
[str(_here / 'notify.sh'),
|
|
f'FAILED: sunrise Mattermost post {today_date_str}',
|
|
f'File uploaded but post failed: {post_resp.status_code}'],
|
|
check=False
|
|
)
|
|
raise SystemExit(1)
|
|
|
|
|
|
# ── Cleanup old unpinned posts ─────────────────────────────────────────────────
|
|
retention_days = int(config.get('MM_SUNRISE_RETENTION_DAYS', 8))
|
|
if retention_days <= 0:
|
|
raise SystemExit(0)
|
|
|
|
cutoff_ms = (datetime.now().timestamp() - retention_days * 86400) * 1000
|
|
deleted = 0
|
|
page = 0
|
|
|
|
while True:
|
|
r = requests.get(
|
|
f'{mattermost_url}/api/v4/channels/{channel_id}/posts',
|
|
headers=headers,
|
|
params={'page': page, 'per_page': 200}
|
|
)
|
|
if r.status_code != 200:
|
|
print(f'Cleanup: could not fetch posts ({r.status_code}) — skipping')
|
|
break
|
|
|
|
data = r.json()
|
|
posts = data.get('posts', {})
|
|
order = data.get('order', [])
|
|
if not order:
|
|
break
|
|
|
|
for post_id in order:
|
|
post = posts[post_id]
|
|
if post.get('is_pinned'):
|
|
continue
|
|
if not post.get('file_ids'):
|
|
continue
|
|
if post['create_at'] >= cutoff_ms:
|
|
continue
|
|
del_r = requests.delete(
|
|
f'{mattermost_url}/api/v4/posts/{post_id}',
|
|
headers=headers
|
|
)
|
|
if del_r.status_code == 200:
|
|
deleted += 1
|
|
|
|
if len(order) < 200:
|
|
break
|
|
page += 1
|
|
|
|
if deleted:
|
|
print(f'Cleanup: removed {deleted} unpinned post(s) older than {retention_days} days')
|