- Changed multi-result mode from a single pipe-separated message to
individual messages: each result is now its own IRC message (Title —
snippet) followed by a separate Read full article: LINK message.
- Removed numbering from multi-result messages.
- Changed single-result extract to show the first chapter instead of only
the introductory paragraph by removing exintro=true from the Extracts
API URL.
- Increased extract character limit from 500 to 800 (exchars=800).
- Added truncation indicator (...) for extracts approaching the character
limit.
- Added Read full article: prefix to the single-result link message.
- Updated CHANGELOG.md with 0.43.1 entry.
- Add !wp <search terms> command: searches Wikipedia via MediaWiki API
(action=query&list=search), displays article titles, snippets, and links.
Single-result mode fetches the first chapter via Extracts API (exchars=800);
multi-result mode shows a numbered list with short snippets and per-result
Read full article links.
- Add self-contained JSON parser helpers: wp_json_get_string(),
wp_json_next_array_item(), wp_decode_json_string() with \uXXXX Unicode
support.
- Add wp_url_encode() (RFC 3986), wp_codepoint_to_utf8(), wp_strip_html(),
wp_collapse_spaces(), wp_hex_digit().
- Add WITH_WP CMake feature toggle (ON by default, HAS_CMD_WP compile def).
- Add !wp to the !help command listing.
- Add doc comments to all 9 functions in cmd_wp.h.
- Update CHANGELOG.md with 0.43.0 entry.
- Update README.md: add Wikipedia to features list, feature toggles table,
IRC commands utilities table, and minimal build command.
Note: the previous commit (3b97b4d) implemented the flood protection
changes but was committed as 0.41.2. This commit corrects the version
to 0.42.0 to reflect the scope of the changes (breaking rate_limit_check
signature, new constants, new outgoing throttle, etc.) and adds the
matching detailed CHANGELOG.md entry.
Changes documented in CHANGELOG.md:
- Per-poll line limit (MAX_LINES_PER_POLL = 50) in irc_feed()
- Userhost-based rate limiting replacing nick-based tracking
- Violation tracking in RateLimitEntry for escalating penalties
- RATE_LIMIT_MAX increased from 8 to 32
- Outgoing message throttle (OUTGOING_DELAY_MS = 100) in net_send()
- 200ms inter-line delay in !gitlog output
- 150ms inter-chunk delay in AI response delivery
- AI_COOLDOWN_SECS = 5 after query completion
- Rate limiting added to greeting/chat responses
- TELL_DELIVER_MAX = 5 messages per nick per delivery
- Updated all 19 command headers for new rate_limit_check() signature
Phase 1: Incoming Protocol-Level Protection
- Added MAX_LINES_PER_POLL = 50 constant to limit lines processed per
poll iteration in irc_feed(), preventing burst flooding.
- Added poll_lines_left counter to Session struct, reset each poll cycle.
- Changed RateLimitEntry to track by user@host instead of nick, preventing
trivial bypass via nick cycling.
- Parse userhost from IRC prefix (nick!user@host) in irc_handle().
- Added violation tracking to RateLimitEntry for escalating penalties.
Phase 2: Outgoing Message Throttling
- Added OUTGOING_DELAY_MS = 100 constant for minimum delay between sends.
- Modified net_send() with timestamp-based throttling using clock_gettime().
- Added 200ms delay between lines in !gitlog output (cmd_gitlog.h).
- Added 150ms delay between AI response chunks (ai.h).
Phase 3: Rate Limit Improvements
- Increased RATE_LIMIT_MAX from 8 to 32 for larger tracking buffer.
- Added TELL_DELIVER_MAX = 5 to limit messages per nick delivery.
- Added AI_COOLDOWN_SECS = 5 to prevent rapid AI query re-submission.
Phase 4: Command-Specific Fixes
- Added rate_limit_check() to cmd_greeting_or_chat.h for casual chat.
- Limited tell_deliver() to 5 messages per nick, excess stays queued.
- Added 5-second cooldown after AI query completion before next query.
Files modified: zynk.h, zynk.c, irc.h, net.h, ai.h, db.h, and all
19 command header files to pass current_userhost to rate_limit_check().
- Moved doc comment block for yt_url_encode() from top of cmd_yt.h
to immediately before the function definition for better readability.
- Added doc comments to volatile sig_atomic_t running variable describing
its purpose as main loop control flag and async-signal-safety.
- Added doc comments to start_time variable explaining its role in
uptime calculation.
- Updated CHANGELOG.md to reflect yt_url_encode() doc comment move.
- Added welcome message when users join a channel: handle_join() now sends "Welcome, <nick>!" to the channel for non-bot users. The bot's own joins are silently ignored to avoid echo loops.
- Added src_nick parameter to handle_join() to identify the joining user; the call site in irc_handle() was updated to pass src_nick.
- Added doc comments to chans[] (global channel tracking array) and nchans (count of tracked channels) in irc.h.
- Bumped version to 0.41.2 in CMakeLists.txt.
Fixed !yt displaying raw JSON Unicode escape sequences (e.g. \u0027
instead of ') in video titles and channel names. YouTube's oEmbed API
returns non-ASCII characters as \uXXXX escapes, which were previously
passed through as-is to IRC.
- Added yt_hex_digit(): converts a hex character to its numeric value (0-15)
- Added yt_codepoint_to_utf8(): encodes a Unicode code point as 1-4 UTF-8
bytes, handling the full range from U+0000 through U+10FFFF
- Added yt_decode_unicode_escapes(): scans a string for \uXXXX sequences
and replaces each with the corresponding UTF-8 bytes in-place. Safe for
in-place modification because UTF-8 output is always shorter than the
6-byte escape sequence
- Both title and author fields are now decoded after extraction from the
JSON response
- Added doc comments to all three new helper functions
- Updated CHANGELOG.md with 0.41.1 entry documenting all changes
Added new !yt command (cmd_yt.h) that fetches the title and channel name
of a YouTube video using the YouTube oEmbed API. The video URL is
percent-encoded via a new yt_url_encode() helper before being passed as a
query parameter. The JSON response is parsed for 'title' and 'author_name'
fields using the existing json_find() helper, and the result is formatted
as 'Title - Channel'.
- New file: cmd_yt.h with yt_url_encode() and cmd_yt() command handler
- CMakeLists.txt: added WITH_YT feature toggle (ON by default)
- zynk.c: added conditional #include for cmd_yt.h
- irc.h: added cmd_yt() dispatch in handle_privmsg()
- cmd_help.h: added !yt <url> to the help listing
- zynk.c: added doc comments to json_find() function
- cmd_yt.h: added doc comments to yt_url_encode() and cmd_yt()
- CHANGELOG.md: added 0.41.0 entry documenting all changes
- README.md: updated feature summary, features list, feature toggles
table, minimal build command, and IRC commands utilities table
- Reduced MAX_HISTORY from 100 to 12 to decrease AI context memory usage.
- Moved LOAD_FUNC macro from zynk.h to db.h for better code organization (closer to its usage in sqlite_init_lib).
- Added comprehensive doc comment to irc_reply() function describing its nick-prefixing behavior for channel messages.
- Reordered includes in zynk.c to alphabetical order.
- Added irc_reply(): new IRC response helper that automatically prepends
the sender's nick to channel messages (e.g. 'nick: pong!'), while
leaving private-message responses unmodified. This makes it clear
which user the bot is addressing in busy channels.
- Converted all non-admin command handlers to use irc_reply() instead of
raw irc_msg(): !ping, !zynk, !weather, !forecast, !stock, !calc,
!time, !seen, !tell, !gitlog, !changelog, !ai, !uptime, !version,
!help, greeting/chat responses, and the 'Not implemented...' fallback.
- Converted AI async responses (ai_check_completion()) to use irc_reply(),
so AI answers delivered after the original query also show the nick.
- Converted AI error messages in ai_ask() (rate-limit busy, fork failure,
start failure) to use irc_reply().
- Removed redundant manual nick prefixes from commands that already
embedded the sender's nick in their response text: !ping, !calc,
!time, !uptime, and greeting/chat responses.
- Admin commands (!quit, !reload, !restart, !rebuild, !code) are
intentionally excluded - these are privileged operations where the
nick prefix adds no value.
- The tell_deliver() function retains its own existing format.
- Moved _FORTIFY_SOURCE=3 from target_compile_definitions to
target_compile_options with a preceding -U_FORTIFY_SOURCE via
the SHELL: prefix to prevent redefinition warnings on musl/Alpine
where the compiler already defines it via built-in macros.
- The SHELL: prefix ensures both flags stay together and in the
correct order on the compile command line.
- Updated CHANGELOG.md entry for 0.40.7 with the fix.
- Fixed trailing space in weather descriptions from wttr.in (e.g.
'Partly Cloudy ' -> 'Partly Cloudy'), which caused a stray space
before the closing parenthesis in !forecast full output (e.g.
'(Partly Cloudy )' instead of '(Partly Cloudy)').
- Applied the same trailing-space fix to the cond field used by
non-full !forecast and !weather.
- Updated CHANGELOG.md with 0.40.6 entry.
- Added 'full' keyword to !forecast: '!forecast frankfurt full' shows
per-day detail lines with weather description, sunrise/sunset, and
moonrise/moonset in 24-hour format, sent as separate IRC messages.
- Added to_24h() helper to convert wttr.in's 12-hour AM/PM astronomy
times to 24-hour format (e.g. '05:20 AM' -> '05:20').
- Fixed missing first letter of weather descriptions in full mode
('value' offset was 11 instead of 10, skipping the first char).
- Startup message now includes the version: '[zynk 0.40.5] starting up'.
- Added doc comments to to_24h(), find_day_obj(), and other forecast
helper functions with full parameter documentation.
- Fixed 'Not implemented...' fallback to only trigger when ! is
followed by a letter (e.g. !foo), so bare ! or !!! no longer
produce the message.
- Updated README.md to document !forecast [city] [full] syntax.
- Updated CHANGELOG.md with 0.40.5 entry.
- Increased AI code mode response limit back to 2000 chars (was reduced
to 1000 in 0.40.3, but code changes need more room).
- Simplified the AI plan mode system prompt further: removed the explicit
'tell them you are in plan mode' instruction, keeping it to a short
'You are in plan mode. Answer briefly.' directive.
- Fixed !gitlog default count in README.md from 5 to 1 (matching the
actual GITLOG_COUNT value set in 0.38.2).
- Re-aligned the utilities command table in README.md for cleaner
formatting.
- Minor grammar and punctuation fixes across older changelog entries.
- Refactored CMake feature configuration: merged the two separate
foreach loops (one for building _cmd_defs and one for printing status)
into a single loop, removing redundant iteration. Improved the status
banner formatting for clearer feature configuration output.
- Reduced AI code mode response limit from 2000 to 1000 chars to keep
IRC replies concise.
- Increased AI plan mode response limit from 800 to 1000 chars, giving
slightly more room for explanations.
- Simplified AI plan mode system prompt description for clarity.
- Fixed minor whitespace alignment in README.md command table.
- !gitlog now shows the commit date and time in [YYYY-MM-DD HH:MM:SS]
format at the beginning of each commit line, e.g.:
[2026-07-22 14:30:15] 184d4c4 Fix whitespace... - https://...
- Changed git log format from '%H %s' to '%ci %H %s' and parsed the
ISO date to extract date+time without timezone offset.
(0.40.2)
- Fixed db_set() using datetime('now') which returns UTC, causing
!zynk <key> to display timestamps offset by the server's timezone
difference. Changed to datetime('now','localtime') so timestamps
match the system's local time.
- Also fixed db_rotate_history() (AI query context rotation) and the
zynk/tell table DEFAULT clauses to use localtime.
- Same pattern as the !seen fix in 0.39.1.
(0.40.1)
- !gitlog now shows a link to each commit in the repository, using the
git remote URL converted from SSH/SCP to HTTPS
- !changelog now shows a 'Full changelog: <url>' link to CHANGELOG.md
in the repository as the last message after all entries
- Removed 'full' keyword from !gitlog and !changelog (previously showed
full commit messages and complete changelog entries)
- Switched build system from Make to CMake with feature toggles
(-DWITH_<FEATURE>=ON/OFF)
- Refactored single-file codebase into dedicated header files: each
command now lives in its own cmd_*.h file, with separate modules for
database (db.h), IRC protocol (irc.h), networking (net.h), TLS (tls.h),
and AI (ai.h)
- Updated README.md to reflect the simplified !gitlog and !changelog
syntax and the new CMake build instructions
(0.40.0)
- Added strip_opencode_header(): removes the opencode header line (e.g. '> plan · big-pickle') and any trailing blank lines from AI responses before sending to IRC.
- Previous strip_before_double_space() approach failed because ANSI escape codes in the raw opencode output prevented strstr() from matching literal double spaces.
- New approach runs strip_formatting() first to clean up ANSI codes, then detects the '> ' prefix and strips the header line plus blank lines after it.
- Pipeline order: format strip -> header strip -> phrase strip.
- Version bumped to 0.38.3.
- Added optional full keyword to !gitlog and !changelog for showing
full commit messages and changelog entries instead of just headers.
- !gitlog full uses git log (without --oneline) and strips leading
indentation from commit blocks, collapses multiple spaces.
- !changelog full collects full entry text with bullet points and
shows the newest entries (first N in file, not last N).
- Added optional pull argument to !rebuild: !rebuild pull runs git
pull before make clean/make, plain !rebuild only rebuilds.
- Added collapse_spaces() call in !gitlog oneline path to remove
double spaces in compact output.
- Changed default GITLOG_COUNT and CHANGELOG_COUNT from 5 to 1.
- Reduced GITLOG_MAX from 10 to 5.
- Updated !help text to document [full] and [pull] options.
- Updated README.md with full option for !gitlog/!changelog and
pull option for !rebuild.
- Added CHANGELOG.md entry for v0.38.2.
- Op users in channels are now exempt from rate limiting: rate_limit_check()
calls chan_is_op() and allows ops through immediately, so ops can use
commands without being throttled.
- Updated rate_limit_check() signature to accept a channel parameter;
all 20 call sites now pass reply_target so the exemption works for
both channel and DM contexts.
- Removed unused AI_REPLY_MAX constant from the enum.
- Removed stale commented-out #include <sys/utsname.h>.
- Version bumped to 0.38.1.
- Added !changelog [count] command: displays the latest changelog entries
from CHANGELOG.md. Defaults to 5 entries (CHANGELOG_COUNT), accepts 1-10
as count. Each entry title is sent as a separate IRC message with a
200ms delay to avoid IRC server flood protection.
- Reduced GITLOG_MAX and CHANGELOG_MAX from 20 to 10.
- Rewrote cmd_changelog to use getline() for robust line reading and
isdigit() checks for reliable date pattern detection.
- Updated !help to list !changelog.
- Updated README.md with !changelog documentation and corrected max values.
- Added CHANGELOG.md entry for v0.38.0.
- Version bumped to 0.38.0.
Added !rebuild op-only command that runs git pull, make clean, and make
in sequence, then triggers hot reload on success. Reports build errors
from stderr if any step fails. Updated !help and README.md docs.
seen_update() used SQLite's datetime('now') which returns UTC, causing
!seen to show wrong times (e.g. 16:00 instead of 18:00 on UTC+2).
Changed to datetime('now','localtime') so seen timestamps match the
system's local time.
Version bumped to 0.36.1.
Add detailed function documentation for cmd_gitlog() matching the
existing style used throughout zynk.c. Describes the fork/exec
pattern for running git log, output handling, rate limiting, and
clamping behavior for the count argument.
Updated CHANGELOG.md 0.36.0 entry to reflect this addition.
Add !gitlog [count] IRC command that runs 'git log --oneline' and
sends each commit as a separate PRIVMSG. Defaults to GITLOG_COUNT (5)
commits when no argument is provided; accepts 1-20 as count.
- Fork/exec pattern consistent with existing !calc command
- Rate-limited per-nick like all other commands
- Listed in !help output
- Added git to README.md requirements
- Documented in README.md utilities section
- Added changelog entry for v0.36.0
The !time command now outputs only date and time (YYYY-MM-DD HH:MM:SS)
without the timezone abbreviation. This keeps responses shorter and
cleaner in IRC. Removed the dead abbreviation-matching code from
tz_format() which previously mapped 45 IANA timezone names to their
respective abbreviations (CEST, EDT, JST, etc.). Updated README to
reflect the new output format.
The CHANGELOG lists versions from newest to oldest. v0.35.4 (README
update for pi() docs and date requirement) was accidentally placed after
v0.35.3 (pi() support in !calc). Fixed so entries read:
0.35.5 -> 0.35.4 -> 0.35.3 -> 0.35.2 -> ...
- Added doc comments (/* ... */) to all 14 undocumented static functions in
zynk.c matching the existing documentation style (function name, description,
Parameters, Returns sections):
* DB ops: seen_update, seen_lookup, tell_add, tell_deliver
* String utils: valid_irc_word, normalize_city, collapse_spaces
* Command handlers: cmd_reload, cmd_time, cmd_seen, cmd_tell, cmd_greeting_or_chat
* Timezone helpers: tz_lookup, tz_format
- Added complete reference table of all 45 built-in timezones to README.md
with region, UTC offset, and DST support status, so users know which
timezones work without tzdata installed.
- Updated CHANGELOG.md v0.35.5 entry to reflect these additions.
- Clean build, zero warnings.
The !calc command was broken because the expression was never piped to
bc's stdin - only stdout was set up via a pipe. Added a second pipe
to feed the expression into bc before reading the result.
- Bumped version to 0.35.1
bc reads from stdin but the child process never wrote the expression
to it, causing empty output. Added a second pipe to feed the expression
into bc's stdin before reading the result from stdout.
New IRC commands:
- !calc <expr>: evaluate math via bc -l (arithmetic, powers, trig, log, vars)
- !time [tz]: show current time in any IANA timezone
- !seen <nick>: track/query last-seen activity (auto-updated on speak/part/quit)
- !tell <nick> <msg>: leave offline messages, delivered when target speaks
Database:
- Added 'seen' table for user activity tracking (nick, timestamp, channel, action)
- Added 'tell' table for pending offline messages
Other changes:
- Updated !help to list all new commands
- Updated README.md with detailed docs for all commands including !calc
expression syntax and !time timezone examples
- Bumped version to 0.35.0