Advertisement
kayvansylvan

zoom-manage

Dec 6th, 2023
137
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/osascript
  2.  
  3. (*
  4.  Copyright (c) 2023 Kayvan A. Sylvan
  5.  
  6.  Permission is hereby granted, free of charge, to any person obtaining a copy of
  7.  this software and associated documentation files (the "Software"), to deal in
  8.  the Software without restriction, including without limitation the rights to
  9.  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  10.  the Software, and to permit persons to whom the Software is furnished to do so,
  11.  subject to the following conditions:
  12.  
  13.  The above copyright notice and this permission notice shall be included in all
  14.  copies or substantial portions of the Software.
  15.  
  16.  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17.  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  18.  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  19.  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  20.  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  21.  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22.  *)
  23.  
  24. -- Global properties
  25. property scriptName : "Zoom Manage"
  26. property topLevelDirectory : missing value
  27.  
  28. -- Zoom Related properties
  29. property appName : "zoom.us"
  30. property appVersion : missing value
  31. property topZoomWindow : "Zoom"
  32. property meetingWindow : "Zoom Meeting"
  33. property sharingWindow : "zoom share statusbar window"
  34. property participantWindow : missing value
  35.  
  36. -- These three files are created in the logs/ subdirectory.
  37. -- The pattern of filenames: logs/20231014-log.txt
  38. -- This makes it easy to store and index the logs at a later time.
  39. -- roster.txt is the running log file capturing the roster information.
  40. -- log.txt is the overall log file.
  41. -- filtered.txt is the filtered log file (capturing "Hands Raised", etc.)
  42. property logFile : "log.txt"
  43. property meetingRoster : "roster.txt"
  44. property filteredRoster : "filtered.txt"
  45.  
  46. -- The FastAPI server should be running at this URL
  47. property trackerURL : "http://localhost:5000"
  48. property batchCount : 50 -- can be set via ZOOM_MANAGE_BATCH_SIZE environment variable
  49.  
  50. -- Top level commands the script understands
  51. property knownCommands : {  "help", "reset", "roster", "hands", "camera_off", "admit", "server", "dashboard",   "breakout", "camera_off", "camera_on", "phone", "no_audio", "muted", "unmuted",     "rename", "co-host"}
  52.  
  53. -- zoomRosterDebug uses the ZOOM_DEBUG environment variable.
  54. -- If it is set to true, filtered rosters (like "hands") will be
  55. -- output to the console (stderr) in addition to going in the log.
  56. property zoomRosterDebug : missing value
  57.  
  58. -- renameParticipantsFile is a mapping of users to rename upon entry to the meeting.
  59. -- If set, we use it in the "roster" command to check for and rename
  60. -- participants. It references the ZOOM_RENAME_FILE environment
  61. property renameParticipantsFile : missing value
  62. property renameMappings : missing value -- the mappings found in the above file
  63.  
  64. -- Path to cliclick. See https://github.com/BlueM/cliclick
  65. property cliclick : "/opt/homebrew/bin/cliclick"
  66.  
  67. property environmentDocs:   "Environment Varibles:" & linefeed  & "    - ZOOM_DEBUG: Set this to any value to see rosters and filtered lists output on the console." & linefeed     & "    - ZOOM_RENAME_FILE: Path to a file of participant rename mappings. See rename/README.md file for details." & linefeed
  68.  
  69. on usageMessage(section)
  70.     set tid to AppleScript's text item delimiters
  71.     set AppleScript's text item delimiters to ":"
  72.     set path_parts to every text item of ((path to me) as string)
  73.     set AppleScript's text item delimiters to tid
  74.     if section is "top" then
  75.         return linefeed & "Usage: " & (item -1 of path_parts) & " [command]" & linefeed             & "    help - show usage message." & linefeed & linefeed            & "    server - run the backend server. Starts the server in its own terminal." & linefeed          & "    dashboard - open the Zoom Meeting Tracker dashboard." & linefeed             & "    reset - reset the tracking database." & linefeed & linefeed          & "    roster (default action) - get current roster." & linefeed            & linefeed          & "    hands - get the current hands raised." & linefeed            & "    camera_off - get the list of camera off participants." & linefeed            & "    camera_on - get the list of participants who are on video." & linefeed           & "    no_audio - get the list of participants not connected to audio." & linefeed          & "    muted - get the list of participants who are muted." & linefeed          & "    unmuted - get the list of participants who are unmuted." & linefeed          & "    phone - get the list of participants dialing in by phone." & linefeed            & linefeed          & "    admit - admit everyone in the Waiting Room." & linefeed          & "    breakout - create a set of named breakout rooms." & linefeed             & linefeed          & "    rename - rename a participant." & linefeed           & "    co-host - make a participant a co-host." & linefeed          & linefeed & environmentDocs
  76.     else if section is "breakout" then
  77.         return linefeed & "Usage: " & (item -1 of path_parts) & " breakout [create | get | help] filename" & linefeed           & "    help - print the breakout related help message." & linefeed          & "    create - Opens up the named file and creates the rooms named there." & linefeed          & "    get - Get the named rooms and write them into filename." & linefeed
  78.     else if section is "rename" then
  79.         return linefeed & "Usage: " & (item -1 of path_parts) & " rename 'Original Name' 'New Name'" & linefeed
  80.     end if
  81. end usageMessage
  82.  
  83. on todayYMD()
  84.     -- Return the current date in the format YYYYMMDD.
  85.     set [_day, _month, _year] to [day, month, year] of (current date)
  86.     set _month to _month * 1 --> 3
  87.     set _month to text -1 thru -2 of ("0" & _month) --> "03"
  88.     set _day to text -1 thru -2 of ("0" & _day)
  89.     set result to _year & _month & _day
  90.     return result
  91. end todayYMD
  92.  
  93. on setUpLogFiles()
  94.     -- setUpLogFiles handler:
  95.     -- Creates a folder called "logs" in the same directory as the program if it does not already exist.
  96.     -- It then creates two files, logFile and meetingRoster, in the logs folder, with a prefix of the current date.
  97.     tell application "Finder"
  98.         set _currentDirectory to container of (path to me)
  99.         if not (exists folder "logs" of _currentDirectory) then
  100.             make new folder at _currentDirectory with properties {name:"logs"}
  101.         end if
  102.     end tell
  103.     set _currentDirectory to POSIX path of (_currentDirectory as text)
  104.     set _prefix to my todayYMD()
  105.     set topLevelDirectory to _currentDirectory
  106.     set logFile to POSIX file (topLevelDirectory & "logs/" & _prefix & "-" & logFile)
  107.     set meetingRoster to POSIX file (topLevelDirectory & "logs/" & _prefix & "-" & meetingRoster)
  108.     set filteredRoster to POSIX file (topLevelDirectory & "logs/" & _prefix & "-" & filteredRoster)
  109.     my setupEnvVariables()
  110. end setUpLogFiles
  111.  
  112. on setupEnvVariables()
  113.     -- zoomRosterDebug is false if ZOOM_DEBUG is not set
  114.     set zoomRosterDebug to do shell script "echo ${ZOOM_DEBUG:-unset}"
  115.     set zoomRosterDebug to (zoomRosterDebug is not "unset") -- set to false or true
  116.     -- batchCount is set to value of ZOOM_MANAGER_BATCH_SIZE (50 if not set)
  117.     set batchCount to do shell script "echo ${ZOOM_MANAGE_BATCH_SIZE:-" & batchCount & "}"
  118.     -- renameParticipantsFile points to a mapping file of old to new names with "->" in between them.
  119.     set renameParticipantsFile to do shell script "echo ${ZOOM_RENAME_FILE:-}"
  120.     if renameParticipantsFile is "" then set renameParticipantsFile to missing value
  121. end setupEnvVariables
  122.  
  123. on filterFileContents(fileLines)
  124.     -- Given a set of fileLines (usually gathered from "paragraphs of fileContents" idiom)
  125.     -- Return a new list, ignoring all empty lines and lines starting with "#"
  126.     set _filtered to {}
  127.     repeat with _item in fileLines
  128.         set _s to _item as string
  129.         if _s is not "" and _s does not start with "#" then
  130.             if _s starts with "\\" then set _s to text 2 through -1 of _s
  131.             set end of _filtered to _s
  132.         end if
  133.     end repeat
  134.     return _filtered
  135. end filterFileContents
  136.  
  137. on splitText(theText, theDelimiter)
  138.     set oldDelimiters to AppleScript's text item delimiters
  139.     set AppleScript's text item delimiters to theDelimiter
  140.     set textItems to every text item of theText
  141.     set AppleScript's text item delimiters to oldDelimiters -- Restore the original delimiters
  142.     return textItems
  143. end splitText
  144.  
  145. on loadRenameMappings()
  146.     if renameParticipantsFile is missing value then return
  147.     if not renameParticipantsFile starts with "/" then
  148.         set renameParticipantsFile to topLevelDirectory & renameParticipantsFile
  149.     end if
  150.     tell application "System Events"
  151.         if (not (exists file renameParticipantsFile)) then
  152.             error ("Missing ZOOM_RENAME_FILE file: " & renameParticipantsFile) number -1
  153.         end if
  154.         set _size to size of (info for POSIX file renameParticipantsFile)
  155.         if _size is 0 then
  156.             error ("Empty ZOOM_RENAME_FILE file: " & renameParticipantsFile) number -2
  157.         end if
  158.     end tell
  159.     set _rFile to POSIX file renameParticipantsFile
  160.     set _file to open for access file _rFile
  161.     set fileContents to read _file
  162.     close access _rFile
  163.  
  164.     set mappings to my filterFileContents(paragraphs of fileContents)
  165.     set renameMappings to {}
  166.     repeat with _item in mappings
  167.         set end of renameMappings to my splitText(_item, "->")
  168.     end repeat
  169. end loadRenameMappings
  170.  
  171. on lookupRenaming(oldName)
  172.     if renameMappings is missing value then return
  173.     repeat with _i in renameMappings
  174.         if item 1 of _i as text is oldName as text then return _i
  175.     end repeat
  176.     return missing value
  177. end lookupRenaming
  178.  
  179. on formatDateTime(theDateTime)
  180.     (*
  181.     This code takes a date and time as input and formats it into a string in the form of MM/DD/YYYY HH:MM:SS.
  182.     It does this by extracting the day, month, year, hours, minutes, and seconds from the input and
  183.     adding a leading zero if necessary.
  184.     *)
  185.     set [_day, _month, _year, _hours, _minutes, _seconds] to [day, month, year, hours, minutes, seconds] of theDateTime
  186.     set _month to _month * 1 --> 3
  187.     set _month to text -1 thru -2 of ("0" & _month) --> "03"
  188.     set _day to text -1 thru -2 of ("0" & _day)
  189.     set _hours to text -1 thru -2 of ("0" & _hours) --> "05"
  190.     set _minutes to text -1 thru -2 of ("0" & _minutes)
  191.     set _seconds to text -1 thru -2 of ("0" & _seconds)
  192.     set result to _month & "/" & _day & "/" & _year & " " & _hours & ":" & _minutes & ":" & _seconds
  193.     return result
  194. end formatDateTime
  195.  
  196. on appendToFile(theFile, message, logToConsole)
  197.     -- Write the message to theFile. logToConsole is a boolean indicating whether to log to console.
  198.     write message & linefeed to theFile starting at eof
  199.     if logToConsole then log message
  200. end appendToFile
  201.  
  202. on logMessage(message, filePath)
  203.     -- Usage:
  204.     -- set logFilePath to (path to desktop as string) & "logfile.txt" -- Path to the log file
  205.     -- my logMessage("This is a log message.", logFilePath)
  206.     try
  207.         -- Open the log file for writing, creating it if it doesn't exist
  208.         set _log to open for access file filePath with write permission
  209.        
  210.         -- Construct the log entry
  211.         set logEntry to (my formatDateTime(current date)) & " " & message
  212.        
  213.         -- Write the log entry to the file
  214.         my appendToFile(_log, logEntry, false)
  215.        
  216.         -- Close the log file
  217.         close access _log
  218.        
  219.     on error errMsg number errNum
  220.         -- If an error occurs, close the log file (if it's open)
  221.         try
  222.             close access _log
  223.         end try
  224.         -- Optionally, report the error in some way
  225.         set _msg to "Error: " & errMsg & " (" & errNum & ")"
  226.         log _msg
  227.         display dialog _msg
  228.     end try
  229. end logMessage
  230.  
  231. on checkZoomRunning()
  232.     -- This code checks if an application (appName) is running and if it is,
  233.     -- it sets the variable appVersion to the version of the application.
  234.     set _isRunning to false
  235.     tell application "System Events"
  236.         set _isRunning to (name of every process) contains appName
  237.     end tell
  238.     if not _isRunning then
  239.         tell application appName to activate
  240.         delay 1.0
  241.     end if
  242.     set appVersion to version of application appName
  243. end checkZoomRunning
  244.  
  245. on findStatusMenu(nameSubstring)
  246.     -- Find a status menu of Zoom that contains nameSubstring
  247.     set _ret to missing value
  248.     tell application "System Events" to tell process appName
  249.         set menuItems to every menu item of menu 1 of menu bar 2
  250.         repeat with m in menuItems
  251.             if (exists name of m) and name of m contains nameSubstring then
  252.                 set _ret to m
  253.                 exit repeat
  254.             end if
  255.         end repeat
  256.     end tell
  257.     return _ret
  258. end findStatusMenu
  259.  
  260. on clickStatusMenu(nameSubstring)
  261.     -- Click a menu item in status menu. Returns true if done, false otherwise.
  262.     tell application "System Events" to tell process appName
  263.         set m to my findStatusMenu(nameSubstring)
  264.         if m is not missing value then
  265.             click m
  266.             delay 1
  267.         end if
  268.     end tell
  269.     return (m is not missing value)
  270. end clickStatusMenu
  271.  
  272. on enableParticipantsWindow()
  273.     (*
  274.     This code is used to click the "Manage Participants" menu item of the status menu bar.
  275.     A delay of 1 second is added after the click.
  276.     *)
  277.     my clickStatusMenu("Participants")
  278.     my logMessage("Participants panel started.", logFile)
  279. end enableParticipantsWindow
  280.  
  281. on findSubWindow(nameOfApp, subWinName)
  282.     -- Look for a sub-window of nameOfApp whose name contains subWinName
  283.     tell application "System Events" to tell process nameOfApp
  284.         set _wins to windows
  285.         set _ret to missing value
  286.         repeat with w in _wins
  287.             if name of w starts with subWinName then
  288.                 set _ret to w
  289.                 exit repeat
  290.             end if
  291.         end repeat
  292.     end tell
  293.     return _ret
  294. end findSubWindow
  295.  
  296. on findParticipantsWindow()
  297.     tell application "System Events" to tell process appName
  298.         if exists scroll area 1 of window meetingWindow then
  299.             set participantWindow to window meetingWindow -- Participants panel.
  300.         else
  301.             set participantWindow to my findSubWindow(appName, "Participants")
  302.         end if
  303.     end tell
  304. end findParticipantsWindow
  305.  
  306. on startParticipantWindow()
  307.     tell application "System Events" to tell process appName
  308.         my findParticipantsWindow()
  309.         if participantWindow is missing value then
  310.             my enableParticipantsWindow() -- participant window started
  311.         end if
  312.         my findParticipantsWindow()
  313.     end tell
  314. end startParticipantWindow
  315.  
  316. on checkZoomMeetingRunning()
  317.     repeat
  318.         -- Make sure we are in a Zoom meeting
  319.         tell application "System Events" to tell process appName
  320.             set _startMeetingMenu to my findStatusMenu("Join a Meeting")
  321.             if _startMeetingMenu is not missing value then
  322.                 display dialog "The " & scriptName & " application needs a Zoom Meeting." & linefeed & "Please join the Zoom Meeting and press OK."
  323.             else
  324.                 exit repeat
  325.             end if
  326.         end tell
  327.     end repeat
  328. end checkZoomMeetingRunning
  329.  
  330. on appLogMessage(message)
  331.     my logMessage("=== " & scriptName & ": " & message, logFile)
  332. end appLogMessage
  333.  
  334. on resetTrackigData()
  335.     do shell script "curl -X POST " & trackerURL & "/reset"
  336. end resetTrackigData
  337.  
  338. on replaceText(theText, searchStr, replacementStr)
  339.     set theText to theText as text
  340.     if theText does not contain searchStr then
  341.         return theText -- return the original string if it doesn't contain the search string
  342.     end if
  343.     set oldDelims to AppleScript's text item delimiters -- save old delimiters
  344.     set AppleScript's text item delimiters to the searchStr
  345.     set theTextItems to every text item of theText
  346.     set AppleScript's text item delimiters to the replacementStr
  347.     set theText to theTextItems as string
  348.     set AppleScript's text item delimiters to oldDelims -- restore old delimiters
  349.     return theText
  350. end replaceText
  351.  
  352. on trackList(_nameList, apiEndpoint)
  353.     if (count _nameList) is 0 then
  354.         return
  355.     end if
  356.     set _sanitized to {}
  357.     repeat with _p in _nameList
  358.         set _p to my replaceText(_p, "'", "-") -- Hack for people with single-quotes in their name
  359.         set _sanitized to _sanitized & {_p}
  360.     end repeat
  361.     set cmd to "curl -X PUT -H 'Content-Type: application/json' -d ' ["
  362.     set qList to {}
  363.     repeat with n in _sanitized
  364.         set qList to qList & {"\"" & n & "\""}
  365.     end repeat
  366.     set _tid to AppleScript's text item delimiters
  367.     set AppleScript's text item delimiters to ","
  368.     set cmd to cmd & (qList as string) & "]' " & trackerURL & apiEndpoint
  369.     set AppleScript's text item delimiters to _tid
  370.     do shell script cmd
  371.     -- log cmd
  372. end trackList
  373.  
  374. on trackListBatched(_nameList, apiEndpoint)
  375.     set _nameCount to count _nameList
  376.     repeat with i from 1 to _nameCount by batchCount
  377.         set endIndex to i + batchCount - 1
  378.         if endIndex > _nameCount then set endIndex to _nameCount
  379.         my trackList(items i thru endIndex of _nameList, apiEndpoint)
  380.     end repeat
  381. end trackListBatched
  382.  
  383. on trackJoined(_nameList)
  384.     my trackListBatched(_nameList, "/joined_list")
  385. end trackJoined
  386.  
  387. on trackWaiting(_nameList)
  388.     my trackListBatched(_nameList, "/waiting_list")
  389. end trackWaiting
  390.  
  391. on runBackendServer()
  392.     -- check if the server is already running
  393.     set serverRunning to false
  394.     try
  395.         set jsonOutput to do shell script "curl -X GET " & trackerURL & "/health -H 'accept: application/json'"
  396.         if jsonOutput starts with "{" then set serverRunning to true
  397.     on error errMessage number errNum
  398.         error errMessage number errNum
  399.     end try
  400.     if serverRunning then
  401.         error "Server is already running. " & jsonOutput number -1
  402.     end if
  403.     set titleString to "\\033]0;" & scriptName & " Backend Server\\007"
  404.     set cmd to "echo -n -e \"" & titleString & "\";cd " & topLevelDirectory & "backend; ./server.py; exit"
  405.     do shell script "open -a Terminal " & topLevelDirectory
  406.     tell application "Terminal"
  407.         activate
  408.         do script cmd in window 1
  409.     end tell
  410. end runBackendServer
  411.  
  412. on openDashboard()
  413.     do shell script "open " & topLevelDirectory & "frontend/index.html"
  414. end openDashboard
  415.  
  416. on writeToRoster(message, filePath)
  417.     -- Usage:
  418.     -- set logFilePath to (path to desktop as string) & "roster.txt" -- Path to the roster file
  419.     -- my writeTeRoster("John Smith", logFilePath)
  420.     try
  421.         -- Open the log file for writing, creating it if it doesn't exist
  422.         set _log to open for access file filePath with write permission
  423.         my appendToFile(_log, message, zoomRosterDebug)
  424.         -- Close the log file
  425.         close access _log
  426.     on error errMsg number errNum
  427.         -- If an error occurs, close the log file (if it's open)
  428.         try
  429.             close access _log
  430.         end try
  431.         -- Optionally, report the error in some way
  432.         set _msg to "Error: " & errMsg & " (" & errNum & ")"
  433.         log _msg
  434.         display dialog _msg
  435.     end try
  436. end writeToRoster
  437.  
  438. on writeToRosterPart(namesList, spacesPrefix)
  439.     -- Write a list of names to the roster.txt file
  440.     -- prepend each with the spacesPrefix
  441.     if (count namesList) is 0 then return
  442.     set i to 1
  443.     repeat with n in namesList
  444.         my writeToRoster(spacesPrefix & i & ". " & n, meetingRoster)
  445.         set i to i + 1
  446.     end repeat
  447. end writeToRosterPart
  448.  
  449. on generateRoster()
  450.     set _joinedList to {}
  451.     set _waitingList to {}
  452.     set _joinedPrefix to "Joined "
  453.     set _waitingPrefix to "Waiting Room "
  454.     set _notJoinedPrefix to "Not Joined"
  455.     my loadRenameMappings()
  456.     tell application "System Events" to tell process appName
  457.         tell outline 1 of scroll area 1 of participantWindow
  458.             set allParticipantNames to get value of static text of UI element of rows
  459.             set _num to (count allParticipantNames)
  460.             set _inWaitingList to false
  461.             set _inJoinedList to false
  462.             repeat with i from 1 to _num
  463.                 set _pName to item i of allParticipantNames as string
  464.                 if _pName starts with _waitingPrefix then
  465.                     set _inWaitingList to true
  466.                 end if
  467.                 if _pName starts with _joinedPrefix then
  468.                     set _inWaitingList to false
  469.                     set _inJoinedList to true
  470.                 end if
  471.                 if _pName starts with _notJoinedPrefix then exit repeat
  472.                 if (i is 1) and (not _inWaitingList) and (not _inJoinedList) then
  473.                     set _joinedList to allParticipantNames
  474.                     exit repeat
  475.                 end if
  476.                 if _inWaitingList then
  477.                     if not (_pName starts with _waitingPrefix) then
  478.                         set _waitingList to _waitingList & {_pName}
  479.                     end if
  480.                 else
  481.                     if not (_pName starts with _joinedPrefix) then
  482.                         set _joinedList to _joinedList & {_pName}
  483.                     end if
  484.                 end if
  485.             end repeat
  486.         end tell
  487.     end tell
  488.  
  489.     set _intro to "=== " & (my formatDateTime(current date)) & " ==="
  490.     my writeToRoster(_intro, meetingRoster)
  491.  
  492.     if (count _waitingList) > 0 then
  493.         my writeToRoster("Waiting Room:", meetingRoster)
  494.         my writeToRosterPart(_waitingList, "  ")
  495.         my writeToRoster("Joined:", meetingRoster)
  496.         my writeToRosterPart(_joinedList, "  ")
  497.     else
  498.         my writeToRosterPart(_joinedList, "")
  499.     end if
  500.  
  501.     set _num to (count _waitingList) + (count _joinedList)
  502.     if _num > 1 then
  503.         set plural to "s"
  504.     else
  505.         set plural to ""
  506.     end if
  507.     set _summary to "=== " & (_num as string) & " participant" & plural         & " " & (my formatDateTime(current date)) & " ==="
  508.     my writeToRoster(_summary, meetingRoster)
  509.  
  510.     my trackWaiting(_waitingList)
  511.     my trackJoined(_joinedList)
  512.  
  513.     -- Now, run our rename procedure if needed.
  514.     if renameMappings is missing value then return
  515.     set _renameToDo to {}
  516.     set _candidates to {}
  517.     repeat with _i in renameMappings
  518.         set end of _candidates to item 1 of _i as text
  519.     end repeat
  520.     repeat with participant in _joinedList
  521.         if participant as text is in _candidates then set end of _renameToDo to participant as text
  522.     end repeat
  523.     -- Now we go through each participant in _renameToDo and finish setting up the renaming.
  524.     repeat with _name in _renameToDo
  525.         set _res to my lookupRenaming(_name)
  526.         if _res is not missing value then
  527.             my renameParticipant(item 1 of _res, item 2 of _res, false)
  528.         end if
  529.         delay 0.4
  530.     end repeat
  531. end generateRoster
  532.  
  533. on joinStringList(_strings)
  534.     set _tid to AppleScript's text item delimiters
  535.     set AppleScript's text item delimiters to ":"
  536.     set _ret to _strings as string
  537.     set AppleScript's text item delimiters to _tid
  538.     return _ret
  539. end joinStringList
  540.  
  541. on generateFilteredRoster(filterString)
  542.     -- Look for filter strings in the description strings in participants window
  543.     set _intro to "=== " & filterString & " " & (my formatDateTime(current date)) & " ==="
  544.     my writeToRoster(_intro, filteredRoster)
  545.     set _num to 0
  546.     tell application "System Events" to tell process appName
  547.         tell outline 1 of scroll area 1 of participantWindow
  548.             set allParticipantNames to get value of static text of UI element of rows
  549.             set numPart to (count allParticipantNames)
  550.             set allRows to rows
  551.             repeat with i from 1 to numPart
  552.                 set pName to item i of allParticipantNames as string
  553.                 set aRow to item i of allRows
  554.                 set allElem to UI elements of (item 1 of UI element of aRow)
  555.                 set allDescriptions to description of UI elements of (item 1 of UI element of aRow)
  556.                 set joinedDescription to my joinStringList(allDescriptions)
  557.                 if joinedDescription contains filterString then
  558.                     set _num to _num + 1
  559.                     set _msgToLog to (_num as string) & ". " & pName
  560.                     my writeToRoster(_msgToLog, filteredRoster)
  561.                 end if
  562.             end repeat
  563.         end tell
  564.     end tell
  565.     if _num > 1 then
  566.         set plural to "s"
  567.     else
  568.         set plural to ""
  569.     end if
  570.     set _summary to "=== " & filterString & " " & (_num as string) & " participant" & plural &      " " & (my formatDateTime(current date)) & " ==="
  571.     my writeToRoster(_summary, filteredRoster)
  572. end generateFilteredRoster
  573.  
  574. on howManyWaiting(waitingRoomText)
  575.     -- Take a string like "Waiting Room (3)" and return the number in the parentheses
  576.     set {tid, AppleScript's text item delimiters} to {AppleScript's text item delimiters, "("}
  577.     set thePart to text item 2 of waitingRoomText
  578.     set AppleScript's text item delimiters to ")"
  579.     set theValue to text item 1 of thePart
  580.     set AppleScript's text item delimiters to tid -- restore original text item delimiters
  581.     return theValue as integer
  582. end howManyWaiting
  583.  
  584. on clickSubElement(target, searchString)
  585.     -- Search for a button by description that is a
  586.     -- sub element of the target. This works for windows, or
  587.     -- other UI elements (outlines, scroll areas, etc.)
  588.     tell application "System Events" to tell process appName
  589.         set elems to every UI element of target
  590.         repeat with e in elems
  591.             if description of e is searchString as text then
  592.                 click e
  593.                 exit repeat
  594.             end if
  595.         end repeat
  596.     end tell
  597. end clickSubElement
  598.  
  599. on letPeopleIn()
  600.     tell application "System Events" to tell process appName
  601.         tell outline 1 of scroll area 1 of participantWindow
  602.             -- Fetch all the rows in the Participants list.
  603.             set participantRows to every row
  604.             -- Check to see if Waiting Room exists.
  605.             set firstRowName to (get value of static text of UI element of row 1) as string
  606.             if firstRowName does not start with "Waiting Room " then
  607.                 return -- Nothing to do here
  608.             end if
  609.             set allParticipantNames to get value of static text of UI element of rows
  610.             set _waitingNumber to my howManyWaiting(firstRowName)
  611.             if _waitingNumber > 1 then
  612.                 -- We will find and click the "Admit All" button.
  613.                 my clickSubElement(item 1 of UI element of row 1, "Admit All")
  614.             else
  615.                 -- We will click the Admit button on row 2 (the participant)
  616.                 my clickSubElement(item 1 of UI element of row 2, "Admit")
  617.             end if
  618.             delay 1
  619.             -- Now, we log all the people we admitted.
  620.             -- First, gather the list of people.
  621.             set myList to {}
  622.             repeat with _name in allParticipantNames
  623.                 set _name to _name as text
  624.                 if _name starts with "Joined " then
  625.                     exit repeat
  626.                 else
  627.                     if not (_name starts with "Waiting Room ") then
  628.                         set myList to myList & {_name}
  629.                         my logMessage("Waiting Room: Admitted " & _name, logFile)
  630.                     end if
  631.                 end if
  632.             end repeat
  633.             my trackListBatched(myList, "/waiting_list")
  634.         end tell
  635.     end tell
  636. end letPeopleIn
  637.  
  638. on createBreakoutRooms(breakoutFilename)
  639.     if breakoutFilename starts with "/" then
  640.         set borFilePath to breakoutFilename
  641.     else
  642.         set borFilePath to topLevelDirectory & breakoutFilename
  643.     end if
  644.     tell application "System Events"
  645.         if (not (exists file borFilePath)) then
  646.             error ("Missing breakout file: " & borFilePath) number -1
  647.         end if
  648.         set _size to size of (info for POSIX file borFilePath)
  649.         if _size is 0 then
  650.             error ("Empty breakout file: " & borFilePath) number -2
  651.         end if
  652.     end tell
  653.    
  654.     set bFile to POSIX file (borFilePath)
  655.     set _file to open for access file bFile
  656.     set fileContents to read _file
  657.     close access _file
  658.    
  659.     set rooms to my filterFileContents(paragraphs of fileContents)
  660.    
  661.     set roomCount to (count rooms)
  662.     if roomCount is 0 then
  663.         return "No rooms are defined in the file " & borFilePath & " - exiting."
  664.     end if
  665.    
  666.     set bor_clicked to my clickStatusMenu("Breakout")
  667.     if not bor_clicked then
  668.         return "Breakout room functionality not available. Make sure breakout rooms are enabled and you are a host or co-host."
  669.     end if
  670.     set _borWin to my findSubWindow(appName, "Breakout")
  671.     if name of _borWin contains "In Progress" then
  672.         return "Breakout rooms already started. Please close them first."
  673.     else
  674.         tell application "System Events" to tell process appName
  675.             click _borWin
  676.             if exists text field 1 of _borWin then
  677.                 click text field 1 of _borWin
  678.                 delay 0.5
  679.                 keystroke " " & roomCount
  680.                 delay 0.5
  681.                 my clickSubElement(_borWin, "Assign manually")
  682.                 my clickSubElement(_borWin, "Create")
  683.             else
  684.                 my clickSubElement(_borWin, "Recreate")
  685.                 delay 0.5
  686.                 set _menuWinName to "Menu window"
  687.                 if exists window _menuWinName then
  688.                     key code 125
  689.                     key code 125
  690.                     keystroke " "
  691.                 end if
  692.                 keystroke "\t" & roomCount
  693.                 delay 0.5
  694.                 set w to window "All existing rooms will be replaced."
  695.                 my clickSubElement(w, "Assign manually")
  696.                 my clickSubElement(w, "Recreate")
  697.             end if
  698.         end tell
  699.     end if
  700.  
  701.     -- Now, with the right number of rooms, we can rename them all.
  702.     -- We can use the _borWin window handle we already have.
  703.     tell application "System Events" to tell process appName to tell _borWin
  704.         tell table 1 of scroll area 1 of group 1
  705.             set _rows to rows
  706.             set i to 0
  707.             repeat with _r in _rows
  708.                 set i to i + 1
  709.                 click UI element 2 of UI element 1 of _r
  710.                 delay 0.2
  711.                 keystroke item i of rooms
  712.                 delay 0.2
  713.                 keystroke "\r"
  714.                 delay 0.2
  715.             end repeat
  716.         end tell
  717.     end tell
  718. end createBreakoutRooms
  719.  
  720. on getBreakoutRooms(breakoutFilename)
  721.     if breakoutFilename starts with "/" then
  722.         set borFilePath to breakoutFilename
  723.     else
  724.         set borFilePath to topLevelDirectory & breakoutFilename
  725.     end if
  726.  
  727.     tell application "System Events"
  728.         if (exists file borFilePath) then
  729.             error ("Will not overwrite file: " & borFilePath) number -3
  730.         end if
  731.     end tell
  732.  
  733.     set bFile to POSIX file (borFilePath)
  734.     set _file to open for access file bFile with write permission
  735.  
  736.     set bor_clicked to my clickStatusMenu("Breakout")
  737.     if not bor_clicked then
  738.         return "Breakout room functionality not available. Make sure breakout rooms are enabled and you are a host or co-host."
  739.     end if
  740.     set _borWin to my findSubWindow(appName, "Breakout")
  741.     set _title to (name of _borWin)
  742.     set logEntry to "# === " & (my formatDateTime(current date)) & " " & _title
  743.     my appendToFile(_file, logEntry, zoomRosterDebug)
  744.  
  745.     tell application "System Events" to tell process appName to tell _borWin
  746.         tell table 1 of scroll area 1 of group 1
  747.             set _rows to rows
  748.             set rooms to {}
  749.             repeat with _r in _rows
  750.                 set _room to description of UI element 1 of UI element 1 of _r as string
  751.                 if _room is "text"
  752.                     set _room to "#  " & name of UI element 1 of UI element 1 of _r as string
  753.                 end if
  754.                 set end of rooms to _room
  755.             end repeat
  756.         end tell
  757.     end tell
  758.     repeat with eachRoom in rooms
  759.         my appendToFile(_file, eachRoom as text, zoomRosterDebug)
  760.     end repeat
  761.     close access _file
  762. end getBreakoutRooms
  763.  
  764. on moveMouse(x, y)
  765.     do shell script cliclick & " m:=" & x & ",=" & y
  766. end moveMouse
  767.  
  768. on clickMouse(x, y)
  769.     do shell script cliclick & " c:=" & x & ",=" & y
  770. end clickMouse
  771.  
  772. on renameParticipant(oldName, newName, verbose)
  773.     set _canRenameOthers to false
  774.     set _renamingMe to false
  775.     tell application "System Events" to tell process appName
  776.         tell outline 1 of scroll area 1 of participantWindow
  777.             set allParticipantNames to get value of static text of UI element of rows
  778.             set _myName to item 1 of allParticipantNames as string
  779.             set _renamingMe to (_myName is oldName)
  780.             set _canRenameOthers to (_myName contains "(Host, me)") or (_myName contains "(Co-host, me)")
  781.         end tell
  782.     end tell
  783.     if not _renamingMe and not _canRenameOthers then
  784.         if verbose then
  785.             log "ERROR: You need to be Host or Co-Host to rename other partcipants."
  786.             return
  787.         end if
  788.     end if
  789.     if not _renamingMe and oldName ends with "(Host)" or oldName ends with "(Co-host)" then
  790.         log "ERROR: You can not rename a Host or Co-host."
  791.         return
  792.     end if
  793.     tell application appName to activate
  794.     tell application "System Events" to tell process appName
  795.         tell outline 1 of scroll area 1 of participantWindow
  796.             repeat with _r in rows
  797.                 if (get value of static text of UI element of _r) as text is oldName then
  798.                     set {_x, _y} to position of UI element 1 of UI element 1 of _r
  799.                     my moveMouse(_x, _y)
  800.                     set {_x2, _y2} to position of UI element 3 of UI element 1 of _r
  801.                     set _drag to (_x2 - _x) as integer
  802.                     do shell script cliclick & " c:+" & _drag & ",+0"
  803.                     delay 0.5
  804.                     click menu item "Rename" of menu 1 of UI element 1 of _r
  805.                     delay 0.5
  806.                     keystroke newName
  807.                     keystroke "\r"
  808.                     my logMessage("Renamed: " & oldName & " => " & newName, logFile)
  809.                     exit repeat
  810.                 end if
  811.             end repeat
  812.         end tell
  813.     end tell
  814. end renameParticipant
  815.  
  816. on doBreakoutRooms(borCommand, filename)
  817.     if borCommand is "create" then
  818.         return my createBreakoutRooms(filename)
  819.     end if
  820.     if borCommand is "get" then
  821.         return my getBreakoutRooms(filename)
  822.     end if
  823.     log "Error: Unknown breakout subcommand: " & borCommand
  824.     return my usageMessage("breakout")
  825. end doBreakoutRooms
  826.  
  827. on run argv
  828.     set argCount to (count argv)
  829.     if (argCount > 0) then
  830.         set arg to item 1 of argv
  831.     else
  832.         set arg to "roster"
  833.     end if
  834.     if arg is not in knownCommands then log "Error: unknown command: " & arg
  835.     if arg is "help" or arg is not in knownCommands then
  836.         return my usageMessage("top")
  837.     else if arg is "reset" then
  838.         return my resetTrackigData()
  839.     end if
  840.    
  841.     my setUpLogFiles()
  842.     if arg is "server" then
  843.         return my runBackendServer()
  844.     end if
  845.     if arg is "dashboard" then
  846.         return my openDashboard()
  847.     end if
  848.    
  849.     my appLogMessage("START " & arg)
  850.     try
  851.         my checkZoomRunning()
  852.         my logMessage("Zoom Version: " & appVersion, logFile)
  853.         my checkZoomMeetingRunning()
  854.         my startParticipantWindow()
  855.         if arg is "roster" then
  856.             my generateRoster()
  857.         else if arg is "hands" then
  858.             my generateFilteredRoster("Hand raised")
  859.         else if arg is "camera_off" then
  860.             my generateFilteredRoster("Video off")
  861.         else if arg is "camera_on" then
  862.             my generateFilteredRoster("Video on")
  863.         else if arg is "phone" then
  864.             my generateFilteredRoster("Telephone")
  865.         else if arg is "no_audio" then
  866.             my generateFilteredRoster("No Audio")
  867.         else if arg is "muted" then
  868.             my generateFilteredRoster(" muted")
  869.         else if arg is "unmuted" then
  870.             my generateFilteredRoster(" unmuted")
  871.         else if arg is "admit" then
  872.             my letPeopleIn()
  873.         else if arg is "breakout" then
  874.             if argCount is not 3 then
  875.                 log my usageMessage("breakout")
  876.             else
  877.                 log my doBreakoutRooms(item 2 of argv, item 3 of argv)
  878.             end if
  879.         else if arg is "rename" then
  880.             if argCount is not 3 then
  881.                 log my usageMessage("rename")
  882.             else
  883.                 log my renameParticipant(item 2 of argv, item 3 of argv, true)
  884.             end if
  885.         end if
  886.     on error errMsg number errNum
  887.         set e_str to "Error: " & errMsg
  888.         my logMessage(e_str, logFile)
  889.         my appLogMessage("ABORT " & arg)
  890.         error e_str number errNum
  891.     end try
  892.     my appLogMessage("END " & arg)
  893. end run
  894.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement