From 9d70f29379078d1cb3e1af98e64b256eaa63dcdf Mon Sep 17 00:00:00 2001 From: hanez Date: Tue, 26 May 2026 02:45:56 +0200 Subject: [PATCH] Added basic IRC library fully written in Fun. Some fixes. (0.41.12) --- CMakeLists.txt | 2 +- examples/net/irc_channel_demo.fun | 20 +++--- examples/net/irc_echo_bot.fun | 102 ++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 13 deletions(-) create mode 100644 examples/net/irc_echo_bot.fun diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f2baaf..dd6bfee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.41.11 LANGUAGES C) +project(fun VERSION 0.41.12 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/net/irc_channel_demo.fun b/examples/net/irc_channel_demo.fun index 8cf7f01..896ba3f 100644 --- a/examples/net/irc_channel_demo.fun +++ b/examples/net/irc_channel_demo.fun @@ -58,23 +58,19 @@ if (!c.connect()) c.register(nick, user, real, pass) -// Process greetings and auto-respond to PINGs +// Process greetings and auto-respond to PINGs (briefly) start = dt.now_ms() -while (dt.now_ms() - start < 3000) +while (dt.now_ms() - start < 1500) handle_lines(c) -// Join #funlang and speak +// Join #funlang and speak once c.join(channel) dt.sleep_ms(500) c.privmsg(channel, real) -// Read a bit more to display echoes/traffic -start2 = dt.now_ms() -while (dt.now_ms() - start2 < 2000) +// Stay connected: continuously read and print lines; auto-respond to PINGs +print("Listening (press Ctrl-C to quit)...") +while (true) handle_lines(c) - -c.quit("bye") -dt.sleep_ms(200) -c.close() - -print("Done.") + // avoid busy loop if no data + dt.sleep_ms(50) diff --git a/examples/net/irc_echo_bot.fun b/examples/net/irc_echo_bot.fun new file mode 100644 index 0000000..7967301 --- /dev/null +++ b/examples/net/irc_echo_bot.fun @@ -0,0 +1,102 @@ +#!/usr/bin/env fun + +/* + * IRC Echo Bot example using lib/net/irc.fun + * + * Connects to an IRC server, registers, joins #funlang, and listens for + * messages. If a message contains "!echo ", the bot replies with . + * + * Notes: + * - Works over plain TCP. For TLS, point host/port to a local TLS tunnel + * (e.g., stunnel) and set use_tls=1. + */ + +#include +#include +#include +#include + +// --- Configuration --- +// freenode (plain): host="chat.freenode.net", port=6667, use_tls=0 +// For TLS via a local tunnel to remote 6697: host="127.0.0.1", port=, use_tls=1 +host = "chat.freenode.net" +port = 6667 +use_tls = 0 + +bot_nick = "FunEchoBot" // Choose an unused nickname +bot_user = "funecho" +bot_real = "Fun Echo Bot" +bot_pass = "" // Optional network password (not NickServ) +channel = "#fun-lang" + +// Globals used by the callback +g_client = nil +g_dt = DateTime() + +// Extract nick from a prefix of form "nick!user@host" or just "nick" +fun prefix_nick(prefix) + p = to_string(prefix) + ex = find(p, "!") + if (ex >= 0) + return substr(p, 0, ex) + at = find(p, "@") + if (at >= 0) + return substr(p, 0, at) + return p + +// Callback for IRCClient.pump(line, msg) +fun on_line(line, msg) + // Print all received lines to console + print(line) + + cmd = msg["command"] + if (cmd == "PRIVMSG") + params = msg["params"] + trg = "" + if (len(params) > 0) + trg = params[0] + txt = msg["trailing"] + + // Decide reply target: channel or private + reply_to = trg + if (str_to_upper(trg) == str_to_upper(bot_nick)) + reply_to = prefix_nick(msg["prefix"]) // PM -> reply to user + + // Detect command pattern: !echo + if (len(txt) >= 6) + if (substr(txt, 0, 6) == "!echo ") + echo_text = substr(txt, 6, len(txt) - 6) + if (g_client != nil) + g_client.privmsg(reply_to, echo_text) + +// --- Main flow --- +mode = " (plain)" +if (use_tls == 1) + mode = " (TLS via tunnel)" +print("Connecting to " + host + ":" + to_string(port) + mode) + +c = IRCClient(host, port, use_tls) +if (!c.connect()) + print("Failed to connect.") + exit(1) + +g_client = c + +c.register(bot_nick, bot_user, bot_real, bot_pass) + +// Process greetings and auto-respond to PINGs via pump() +start = g_dt.now_ms() +while (g_dt.now_ms() - start < 3000) + c.pump(on_line) + +// Join the channel and announce +c.join(channel) +g_dt.sleep_ms(500) +c.privmsg(channel, "FunEchoBot online. Say: !echo ") + +// Stay connected and keep handling messages until interrupted +print("Listening (press Ctrl-C to quit)...") +while (true) + c.pump(on_line) + // avoid busy loop when there's no data + g_dt.sleep_ms(50)