|
| 1 | +#!/usr/bin/python3 |
| 2 | +import datetime |
| 3 | +import json |
| 4 | + |
| 5 | +import googleapiclient.discovery |
| 6 | +from google.oauth2.credentials import Credentials |
| 7 | +from googleapiclient.http import MediaFileUpload |
| 8 | + |
| 9 | +from smtplib import SMTP_SSL |
| 10 | +from email.message import EmailMessage |
| 11 | + |
| 12 | +NOW = datetime.datetime.now(tz=datetime.timezone.utc) |
| 13 | +with open('/opt/webcast/recurring.json') as f: |
| 14 | + RECURRING = json.load(f) |
| 15 | + |
| 16 | + |
| 17 | +def convert_time(time: str): |
| 18 | + if not time: |
| 19 | + return '' |
| 20 | + |
| 21 | + return datetime.datetime.fromisoformat(time.replace('Z', '-00:00')) |
| 22 | + |
| 23 | +stream_ids = { |
| 24 | + "wardname": "STREAM_ID_FROM_GOOGLE_API" |
| 25 | +} |
| 26 | + |
| 27 | +for ward in stream_ids.keys(): |
| 28 | + creds = Credentials.from_authorized_user_file(f'/opt/webcast/auth/{ward}.json') |
| 29 | + youtube = googleapiclient.discovery.build("youtube", "v3", credentials=creds) |
| 30 | + |
| 31 | + broadcasts = youtube.liveBroadcasts().list( |
| 32 | + part="id,snippet,contentDetails,status", |
| 33 | + mine=True, |
| 34 | + maxResults=50 |
| 35 | + ).execute() |
| 36 | + |
| 37 | + for broadcast in broadcasts['items']: |
| 38 | + if not broadcast['snippet'].get('scheduledStartTime', None): |
| 39 | + youtube.liveBroadcasts().delete(id=broadcast['id']).execute() |
| 40 | + continue |
| 41 | + |
| 42 | + endTime = convert_time(broadcast['snippet'].get('scheduledEndTime', '')) |
| 43 | + if not endTime: |
| 44 | + endTime = convert_time(broadcast['snippet']['scheduledStartTime']) + datetime.timedelta(hours=1) |
| 45 | + |
| 46 | + if NOW > endTime and broadcast['status']['lifeCycleStatus'] != 'complete': |
| 47 | + stream_id = stream_ids.get(ward, None) |
| 48 | + stream = youtube.liveStreams().list( |
| 49 | + part="id,status", |
| 50 | + id=stream_id |
| 51 | + ).execute()['items'][0] |
| 52 | + if (stream['status']['streamStatus'] == 'inactive') or (NOW > endTime + datetime.timedelta(minutes=15)): |
| 53 | + youtube.liveBroadcasts().transition(part='id', id=broadcast['id'], broadcastStatus='complete').execute() |
| 54 | + print(f"Marked broadcast for {ward} complete ({broadcast['id']})") |
| 55 | + print(f"Reason: ", end='') |
| 56 | + print(f"Stream status ({stream['status']['streamStatus']})" if stream['status']['streamStatus'] == 'inactive' else "Time exceeded 15 minutes past end time") |
| 57 | + |
| 58 | + if broadcast['status']['lifeCycleStatus'] == 'complete' and datetime.datetime.now().day != endTime.day: |
| 59 | + with open('/opt/webcast/clerk_emails.json') as f: |
| 60 | + clerks = json.load(f) |
| 61 | + viewers = round(int(youtube.videos().list(part='statistics', id=broadcast['id']).execute()['items'][0]['statistics']['viewCount']) * 2.1) |
| 62 | + msg = EmailMessage() |
| 63 | + msg["Subject"] = "Online YouTube Meeting Attendance" |
| 64 | + msg[ "From"] = "Webcast <[email protected]>" |
| 65 | + msg["To"] = clerks[ward] |
| 66 | + msg.set_content(f"The number of individuals watching {broadcast['snippet']['title']} online on {convert_time(broadcast['snippet']['scheduledStartTime']).strftime('%d %b %Y')} was {viewers}.\n\n\nThis is an automated message. Please do not respond.") |
| 67 | + with SMTP_SSL('SMTP_SERVER_HERE', 465) as smtp: |
| 68 | + smtp. login( '[email protected]', 'ITS_PASSWORD') |
| 69 | + smtp.send_message(msg) |
| 70 | + smtp.quit() |
| 71 | + youtube.liveBroadcasts().delete(id=broadcast['id']).execute() |
| 72 | + print(f"Deleted broadcast on {ward} ({broadcast['id']})") |
| 73 | + |
| 74 | + if broadcast['contentDetails'].get("boundStreamId", "") != stream_ids.get(ward, "NOT FOUND"): |
| 75 | + youtube.liveBroadcasts().bind( |
| 76 | + id=broadcast['id'], |
| 77 | + part="id", |
| 78 | + streamId=stream_ids.get(ward, '') |
| 79 | + ).execute() |
| 80 | + print(f"Attempted to bind stream to broadcast for {ward}") |
| 81 | + |
| 82 | + for event in RECURRING[ward]: |
| 83 | + changeInDays = event['day'] - datetime.datetime.now().weekday() |
| 84 | + if changeInDays < 0: |
| 85 | + changeInDays += 7 |
| 86 | + recurringDate = datetime.datetime.now() + datetime.timedelta(days=changeInDays) |
| 87 | + startTime = datetime.datetime.combine(recurringDate, datetime.time(*[int(x) for x in event['start'].split(':')])) |
| 88 | + endTime = datetime.datetime.combine(recurringDate, datetime.time(*[int(x) for x in event['end'].split(':')])) |
| 89 | + startTime = startTime.replace(tzinfo=NOW.astimezone().tzinfo) |
| 90 | + endTime = endTime.replace(tzinfo=NOW.astimezone().tzinfo) |
| 91 | + |
| 92 | + if startTime not in [convert_time(x['snippet']['scheduledStartTime']) for x in broadcasts['items']] and startTime > NOW: |
| 93 | + new = youtube.liveBroadcasts().insert( |
| 94 | + part='id,snippet,contentDetails,status', |
| 95 | + body={ |
| 96 | + 'snippet': { |
| 97 | + 'title': event['title'], |
| 98 | + 'scheduledStartTime': startTime.isoformat(), |
| 99 | + 'scheduledEndTime': endTime.isoformat(), |
| 100 | + }, |
| 101 | + 'contentDetails': { |
| 102 | + 'enableAutoStart': 'true', |
| 103 | + }, |
| 104 | + 'status': { |
| 105 | + 'privacyStatus': 'public', |
| 106 | + 'selfDeclaredMadeForKids': 'true', |
| 107 | + }, |
| 108 | + } |
| 109 | + ).execute() |
| 110 | + youtube.liveBroadcasts().bind( |
| 111 | + id=new['id'], |
| 112 | + part="id", |
| 113 | + streamId=stream_ids.get(ward, '') |
| 114 | + ).execute() |
| 115 | + |
0 commit comments