import os import sys from typing import Any, Dict, List import requests BASE_URL = os.getenv('SERVICEM8_BASE_URL', 'https://api.servicem8.com') ACCESS_TOKEN = os.getenv('SERVICEM8_ACCESS_TOKEN', 'smk-ac525b-99c4b96305a49c7c-fe4dd3e705b647ea') STATUS = os.getenv('SERVICEM8_WEBHOOK_STATUS', 'all') ENDPOINT = '/webhook_subscriptions' def normalize(item: Dict[str, Any]) -> Dict[str, Any]: sub_type = item.get('type', '-') target = item.get('event') if sub_type == 'event' else item.get('object', '-') fields = ','.join(item.get('fields', [])) if sub_type == 'object' else '-' active = bool(item.get('active', False)) failure_reason = item.get('last_failure_reason') or '' failure_at = item.get('last_failure_at') or '' callback_url = item.get('callback_url', '-') unique_id = item.get('unique_id') or '-' status_label = 'ACTIVE' if active else 'INACTIVE' issue = 'FAIL' if failure_reason else '' sort_bucket = 0 if (not active or failure_reason) else 1 return { 'status': status_label, 'issue': issue, 'type': sub_type, 'target': target or '-', 'fields': fields or '-', 'unique_id': unique_id, 'callback_url': callback_url, 'failure_at': failure_at or '-', 'failure_reason': failure_reason or '-', '_sort_bucket': sort_bucket, } def truncate(value: str, width: int) -> str: value = str(value) return value if len(value) <= width else value[: width - 3] + '...' def render_table(rows: List[Dict[str, Any]]) -> None: columns = [ ('status', 'STATUS', 9), ('issue', 'ISSUE', 5), ('type', 'TYPE', 8), ('target', 'TARGET', 24), ('fields', 'FIELDS', 24), ('unique_id', 'UNIQUE_ID', 18), ('failure_at', 'FAILURE_AT', 19), ('callback_url', 'CALLBACK_URL', 42), ] header = ' | '.join(title.ljust(width) for _, title, width in columns) divider = '-+-'.join('-' * width for _, _, width in columns) print(header) print(divider) for row in rows: line = ' | '.join(truncate(row[key], width).ljust(width) for key, _, width in columns) print(line) def main() -> None: if STATUS not in {'active', 'inactive', 'all'}: print('SERVICEM8_WEBHOOK_STATUS must be one of: active, inactive, all') sys.exit(1) url = f'{BASE_URL}{ENDPOINT}' headers = { 'X-API-Key': f'{ACCESS_TOKEN}', 'Accept': 'application/json', } params = {'status': STATUS} response = requests.get(url, headers=headers, params=params, timeout=30) print(f'HTTP {response.status_code}') try: data = response.json() except ValueError: print('Response was not valid JSON:') print(response.text) sys.exit(1) if not response.ok: print(data) sys.exit(1) rows = [normalize(item) for item in data] rows.sort(key=lambda r: (r['_sort_bucket'], r['status'], r['type'], r['target'])) print(f'Subscriptions returned: {len(rows)}') print(f'Filter: {STATUS}') print() if not rows: print('No webhook subscriptions found.') return render_table(rows) failures = [r for r in rows if r['failure_reason'] != '-'] if failures: print('\nFailure details:') for row in failures: print(f"- {row['type']} {row['target']} -> {row['failure_reason']} (at {row['failure_at']})") if __name__ == '__main__': main()