#!/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() v = re.sub(r'\s+#.*$', '', v) # strip inline comments v = v.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'{sunrise_cam}-{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 # Only delete posts this script created — message starts with "Sunrise YYYY-MM-DD" if not re.match(r'^Sunrise \d{4}-\d{2}-\d{2}', post.get('message', '')): 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')