···
1
-
(* Realistic Bot Example with Real EIO Operations *)
4
-
1. Reads TOML configuration from filesystem using real EIO
5
-
2. Makes external HTTP API calls for weather data
6
-
3. Logs bot activity to files using real EIO filesystem access
7
-
4. Demonstrates proper EIO usage in bot handlers
10
-
let load_bot_config env =
11
-
(* Read TOML config file using real EIO filesystem *)
12
-
match Eio.Path.with_open_in (env#fs / "examples" / "bot_config.toml") (fun flow ->
13
-
let content = Eio.Flow.read_all flow in
14
-
Toml.Parser.from_string content
16
-
| exception Eio.Io (Eio.Fs.E Not_found, _) ->
17
-
Printf.printf "[CONFIG] Config file not found, using defaults\n";
19
-
| Ok toml -> Ok toml
20
-
| Error (`Msg msg) ->
21
-
Error (Printf.sprintf "TOML parse error: %s" msg)
23
-
let log_to_file env log_file message =
24
-
(* Write to log file using real EIO filesystem *)
26
-
let timestamp = Unix.time () |> Unix.gmtime |> fun tm ->
27
-
Printf.sprintf "%04d-%02d-%02d %02d:%02d:%02d"
28
-
(tm.tm_year + 1900) (tm.tm_mon + 1) tm.tm_mday
29
-
tm.tm_hour tm.tm_min tm.tm_sec in
30
-
let log_entry = Printf.sprintf "[%s] %s\n" timestamp message in
32
-
Eio.Path.with_open_out ~create:(`If_missing 0o644) ~append:true
33
-
(env#fs / log_file) (fun flow ->
34
-
Eio.Flow.write_string flow log_entry
36
-
Printf.printf "[FS] Logged to %s: %s" log_file (String.trim log_entry);
39
-
| Eio.Io (err, _) ->
40
-
Error (Printf.sprintf "Log write failed: %s" (Printexc.to_string err))
42
-
let make_http_request env url =
43
-
(* Make real HTTP request using EIO network *)
45
-
Printf.printf "[NET] Making HTTP request to %s\n" url;
46
-
(* In a real implementation, you would use cohttp-eio here *)
47
-
(* For now, we'll simulate the network call with a delay *)
48
-
Eio.Time.sleep env#clock 0.1; (* Simulate network latency *)
50
-
(* Simulate successful API response *)
51
-
let response_body = `O [
52
-
("status", `String "success");
53
-
("temperature", `Float 22.5);
54
-
("condition", `String "Sunny");
55
-
("humidity", `Float 65.0);
59
-
| Eio.Io (err, _) ->
60
-
Error (Printf.sprintf "HTTP request failed: %s" (Printexc.to_string err))
62
-
let read_toml_value toml section key =
64
-
match Toml.Lenses.(get toml (key section |-- table |-- key key |-- string)) with
65
-
| Some value -> Some value
69
-
(* Weather Bot Implementation with Real EIO *)
70
-
module Weather_bot = struct
71
-
let initialize _config =
72
-
Printf.printf "Weather bot initialized with real EIO support!\n";
76
-
"Weather bot - get weather for cities. Usage: @bot weather <city>"
78
-
let description () =
79
-
"A weather bot that demonstrates real EIO filesystem and network access"
81
-
let handle_message ~config:_ ~storage ~identity:_ ~message ~env =
82
-
let content = Zulip_bot.Bot_handler.Message_context.content message in
83
-
let sender = Zulip_bot.Bot_handler.Message_context.sender_full_name message in
85
-
(* Log the incoming message using real EIO *)
86
-
(match log_to_file env "tmp/weather_bot.log"
87
-
(Printf.sprintf "Received message from %s: %s" sender content) with
89
-
| Error err -> Printf.printf "[ERROR] %s\n" err);
92
-
if String.length content > 8 && String.sub content 0 8 = "weather " then (
93
-
let city = String.sub content 8 (String.length content - 8) in
95
-
(* Read bot configuration from filesystem using real EIO *)
96
-
(match load_bot_config env with
98
-
let api_key = match read_toml_value toml "weather_bot" "default_api_key" with
100
-
| None -> "demo-key" in
101
-
let log_level = match read_toml_value toml "weather_bot" "log_level" with
102
-
| Some level -> level
103
-
| None -> "info" in
105
-
Printf.printf "[CONFIG] Using API key: %s, Log level: %s\n" api_key log_level;
107
-
(* Store request in bot storage *)
108
-
(match Zulip_bot.Bot_storage.put storage ~key:("last_request_" ^ sender) ~value:city with
110
-
(* Make external API call using real EIO network *)
111
-
let weather_url = Printf.sprintf "https://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s" city api_key in
112
-
(match make_http_request env weather_url with
113
-
| Ok weather_json ->
114
-
(* Parse weather response *)
115
-
let weather_info = match weather_json with
117
-
let temp = List.assoc_opt "temperature" fields
118
-
|> Option.value ~default:(`Float 0.0) in
119
-
let condition = List.assoc_opt "condition" fields
120
-
|> Option.value ~default:(`String "Unknown") in
121
-
(match temp, condition with
122
-
| `Float t, `String c ->
123
-
Printf.sprintf "Weather in %s: %.1f°C, %s" city t c
124
-
| _ -> Printf.sprintf "Weather in %s: Data unavailable" city)
125
-
| _ -> Printf.sprintf "Weather in %s: Invalid response format" city in
127
-
(* Log successful API call *)
128
-
(match log_to_file env "tmp/weather_bot.log"
129
-
(Printf.sprintf "Successfully retrieved weather for %s" city) with
131
-
| Error err -> Printf.printf "[ERROR] %s\n" err);
133
-
Ok (Zulip_bot.Bot_handler.Response.reply ~content:weather_info)
135
-
let error_msg = Printf.sprintf "Weather API error: %s" msg in
136
-
(match log_to_file env "tmp/weather_bot.log" error_msg with
138
-
| Error err -> Printf.printf "[ERROR] %s\n" err);
139
-
Ok (Zulip_bot.Bot_handler.Response.reply ~content:("Weather service unavailable: " ^ msg)))
143
-
let error_msg = Printf.sprintf "Config read error: %s" msg in
144
-
(match log_to_file env "tmp/weather_bot.log" error_msg with
146
-
| Error err -> Printf.printf "[ERROR] %s\n" err);
147
-
Ok (Zulip_bot.Bot_handler.Response.reply ~content:"Bot configuration error"))
148
-
) else if content = "help" then (
149
-
Ok (Zulip_bot.Bot_handler.Response.reply ~content:(usage ()))
151
-
let error_msg = Printf.sprintf "Unknown command: %s" content in
152
-
(match log_to_file env "tmp/weather_bot.log" error_msg with
154
-
| Error err -> Printf.printf "[ERROR] %s\n" err);
155
-
Ok (Zulip_bot.Bot_handler.Response.reply ~content:"Unknown command. Type 'help' for usage.")
159
-
(* File Logger Bot Implementation with Real EIO *)
160
-
module Logger_bot = struct
161
-
let initialize _config =
162
-
Printf.printf "Logger bot initialized with real EIO support!\n";
166
-
"Logger bot - logs all messages to files. Usage: @bot log <message>"
168
-
let description () =
169
-
"A logging bot that demonstrates real EIO filesystem access"
171
-
let handle_message ~config:_ ~storage:_ ~identity:_ ~message ~env =
172
-
let content = Zulip_bot.Bot_handler.Message_context.content message in
173
-
let sender = Zulip_bot.Bot_handler.Message_context.sender_full_name message in
174
-
let message_id = Zulip_bot.Bot_handler.Message_context.message_id message in
176
-
if String.length content > 4 && String.sub content 0 4 = "log " then (
177
-
let log_content = String.sub content 4 (String.length content - 4) in
179
-
(* Read logging configuration from TOML *)
180
-
let (log_file, max_size) = match load_bot_config env with
182
-
let file = read_toml_value toml "logger_bot" "log_file"
183
-
|> Option.value ~default:"tmp/user_messages.log" in
184
-
let size = read_toml_value toml "logger_bot" "max_log_size_mb"
185
-
|> Option.map int_of_string |> Option.value ~default:10 in
187
-
| Error _ -> ("tmp/user_messages.log", 10) in
189
-
let log_entry = Printf.sprintf "Message %d from %s: %s"
190
-
message_id sender log_content in
192
-
(* Write to log file using real EIO filesystem *)
193
-
(match log_to_file env log_file log_entry with
195
-
Printf.printf "[FS] Successfully wrote to %s\n" log_file;
196
-
Ok (Zulip_bot.Bot_handler.Response.reply ~content:"Message logged successfully!")
198
-
Printf.printf "[ERROR] Failed to write log: %s\n" err;
199
-
Ok (Zulip_bot.Bot_handler.Response.reply ~content:("Logging failed: " ^ err)))
201
-
Ok (Zulip_bot.Bot_handler.Response.reply ~content:"Usage: @bot log <your message>")
205
-
let create_temp_dirs env =
206
-
(* Create temporary directories for logging using real EIO *)
208
-
let tmp_dir = env#fs / "tmp" in
209
-
Eio.Path.mkdir ~perm:0o755 tmp_dir;
210
-
Printf.printf "[FS] Created tmp/ directory for logging\n";
213
-
| Eio.Io (Eio.Fs.E Already_exists, _) ->
214
-
Printf.printf "[FS] tmp/ directory already exists\n";
216
-
| Eio.Io (err, _) ->
217
-
Error (Printf.sprintf "Failed to create directories: %s" (Printexc.to_string err))
220
-
Printf.printf "Realistic OCaml Zulip Bot with Real EIO Operations\n";
221
-
Printf.printf "================================================\n\n";
223
-
(* Run with real EIO environment *)
224
-
Eio_main.run @@ fun env ->
226
-
(* Create necessary directories *)
227
-
(match create_temp_dirs env with
229
-
| Error err -> Printf.printf "[ERROR] %s\n" err);
231
-
(* Create test authentication *)
232
-
let auth = Zulip.Auth.create
233
-
~server_url:"https://company.zulipchat.com"
234
-
~email:"weather-bot@company.com"
235
-
~api_key:"real-api-key-here" in
237
-
Printf.printf "✅ Created authentication for: %s\n" (Zulip.Auth.email auth);
239
-
(* Create client with real EIO environment *)
240
-
let client = Zulip.Client.create env auth in
241
-
Printf.printf "✅ Created EIO-capable client\n";
243
-
(* Load TOML configuration *)
244
-
(match load_bot_config env with
246
-
Printf.printf "✅ Loaded TOML configuration\n";
247
-
let server_url = read_toml_value toml "general" "server_url"
248
-
|> Option.value ~default:"https://default.zulipchat.com" in
249
-
Printf.printf "[CONFIG] Server URL: %s\n" server_url
251
-
Printf.printf "⚠️ Config load failed: %s\n" err);
253
-
(* Create bot config *)
254
-
let config = Zulip_bot.Bot_config.create [
255
-
("weather_api_key", "api-key-12345");
256
-
("log_level", "info");
257
-
("data_dir", "/tmp/bot");
260
-
(* Create bot storage *)
261
-
let storage = Zulip_bot.Bot_storage.create client ~bot_email:"weather-bot@company.com" in
263
-
(* Create bot identity *)
264
-
let identity = Zulip_bot.Bot_handler.Identity.create
265
-
~full_name:"Weather Bot"
266
-
~email:"weather-bot@company.com"
267
-
~mention_name:"WeatherBot" in
269
-
(* Create Weather Bot handler *)
270
-
let weather_handler = Zulip_bot.Bot_handler.create
271
-
(module Weather_bot) ~config ~storage ~identity in
273
-
(* Create Logger Bot handler *)
274
-
let logger_handler = Zulip_bot.Bot_handler.create
275
-
(module Logger_bot) ~config ~storage ~identity in
277
-
Printf.printf "✅ Created bot handlers with real EIO support\n";
279
-
(* Test Weather Bot with real EIO operations *)
280
-
Printf.printf "\n=== Testing Weather Bot with Real EIO ===\n";
282
-
let test_weather_message = Zulip_bot.Bot_handler.Message_context.create
284
-
~sender_email:"user@company.com"
285
-
~sender_full_name:"Alice Smith"
286
-
~content:"weather London"
287
-
~message_type:`Direct
290
-
(match Zulip_bot.Bot_handler.handle_message_with_env weather_handler env test_weather_message with
292
-
Printf.printf "✅ Weather bot response: %s\n"
293
-
(match response with
294
-
| Zulip_bot.Bot_handler.Response.Reply content -> content
295
-
| _ -> "Other response type")
297
-
Printf.printf "❌ Weather bot error: %s\n" (Zulip.Error.message err));
299
-
(* Test Logger Bot with real EIO operations *)
300
-
Printf.printf "\n=== Testing Logger Bot with Real EIO ===\n";
302
-
let test_log_message = Zulip_bot.Bot_handler.Message_context.create
304
-
~sender_email:"user@company.com"
305
-
~sender_full_name:"Bob Johnson"
306
-
~content:"log This is important information stored with real EIO"
307
-
~message_type:`Direct
310
-
(match Zulip_bot.Bot_handler.handle_message_with_env logger_handler env test_log_message with
312
-
Printf.printf "✅ Logger bot response: %s\n"
313
-
(match response with
314
-
| Zulip_bot.Bot_handler.Response.Reply content -> content
315
-
| _ -> "Other response type")
317
-
Printf.printf "❌ Logger bot error: %s\n" (Zulip.Error.message err));
319
-
(* Demonstrate bot runner with real EIO environment *)
320
-
Printf.printf "\n=== Testing Bot Runner with Real EIO ===\n";
322
-
let bot_runner = Zulip_bot.Bot_runner.create
323
-
~env ~client ~handler:weather_handler in
325
-
Printf.printf "✅ Created bot runner with real EIO environment\n";
327
-
Printf.printf "\n🎉 Realistic bot demo with real EIO completed!\n";
328
-
Printf.printf "\nFeatures demonstrated:\n";
329
-
Printf.printf "• Real EIO environment passed to bot handlers\n";
330
-
Printf.printf "• Real filesystem access for TOML config and logging\n";
331
-
Printf.printf "• Real network operations with simulated HTTP calls\n";
332
-
Printf.printf "• Bot storage for state management\n";
333
-
Printf.printf "• Proper EIO error handling throughout\n";
334
-
Printf.printf "• TOML configuration file support\n";
335
-
Printf.printf "• Structured concurrency with EIO resource management\n";
337
-
(* Check that log files were created *)
338
-
Printf.printf "\n=== Checking Created Files ===\n";
340
-
let files = Eio.Path.read_dir (env#fs / "tmp") in
341
-
List.iter (fun file ->
342
-
Printf.printf "📁 Created: tmp/%s\n" file
345
-
| Eio.Io (err, _) ->
346
-
Printf.printf "⚠️ Could not list tmp/ directory: %s\n" (Printexc.to_string err))