job "teamsstatus" { group "app" { task "teamsstatus" { driver = "docker" config { image = "python:3.11-slim" command = "/local/start.sh" } # Template for the startup script template { data = <&1 EOF destination = "local/start.sh" perms = "755" } # Template for the token cache template { data = "{{ with nomadVar \"nomad/jobs/teamsstatus\" }}{{ .token_cache_json }}{{ end }}" destination = "local/token_cache.json" } # Template for the Python script template { data = <", "contentType": "text", } }, "expirationDateTime": { "dateTime": expiration_date_time, "timeZone": time_zone }, } headers = { 'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json' } logging.debug(f"Setting status message for user {user_id}") response = requests.post(url, json=payload, headers=headers) if response.status_code == 200: logging.info(f"Teams status message set to: {status_message}") return True else: logging.error(f"Failed to set Teams status message: {response.status_code}") return False def _load_segments(): """Load the journey segments from embedded data into memory""" global _segments if _segments: # Already loaded return aest = timezone(timedelta(hours=10)) for line in JOURNEY_DATA.split('\n')[1:]: # Skip header day, start_time, end_time, start_dist, end_dist, start_loc, end_loc = line.strip().split('\t') # Convert day and times to datetime in AEST day_start = datetime.strptime(f"{day} {start_time}", "%d/%m/%Y %H:%M:%S").replace(tzinfo=aest) day_end = datetime.strptime(f"{day} {end_time}", "%d/%m/%Y %H:%M:%S").replace(tzinfo=aest) # Extract the numeric distance values start_dist = int(start_dist.rstrip('km')) end_dist = int(end_dist.rstrip('km')) _segments.append({ 'start_time': day_start, 'end_time': day_end, 'start_dist': start_dist, 'end_dist': end_dist, 'start_location': start_loc, 'end_location': end_loc }) def get_trip_info(target_datetime): """Determine the distance travelled and locations for the current datetime.""" if target_datetime.tzinfo is None: raise ValueError("target_datetime must be timezone-aware") # Ensure data is loaded _load_segments() # Before journey starts if not _segments or target_datetime < _segments[0]['start_time']: start_loc = end_loc = _segments[0]['start_location'] return (0, start_loc, end_loc) # During journey for i, segment in enumerate(_segments): # If target is before this segment starts if target_datetime < segment['start_time']: prev_segment = _segments[i-1] return (prev_segment['end_dist'], prev_segment['end_location'], prev_segment['end_location']) # If target is during this segment, interpolate if segment['start_time'] <= target_datetime <= segment['end_time']: # Calculate what fraction of the segment has elapsed total_seconds = (segment['end_time'] - segment['start_time']).total_seconds() elapsed_seconds = (target_datetime - segment['start_time']).total_seconds() fraction = elapsed_seconds / total_seconds # Interpolate the distance distance_delta = segment['end_dist'] - segment['start_dist'] current_dist = segment['start_dist'] + int(distance_delta * fraction) return (current_dist, segment['start_location'], segment['end_location']) # Between segments if i < len(_segments) - 1: next_segment = _segments[i + 1] if segment['end_time'] < target_datetime < next_segment['start_time']: return (segment['end_dist'], segment['end_location'], segment['end_location']) # After journey ends return (_segments[-1]['end_dist'], _segments[-1]['end_location'], _segments[-1]['end_location']) def build_message(distance, start_loc, end_loc): """Build the status message based on distance and locations""" message = "On leave" if distance > 13144: message += f", driving my EV back from WA" if distance > 2118: message += f", driving my EV around WA" elif distance > 0: message += f", driving my EV to WA" if distance > 0: distance += random.randint(-5, 5) message += f", {distance}kms travelled so far" if start_loc != end_loc: message += f", next stop {end_loc}" else: message += f", near {start_loc}" message += ", returning July 21st. Contacts {CIM: Grant Gorfine, Inserts: Daniel Pate, DevOps: Rob Duncan, else: Andrian Zubovic}" return message def main(): test_mode = False # Set to True to run in test mode time_scale = 1 # 1/600 # Set to 1/60 to run at 1 second per minute, 1 for normal speed # Set start time to 7:30 AM AEST (UTC+10) on June 8th, 2025 aest = timezone(timedelta(hours=10)) start_time = datetime.now(aest) date_offset = datetime(2025, 6, 8, 7, 30, 0, tzinfo=aest) - start_time if test_mode: logging.info("Running in test mode - status messages will not actually be set") app = get_msal_app(client_id = "e6cda941-949f-495e-88f5-10eb45ffa0e7") last_token_refresh = 0 # Token refresh interval (60 minutes in seconds) TOKEN_REFRESH_INTERVAL = int(60 * 60) # Scale the 1 hour refresh interval old_distance = -1 while True: try: # Check if we need to refresh the token current_time = time.time() if current_time - last_token_refresh >= TOKEN_REFRESH_INTERVAL or last_token_refresh == 0: logging.info("Acquiring/refreshing access token...") access_token = acquire_token(app, scope = ["https://graph.microsoft.com/Presence.ReadWrite"]) if not access_token: logging.error("Failed to acquire token") exit(1) last_token_refresh = current_time logging.info("Token successfully refreshed") # Set the status message now = datetime.now(aest) # Get current time in AEST if time_scale != 1: # Adjust the current time based on the time scale now = start_time + (now - start_time) / time_scale now += date_offset # Adjust to the target start time distance, start_loc, end_loc = get_trip_info(now) # We only need distance for comparison if distance != old_distance: message = build_message(distance, start_loc, end_loc) timestamp = now.strftime("%Y-%m-%d %H:%M:%S %Z") if not test_mode: logging.info(f"[{timestamp}] Message: {message}") success = set_teams_status_message( access_token = access_token, user_id = "1b625872-d8a8-42f4-b237-dfa6d8062360", status_message = message, ) else: logging.info(f"[TEST MODE] [{timestamp}] Message: {message}") success = True else: logging.debug("Status message has not changed, skipping update") success = True old_distance = distance if success: wait_time = 900 * time_scale # Scale the 15 minute wait time logging.debug(f"Waiting {wait_time} seconds before updating status message again...") time.sleep(wait_time) else: last_token_refresh = 0 # Reset token refresh time on failure except KeyboardInterrupt: logging.info("Status update interrupted by user. Exiting...") break except Exception as e: logging.error(f"An error occurred: {e}") time.sleep(300) # Wait 5 minutes before retrying return 0 if __name__ == "__main__": exit(main()) EOF destination = "local/teamsstatus_standalone.py" } resources { cpu = 500 memory = 256 } } restart { attempts = 3 interval = "5m" delay = "15s" mode = "fail" } } }