@@ -6,8 +6,10 @@ import (
6
6
"encoding/hex"
7
7
"flag"
8
8
"fmt"
9
+ "io"
9
10
"net/http"
10
11
"os"
12
+ "path/filepath"
11
13
"strings"
12
14
"time"
13
15
@@ -83,7 +85,7 @@ func zoomCrcValidation(jresp ZoomWebhook) (bool, ChallengeResponse) {
83
85
func filterMeeting (jresp ZoomWebhook ) bool {
84
86
// If the meeting is outside the topic scope, just ignore.
85
87
name := viper .GetString ("meeting_name" )
86
- log .Debugln ("applyMeetingFilters) Topic " + jresp .Payload .Object .Topic )
88
+ log .Debugln ("( applyMeetingFilters) Topic " + jresp .Payload .Object .Topic )
87
89
if name != jresp .Payload .Object .Topic && name != "" {
88
90
log .Infoln ("Received hook but dropping due to topic being filtered." )
89
91
log .Debugln ("(applyMeetingFilter) Hook had topic '" + jresp .Payload .Object .Topic + "'" )
@@ -107,10 +109,58 @@ func setMessageSuffix(jresp ZoomWebhook) string {
107
109
return msg
108
110
}
109
111
112
+ // savePostRequestToFile writes the payload of a POST request to a file in the "hooks" directory.
113
+ //
114
+ // It operates silently, meaning it does not modify the HTTP response or interfere with request processing.
115
+ // If the request is not a POST, has no body, or if an error occurs while reading or writing the body,
116
+ // the function simply returns without taking any action.
117
+ //
118
+ // The request payload is saved in a file named "hook-<timestamp>.json", where <timestamp> follows the
119
+ // "YYYYMMDD-HHMMSS.mmm" format to ensure unique filenames. The directory "hooks" is created if it does not exist.
120
+ //
121
+ // Parameters:
122
+ // - c: A *gin.Context representing the HTTP request and response context.
123
+ //
124
+ // Usage:
125
+ //
126
+ // This function can be used as middleware in a Gin application to capture incoming POST request payloads.
127
+ //
128
+ // Example:
129
+ //
130
+ // r := gin.Default()
131
+ // r.Use(savePostRequestToFile) // Logs POST request bodies to the "hooks" directory
132
+ // r.POST("/hook", func(c *gin.Context) {
133
+ // c.JSON(200, gin.H{"message": "Hook received"})
134
+ // })
135
+ // r.Run(":8080")
136
+ func savePostRequestToFile (c * gin.Context ) {
137
+ if c .Request .Method != "POST" {
138
+ return // Only process POST requests
139
+ }
140
+
141
+ // Read the request body
142
+ body , err := io .ReadAll (c .Request .Body )
143
+ if err != nil || len (body ) == 0 {
144
+ return // If there's an error reading or body is empty, do nothing
145
+ }
146
+
147
+ // Ensure the hooks directory exists
148
+ hooksDir := "hooks"
149
+ _ = os .MkdirAll (hooksDir , 0755 )
150
+
151
+ // Generate a timestamp-based filename
152
+ timestamp := time .Now ().Format ("20060102-150405.000" )
153
+ filename := filepath .Join (hooksDir , "hook-" + timestamp + ".json" )
154
+
155
+ // Write the body to a file (ignoring errors to avoid interfering with request processing)
156
+ _ = os .WriteFile (filename , body , 0644 )
157
+ }
158
+
110
159
func processWebHook (c * gin.Context ) {
111
160
161
+ log .Debugln ("(processWebHook) Processing incoming webhook. at " + time .Now ().String ())
112
162
if gin .IsDebugging () {
113
- // log incoming request if in DEBUG mode
163
+ // savePostRequestToFile(c)
114
164
}
115
165
var jresp ZoomWebhook
116
166
if err := c .BindJSON (& jresp ); err != nil {
@@ -134,13 +184,42 @@ func processWebHook(c *gin.Context) {
134
184
return
135
185
}
136
186
187
+ // If the event type is not a participant join or leave, ignore it.
188
+ if jresp .Event != "meeting.participant_joined" && jresp .Event != "meeting.participant_left" {
189
+ log .Debugln ("(processWebHook) Ignoring event type: " + jresp .Event )
190
+ return
191
+ }
192
+
137
193
msg := setMessageSuffix (jresp )
138
- // create a link for the zoom meeting in the message
139
- msg = msg + " [Zoom Meeting](https://zoom.us/j/" + jresp .Payload .Object .ID + ")"
140
- dispatchMessage (msg )
194
+ log .Debugln ("About to dispatch Message: " + msg )
195
+ dispatchMessage (msg , jresp )
196
+ }
197
+
198
+ func getJoinURL (meetingId string ) string {
199
+ /*
200
+ To enable this feature, you must set the following environment variables:
201
+ ZOOM_API_ENABLE=1
202
+ ZOOM_API_CLIENT_ID
203
+ ZOOM_API_CLIENT_SECRET
204
+ ZOOM_API_ACCOUNT_ID
205
+ */
206
+ joinurl := ""
207
+ if os .Getenv ("ZOOM_API_ENABLE" ) == "1" {
208
+ // Get secret from the zoom API so we can get the meeting details
209
+ // Check to see that ZOOM_API_CLIENT_ID, ZOOM_API_CLIENT_SECRET, and ZOOM_API_ACCOUNT_ID are set
210
+ if os .Getenv ("ZOOM_API_CLIENT_ID" ) != "" && os .Getenv ("ZOOM_API_CLIENT_SECRET" ) != "" && os .Getenv ("ZOOM_API_ACCOUNT_ID" ) != "" {
211
+ log .Debugln (meetingId )
212
+ joinurl := callZoomAPI (meetingId )
213
+ log .Debugln ("Join URL: " + joinurl )
214
+ } else {
215
+ // This should be unreachable code, but it's there for debugging and defense.
216
+ log .Errorln ("ZOOM_API environment credentials are not set. Skipping." )
217
+ }
218
+ }
219
+ return joinurl
141
220
}
142
221
143
- func dispatchMessage (msg string ) {
222
+ func dispatchMessage (msg string , jresp ZoomWebhook ) {
144
223
145
224
slack_enable := viper .GetString ("slack_enable" )
146
225
irc_enable := viper .GetString ("irc_enable" )
@@ -150,9 +229,8 @@ func dispatchMessage(msg string) {
150
229
151
230
if strings .ToLower (slack_enable ) == "true" {
152
231
log .Debugln ("(dispatchMessage) Sending a slack message" )
153
- parseAndSplitSlackHooks (msg )
232
+ parseAndSplitSlackHooks (msg , jresp )
154
233
sent = 1
155
-
156
234
}
157
235
if strings .ToLower (irc_enable ) == "true" {
158
236
log .Debugln ("(dispatchMessage) Sending an IRC message" )
@@ -174,6 +252,7 @@ func inititalize() {
174
252
viper .SetDefault ("slack_enable" , "true" )
175
253
viper .SetDefault ("irc_enable" , "false" )
176
254
viper .SetDefault ("msg_suffix" , "the zoom meeting." )
255
+ viper .SetDefault ("zoom_api_enable" , "false" )
177
256
178
257
viper .BindEnv ("port" , "ZOOMWH_PORT" )
179
258
viper .BindEnv ("slack_enable" , "ZOOMWH_SLACK_ENABLE" )
@@ -186,6 +265,34 @@ func inititalize() {
186
265
viper .BindEnv ("zoom_secret" , "ZOOM_SECRET" )
187
266
}
188
267
268
+ // Zoom API Specifics
269
+ viper .BindEnv ("ZOOM_API_ENABLE" )
270
+ zoomApiEnabled := viper .GetBool ("ZOOM_API_ENABLE" )
271
+ if zoomApiEnabled == false {
272
+ log .Infoln ("Zoom Web API is disabled. Disabling active meeting links and quieries" )
273
+ viper .Set ("zoom_api_enable" , "false" )
274
+ } else {
275
+ log .Infoln ("Zoom Web API is enabled." )
276
+ viper .MustBindEnv ("zoom_api_client_id" , "ZOOM_API_CLIENT_ID" )
277
+ zoom_api_client_id := viper .GetString ("zoom_api_client_id" )
278
+ if zoom_api_client_id == "" {
279
+ log .Errorln ("You must set ZOOM_API_CLIENT_ID environment variable if ZOOM_API_ENABLE=true." )
280
+ bugout = true
281
+ }
282
+ viper .MustBindEnv ("zoom_api_client_secret" , "ZOOM_API_CLIENT_SECRET" )
283
+ zoom_api_client_secret := viper .GetString ("zoom_api_client_secret" )
284
+ if zoom_api_client_secret == "" {
285
+ log .Errorln ("You must set ZOOM_API_CLIENT_SECRET environment variable if ZOOM_API_ENABLE=true." )
286
+ bugout = true
287
+ }
288
+ viper .MustBindEnv ("zoom_api_account_id" , "ZOOM_API_ACCOUNT_ID" )
289
+ zoom_api_account_id := viper .GetString ("zoom_api_account_id" )
290
+ if zoom_api_account_id == "" {
291
+ log .Errorln ("You must set ZOOM_API_ACCOUNT_ID environment variable if ZOOM_API_ENABLE=true." )
292
+ bugout = true
293
+ }
294
+ }
295
+
189
296
// Slack Specifics
190
297
viper .GetString ("slack_enable" )
191
298
if value := os .Getenv ("ZOOMWH_SLACK_ENABLE" ); value == "false" {
@@ -255,7 +362,6 @@ func inititalize() {
255
362
256
363
func main () {
257
364
258
- // Define a --version flag
259
365
showVersion := flag .Bool ("version" , false , "Show version information" )
260
366
flag .Parse ()
261
367
0 commit comments