33 Commits

Author SHA1 Message Date
e0ad67a921 Bump version, update NEWS 2021-08-07 07:53:08 +02:00
565edc15b4 README.adoc: be consistent in emphasizing 2021-08-07 07:40:02 +02:00
5d285ffb96 xB: fix up the special IPC command's name
To reflect the new disorder.
2021-08-06 17:18:06 +02:00
50057d5149 Come up with sillier names for the binaries
I'm not entirely sure, but it looks like some people might not like
jokes about the Holocaust.

On a more serious note, the project has become more serious over
the 7 or so years of its existence.
2021-08-06 16:43:59 +02:00
1f64710e79 NEWS: improve wording
The phrase "input line" has already been used once in the file.
2021-07-24 09:40:35 +02:00
027bf8666e degesch: never bump our own chanuser
With IRCv3.2 echo-message, each successfully sent message would
move us to the front of the list used for chanuser autocomplete.

Such behaviour seems useless.

Also abandon the idea of bumping on other kinds of messages.
2021-07-24 09:27:49 +02:00
7c7e12d8d5 degesch: start with lexically ordered chanusers
This makes nick autocompletion start in a non-arbitrary state.
2021-07-23 19:14:57 +02:00
3cb93d24e8 degesch: order nick autocomplete by time 2021-07-23 18:43:20 +02:00
acddfe2cfa degesch: cleanup 2021-07-23 18:43:19 +02:00
051c43a072 NEWS: fix a garbled up entry
Try not to commit, push and tag releases tired.
2021-07-08 05:17:13 +02:00
0fe0b56280 Bump version, update NEWS 2021-07-08 05:09:30 +02:00
f0281cf028 test-nick-colors: fix and streamline
A recent addition of an N_ELEMENTS macro invocation broke it.
2021-06-25 06:35:00 +02:00
da5dd4eb91 degesch: make /ban and /unban respect EXTBAN 2021-06-17 12:21:48 +02:00
10cb6651c0 degesch: expand/analyze a few TODO comments 2021-06-16 22:10:25 +02:00
7f28dcd1ef degesch: make "/help /command" work
Works for aliases as well.  Resolves a TODO entry.
2021-06-16 21:57:47 +02:00
61c52d793c degesch: fix a GCC compiler warning 2021-06-15 07:11:35 +02:00
b4dd0052ff degesch: pick colours based on relative luminance
Replaces the inaccurate Rec. 709 luma we used to use before.

This is the first feature here that requires libm, which doesn't
seem to be a particularly great sacrifice.

Moreover, I've rectified that the input isn't linear in sRGB,
and then was even normalized wrong for the luma formula.
2021-06-15 07:09:23 +02:00
e3c47c33fa degesch: implement -=/+= for multiple values
It didn't make sense to have these unimplemented,
though perhaps += shouldn't enforce a set.

Sadly, autocomplete is fairly difficult for -= of multiple items.
2021-06-14 09:06:38 +02:00
80c1e8f8eb degesch: make /deop and /devoice default to self
It's pretty annoying to type `/mode -o <user>`, for little reason.
2021-06-03 00:12:22 +02:00
c5f49ab1e6 censor.lua: strip colours, configurable formatting
Colour parsing code taken from prime.lua, and modified to strip.
2021-06-03 00:12:22 +02:00
6f62b9c0c7 degesch: make CHGHOST update our own userhost info
I've almost forgotten that we use this for message spliting.
2021-05-30 08:23:23 +02:00
c1d69e3630 degesch: add support for IRCv3 chghost
This is somewhat similar to a nick change.
2021-05-30 08:06:38 +02:00
c75ef167f2 degesch: document the SASL EXTERNAL support
So far it's only been mentioned in the NEWS file,
which is definitely not sufficient.

It would be good to move this kind of stuff out from README.adoc.
2021-05-29 06:38:33 +02:00
ddffc71abe degesch: factor out irc_try_finish_cap_negotiation()
Too much repeated, non-obvious code.
2021-05-28 04:59:21 +02:00
5a0b2d1c57 degesch: add trivial SASL EXTERNAL support
Just set `tls_cert`, and add `sasl` to `capabilities`.
2021-05-28 04:59:20 +02:00
bb451a5050 degesch: support CAP DEL, request cap-notify
It doesn't require much effort to cancel capabilities, plus with
the newer version we get the respective notification anyway.
2021-05-28 04:59:20 +02:00
61f15ead8a degesch: don't CAP REQ when already registered
The list may later be requested manually, which shouldn't have
an unexpected side-effect.
2021-05-28 04:59:20 +02:00
17f430043a degesch: IRCv3.2 capability negotiation
We can receive and display capability values now.
2021-05-28 04:59:20 +02:00
735096d76d degesch: add a /squery command for IRCnet 2021-05-28 04:06:27 +02:00
1ba59e6ee0 degesch: fix back-parsing outgoing CAP REQ
The bug has apparently been there since the beginning.
2021-05-28 04:04:44 +02:00
f9ba682c0e degesch: reset away-notify on disconnect
Forgotten to do it when adding the support for it.
2021-05-28 04:04:23 +02:00
8e8ffe2c73 degesch: don't switch to channels while typing
We might just always set the highlighted bit on,
it would be consistent with PMs.
2021-04-10 05:11:46 +02:00
d05c85833d degesch: make a second SIGINT force-quit
Also fixed the possibility of eating a sequence of signals
as we reset the indicators /after/ we took action,
which creates a time window for races.
2020-11-01 15:33:16 +01:00
34 changed files with 745 additions and 410 deletions

View File

@@ -1,5 +1,5 @@
cmake_minimum_required (VERSION 3.0)
project (uirc3 VERSION 1.1.0 LANGUAGES C)
project (uirc3 VERSION 1.3.0 LANGUAGES C)
# Options
option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
@@ -61,7 +61,8 @@ endif ()
# -lrt is only for glibc < 2.17
# -liconv may or may not be a part of libc
foreach (extra iconv rt)
# -lm may or may not be a part of libc
foreach (extra iconv rt m)
find_library (extra_lib_${extra} ${extra})
if (extra_lib_${extra})
list (APPEND project_libraries ${extra_lib_${extra}})
@@ -76,9 +77,9 @@ CHECK_C_SOURCE_RUNS ("#include <iconv.h>
int main () { return iconv_open (\"UTF-8//TRANSLIT\", \"ISO-8859-1\")
== (iconv_t) -1; }" ICONV_ACCEPTS_TRANSLIT)
# Dependencies for degesch
# Dependencies for xC
pkg_check_modules (libffi REQUIRED libffi)
list (APPEND degesch_libraries ${libffi_LIBRARIES})
list (APPEND xC_libraries ${libffi_LIBRARIES})
include_directories (${libffi_INCLUDE_DIRS})
link_directories (${libffi_LIBRARY_DIRS})
@@ -91,7 +92,7 @@ if (WITH_LUA)
message (FATAL_ERROR "Lua library not found")
endif ()
list (APPEND degesch_libraries ${lua_LIBRARIES})
list (APPEND xC_libraries ${lua_LIBRARIES})
include_directories (${lua_INCLUDE_DIRS})
link_directories (${lua_LIBRARY_DIRS})
endif ()
@@ -99,10 +100,10 @@ endif ()
find_package (Curses)
pkg_check_modules (ncursesw ncursesw)
if (ncursesw_FOUND)
list (APPEND degesch_libraries ${ncursesw_LIBRARIES})
list (APPEND xC_libraries ${ncursesw_LIBRARIES})
include_directories (${ncursesw_INCLUDE_DIRS})
elseif (CURSES_FOUND)
list (APPEND degesch_libraries ${CURSES_LIBRARY})
list (APPEND xC_libraries ${CURSES_LIBRARY})
include_directories (${CURSES_INCLUDE_DIR})
else ()
message (SEND_ERROR "Curses not found")
@@ -114,13 +115,13 @@ elseif (WANT_READLINE)
# OpenBSD's default readline is too old
if ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD")
include_directories (${OPENBSD_LOCALBASE}/include/ereadline)
list (APPEND degesch_libraries ereadline)
list (APPEND xC_libraries ereadline)
else ()
list (APPEND degesch_libraries readline)
list (APPEND xC_libraries readline)
endif ()
elseif (WANT_LIBEDIT)
pkg_check_modules (libedit REQUIRED libedit)
list (APPEND degesch_libraries ${libedit_LIBRARIES})
list (APPEND xC_libraries ${libedit_LIBRARIES})
include_directories (${libedit_INCLUDE_DIRS})
endif ()
@@ -134,34 +135,34 @@ configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h
include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR})
# Generate IRC replies--we need a custom target because of the multiple outputs
add_custom_command (OUTPUT kike-replies.c kike.msg
COMMAND ${PROJECT_SOURCE_DIR}/kike-gen-replies.sh
> kike-replies.c < ${PROJECT_SOURCE_DIR}/kike-replies
DEPENDS ${PROJECT_SOURCE_DIR}/kike-replies
add_custom_command (OUTPUT xD-replies.c xD.msg
COMMAND ${PROJECT_SOURCE_DIR}/xD-gen-replies.sh
> xD-replies.c < ${PROJECT_SOURCE_DIR}/xD-replies
DEPENDS ${PROJECT_SOURCE_DIR}/xD-replies
COMMENT "Generating files from the list of server numerics")
add_custom_target (replies DEPENDS ${PROJECT_BINARY_DIR}/kike-replies.c)
add_custom_target (replies DEPENDS ${PROJECT_BINARY_DIR}/xD-replies.c)
# Build
foreach (name zyklonb degesch kike)
foreach (name xB xC xD)
add_executable (${name} ${name}.c ${PROJECT_BINARY_DIR}/config.h)
target_link_libraries (${name} ${project_libraries})
add_threads (${name})
endforeach ()
add_dependencies (kike replies)
add_dependencies (degesch replies)
target_link_libraries (degesch ${degesch_libraries})
add_dependencies (xD replies)
add_dependencies (xC replies)
target_link_libraries (xC ${xC_libraries})
# Tests
include (CTest)
if (BUILD_TESTING)
add_executable (test-degesch $<TARGET_PROPERTY:degesch,SOURCES>)
set_target_properties (test-degesch PROPERTIES COMPILE_DEFINITIONS TESTING)
target_link_libraries (test-degesch $<TARGET_PROPERTY:degesch,LINK_LIBRARIES>)
add_threads (test-degesch)
add_dependencies (test-degesch replies)
add_executable (test-xC $<TARGET_PROPERTY:xC,SOURCES>)
set_target_properties (test-xC PROPERTIES COMPILE_DEFINITIONS TESTING)
target_link_libraries (test-xC $<TARGET_PROPERTY:xC,LINK_LIBRARIES>)
add_threads (test-xC)
add_dependencies (test-xC replies)
add_test (NAME test-degesch COMMAND test-degesch)
add_test (NAME test-xC COMMAND test-xC)
add_test (NAME custom-static-analysis
COMMAND ${PROJECT_SOURCE_DIR}/test-static)
endif ()
@@ -181,13 +182,13 @@ add_custom_target (clang-tidy
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
# Installation
install (TARGETS zyklonb degesch kike DESTINATION ${CMAKE_INSTALL_BINDIR})
install (TARGETS xB xC xD DESTINATION ${CMAKE_INSTALL_BINDIR})
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
# XXX: our defaults for XDG_DATA_DIRS expect /usr/local/shore or /usr/share
install (DIRECTORY plugins/zyklonb/
DESTINATION ${CMAKE_INSTALL_DATADIR}/zyklonb/plugins USE_SOURCE_PERMISSIONS)
install (DIRECTORY plugins/degesch/
DESTINATION ${CMAKE_INSTALL_DATADIR}/degesch/plugins)
install (DIRECTORY plugins/xB/
DESTINATION ${CMAKE_INSTALL_DATADIR}/xB/plugins USE_SOURCE_PERMISSIONS)
install (DIRECTORY plugins/xC/
DESTINATION ${CMAKE_INSTALL_DATADIR}/xC/plugins)
# Generate documentation from text markup
find_program (ASCIIDOCTOR_EXECUTABLE asciidoctor)
@@ -195,7 +196,7 @@ if (NOT ASCIIDOCTOR_EXECUTABLE)
message (FATAL_ERROR "asciidoctor not found")
endif ()
foreach (page zyklonb degesch kike)
foreach (page xB xC xD)
set (page_output "${PROJECT_BINARY_DIR}/${page}.1")
list (APPEND project_MAN_PAGES "${page_output}")
add_custom_command (OUTPUT ${page_output}
@@ -216,7 +217,7 @@ foreach (page ${project_MAN_PAGES})
endforeach ()
# CPack
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Unethical IRC client, daemon and bot")
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Unreasonable IRC client, daemon and bot")
set (CPACK_PACKAGE_VERSION "${project_version_safe}")
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")

View File

@@ -1,4 +1,4 @@
Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name>
Copyright (c) 2014 - 2021, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

202
NEWS
View File

@@ -1,8 +1,44 @@
1.3.0 (2021-08-07) "New World Order"
* xC: made nick autocompletion offer recent speakers first
* All binaries have been renamed to something even sillier,
and all references in the source tree have been redacted;
this represents a major incompatible change for all plugins;
configuration and program data have to be adjusted manually
1.2.0 (2021-07-08) "There Are Other Countries As Well"
* xC: added a /squery command for IRCnet
* xC: added trivial support for SASL EXTERNAL, enabled by adding "sasl"
to the respective server's "capabilities" list
* xC: now supporting IRCv3.2 capability negotiation, including CAP DEL
* xC: added support for IRCv3 chghost
* xC: /deop and /devoice without arguments will use the client's user
* xC: /set +=/-= now treats its argument as a string array
* xC: made "/help /command" work the same way as "/help command" does
* xC: /ban and /unban don't mangle extended bans anymore
* xC: joining new channels no longer switches to their buffer automatically
if the current input line isn't empty
* censor.lua: now stripping colours from censored messages;
their attributes are also configurable rather than always black on black
1.1.0 (2020-10-31) "What Do You Mean By 'This Isn't Germany'?"
* degesch: made fancy-prompt.lua work with libedit
* xC: made fancy-prompt.lua work with libedit
* kike: fixed a regression with an unspecified "bind_host"
* xD: fixed a regression with an unspecified "bind_host"
* Miscellaneous minor improvements
@@ -11,55 +47,55 @@
* Coming with real manual pages instead of help2man-generated stubs
* degesch: added support for more IRC colours and strike-through text (M-m x)
* xC: added support for more IRC colours and strike-through text (M-m x)
* degesch: now tolerating all UTF-8 messages cut off by the server
* xC: now tolerating all UTF-8 messages cut off by the server
* degesch: disabled "behaviour.backlog_helper_strip_formatting" by default
* xC: disabled "behaviour.backlog_helper_strip_formatting" by default
since the relevant issue with ACS terminfo entries has been resolved
* degesch: enabled word wrapping in the backlog by default
* xC: enabled word wrapping in the backlog by default
* degesch: made the unread marker span the whole line, with a configurable
* xC: made the unread marker span the whole line, with a configurable
character; the previous behaviour can be obtained by setting it empty
* degesch: fixed the prompt not showing back up after exiting a backlog helper
* xC: fixed the prompt not showing back up after exiting a backlog helper
when an external event has provoked an attempt to change it
* degesch: now watching fellow channel users' away status when the server
* xC: now watching fellow channel users' away status when the server
supports the away-notify capability; indicated by italicised nicknames
* degesch: added a plugin to highlight prime numbers in incoming messages
* xC: added a plugin to highlight prime numbers in incoming messages
* kike: make sure an unspecified "bind_host" binds to both IPv4 and IPv6
* xD: make sure an unspecified "bind_host" binds to both IPv4 and IPv6
* ZyklonB: install plugins to /usr/share and look for them in XDG data dirs
* xB: install plugins to /usr/share and look for them in XDG data dirs
* Miscellaneous little fixes
0.9.8 (2020-09-02) "Yep, Still Using It"
* degesch: fixed a crash and prompt attribute output in libedit 20191231-3.1,
* xC: fixed a crash and prompt attribute output in libedit 20191231-3.1,
though users are officially discouraged from using this library
* degesch: fixed Lua 5.4 build, so far the support is experimental
* xC: fixed Lua 5.4 build, so far the support is experimental
* Miscellaneous little fixes
0.9.7 (2018-10-21) "Business as Usual"
* kike: fix wildcard handling in WHOIS
* xD: fix wildcard handling in WHOIS
* kike: properly handle STATS without parametetrs
* xD: properly handle STATS without parametetrs
* kike: abort earlier when an invalid mode character is detected while
* xD: abort earlier when an invalid mode character is detected while
processing channel MODE messages
* kike: do not send NICK notifications when the nickname doesn't really change
* xD: do not send NICK notifications when the nickname doesn't really change
* kike: fix hostname string verification (only used for "server_name")
* xD: fix hostname string verification (only used for "server_name")
0.9.6 (2018-06-22) "I've Been Sitting Here All This Time"
@@ -68,108 +104,108 @@
* Fix LibreSSL compatibility
* degesch: a second /disconnect cuts the connection by force
* xC: a second /disconnect cuts the connection by force
* degesch: send a QUIT message to the IRC server on Ctrl-C
* xC: send a QUIT message to the IRC server on Ctrl-C
* degesch: add a Slack plugin (even though the gateway's now defunct)
* xC: add a Slack plugin (even though the gateway's now defunct)
* degesch: show an error message on log write failure
* xC: show an error message on log write failure
* degesch: fix parsing of literal IPv6 addresses with port numbers
* xC: fix parsing of literal IPv6 addresses with port numbers
* degesch: fix some error messages
* xC: fix some error messages
* degesch: workaround a Readline bug in the fancy-prompt.lua plugin
* xC: workaround a Readline bug in the fancy-prompt.lua plugin
* kike: fix two memory leaks
* xD: fix two memory leaks
* kike: improve error handling for incoming connections
* xD: improve error handling for incoming connections
* kike: disable TLS session reuse
* xD: disable TLS session reuse
0.9.5 (2016-12-30) "It's Time"
* Better support for the KILL command
* degesch: export many more fields to the Lua API, add a prompt hook
* xC: export many more fields to the Lua API, add a prompt hook
* degesch: show channel user count in the prompt
* xC: show channel user count in the prompt
* degesch: allow hiding join/part messages and other noise (Meta-Shift-H)
* xC: allow hiding join/part messages and other noise (Meta-Shift-H)
* degesch: allow autojoining channels with keys
* xC: allow autojoining channels with keys
* degesch: rejoin channels with keys on reconnect
* xC: rejoin channels with keys on reconnect
* degesch: make /query without arguments just open the buffer
* xC: make /query without arguments just open the buffer
* degesch: add a censor plugin
* xC: add a censor plugin
* degesch: die on configuration parse errors
* xC: die on configuration parse errors
* degesch: request channel modes also on rejoin
* xC: request channel modes also on rejoin
* degesch: don't show remembered channel modes on parted channels
* xC: don't show remembered channel modes on parted channels
* degesch: fix highlight detection in colored text
* xC: fix highlight detection in colored text
* degesch: fix CTCP handling for the real world and don't decode X-QUOTEs
* xC: fix CTCP handling for the real world and don't decode X-QUOTEs
* degesch: add support for OpenSSL 1.1.0
* xC: add support for OpenSSL 1.1.0
0.9.4 (2016-04-28) "Oops"
* degesch: fix crash on characters invalid in Windows-1252
* xC: fix crash on characters invalid in Windows-1252
* degesch: add an auto-rejoin plugin
* xC: add an auto-rejoin plugin
* degesch: better date change messages with customizable formatting;
* xC: better date change messages with customizable formatting;
now also used in the backlog, so it looks closer to regular output
* ZyklonB: add a calc plugin providing a basic Scheme REPL
* xB: add a calc plugin providing a basic Scheme REPL
* ZyklonB: add a seen plugin
* xB: add a seen plugin
* kike, ZyklonB: use pledge(2) on OpenBSD
* xD, xB: use pledge(2) on OpenBSD
0.9.3 (2016-03-27) "Doesn't Even Suck"
* Use TLS Server Name Indication when connecting to servers
* degesch: now we erase the screen before displaying buffers
* xC: now we erase the screen before displaying buffers
* degesch: implemented word wrapping in buffers
* xC: implemented word wrapping in buffers
* degesch: added autocomplete for /topic
* xC: added autocomplete for /topic
* degesch: Lua API was improved and extended
* xC: Lua API was improved and extended
* degesch: added a basic last.fm "now playing" plugin
* xC: added a basic last.fm "now playing" plugin
* degesch: backlog limit was made configurable
* xC: backlog limit was made configurable
* degesch: allow changing the list of IRC capabilities to use if available
* xC: allow changing the list of IRC capabilities to use if available
* degesch: optimize buffer memory usage
* xC: optimize buffer memory usage
* degesch: added logging of messages sent from /quote and plugins
* xC: added logging of messages sent from /quote and plugins
* degesch: M-! and M-a to go to the next buffer in order with
a highlight or new activity respectively
* xC: M-! and M-a to go to the next buffer in order with a highlight
or new activity respectively
* degesch: added --format for previewing things like MOTD files
* xC: added --format for previewing things like MOTD files
* degesch: added /buffer goto supporting case insensitive partial matches
* xC: added /buffer goto supporting case insensitive partial matches
* kike: add support for IRCv3.2 server-time
* xD: add support for IRCv3.2 server-time
* ZyklonB: plugins now run in a dedicated data directory
* xB: plugins now run in a dedicated data directory
* ZyklonB: added a factoids plugin
* xB: added a factoids plugin
* Remote addresses are now resolved asynchronously
@@ -178,28 +214,28 @@
0.9.2 (2015-12-31)
* degesch: added rudimentary support for Lua scripting
* xC: added rudimentary support for Lua scripting
* degesch: added detection of pasting, so that it doesn't trigger other
* xC: added detection of pasting, so that it doesn't trigger other
keyboard shortcuts, such as for autocomplete
* degesch: added auto-away capability
* xC: added auto-away capability
* degesch: added an /oper command
* xC: added an /oper command
* degesch: libedit backend works again
* xC: libedit backend works again
* degesch: added capability to edit the input line using VISUAL/EDITOR
* xC: added capability to edit the input line using VISUAL/EDITOR
* degesch: added Meta-Tab to switch to the last used buffer
* xC: added Meta-Tab to switch to the last used buffer
* degesch: correctly respond to stopping and resuming (SIGTSTP)
* xC: correctly respond to stopping and resuming (SIGTSTP)
* degesch: fixed decoding of text formatting
* xC: fixed decoding of text formatting
* degesch: unseen PMs now show up as highlights
* xC: unseen PMs now show up as highlights
* degesch: various bugfixes
* xC: various bugfixes
0.9.1 (2015-09-25)
@@ -210,23 +246,23 @@
* Pulled in kqueue support
* degesch: added backlog/scrollback functionality using less(1)
* xC: added backlog/scrollback functionality using less(1)
* degesch: made showing the entire set of channel mode user prefixes optional
* xC: made showing the entire set of channel mode user prefixes optional
* degesch: nicknames in /names are now ordered
* xC: nicknames in /names are now ordered
* degesch: nicknames now use the 256-color terminal palette if available
* xC: nicknames now use the 256-color terminal palette if available
* degesch: now we skip entries in the "addresses" list that can't be resolved
* xC: now we skip entries in the "addresses" list that can't be resolved
to an address, along with displaying a more helpful message
* degesch: joins, parts, nick changes and quits don't count as new buffer
* xC: joins, parts, nick changes and quits don't count as new buffer
activity anymore
* degesch: added Meta-H to open the full log file
* xC: added Meta-H to open the full log file
* degesch: various bugfixes and little improvements
* xC: various bugfixes and little improvements
0.9.0 (2015-07-23)

View File

@@ -2,25 +2,25 @@ uirc3
=====
:compact-option:
The [line-through]#unethical# edgy IRC trinity. This project consists of an
IRC client, daemon, and bot. It's all you're ever going to need for chatting,
as long as you can make do with minimalist software.
The unreasonable IRC trinity. This project consists of an IRC client, daemon,
and bot. It's all you're ever going to need for chatting, as long as you can
make do with minimalist software.
All of them have these potentially interesting properties:
- IPv6 support
- TLS support, including client certificates
- lean on dependencies (with the exception of 'degesch')
- lean on dependencies (with the exception of 'xC')
- compact and arguably easy to hack on
- very permissive license
degesch
-------
xC
--
The IRC client. It is largely defined by being built on top of GNU Readline
that has been hacked to death. Its interface should feel somewhat familiar for
weechat or irssi users.
image::degesch.png[align="center"]
image::xC.png[align="center"]
This is the largest application within the project. It has most of the stuff
you'd expect of an IRC client, such as being able to set up multiple servers,
@@ -28,8 +28,8 @@ a powerful configuration system, integrated help, text formatting, CTCP queries,
automatic splitting of overlong messages, autocomplete, logging to file,
auto-away, command aliases and basic support for Lua scripting.
kike
----
xD
--
The IRC daemon. It is designed to be used as a regular user application rather
than a system-wide daemon. If all you want is a decent, minimal IRCd for
testing purposes or a small network of respectful users (or bots), this one will
@@ -48,7 +48,7 @@ Not supported:
- server linking (which also means no services); I consider existing protocols
for this purpose ugly and tricky to implement correctly; I've also found no
use for this feature yet
- online changes to configuration; the configuration system from degesch could
- online changes to configuration; the configuration system from 'xC' could
be used to implement this feature if needed
- limits of almost any kind, just connections and mode `+l`
@@ -56,8 +56,8 @@ This program has been
https://git.janouch.name/p/haven/src/branch/master/hid[ported to Go],
and development continues over there.
ZyklonB
-------
xB
--
The IRC bot. It builds upon the concept of my other VitaminA IRC bot. The main
characteristic of these two bots is that they run plugins as coprocesses, which
allows for enhanced reliability and programming language freedom.
@@ -78,7 +78,7 @@ Building
--------
Build dependencies: CMake, pkg-config, asciidoctor, awk, liberty (included) +
Runtime dependencies: openssl +
Additionally for degesch: curses, libffi, lua >= 5.3 (optional),
Additionally for 'xC': curses, libffi, lua >= 5.3 (optional),
readline >= 6.0 or libedit >= 2013-07-12
Avoid libedit if you can, in general it works but at the moment history is
@@ -102,45 +102,49 @@ Or you can try telling CMake to make a package for you. For Debian it is:
Usage
-----
'degesch' has in-program configuration. Just run it and read the instructions.
Consult its link:degesch.adoc[man page] for details about the interface.
'xC' has in-program configuration. Just run it and read the instructions.
Consult its link:xC.adoc[man page] for details about the interface.
For the rest you might want to generate a configuration file:
$ zyklonb --write-default-config
$ kike --write-default-config
$ xB --write-default-config
$ xD --write-default-config
After making any necessary edits to the file (there are comments to aid you in
doing that), simply run the appropriate program with no arguments:
$ zyklonb
$ kike
$ xB
$ xD
'ZyklonB' stays running in the foreground, therefore I recommend launching it
inside a Screen or tmux session.
'xB' stays running in the foreground, therefore I recommend launching it inside
a Screen or tmux session.
'kike', on the other hand, immediately forks into the background. Use the PID
'xD', on the other hand, immediately forks into the background. Use the PID
file or something like `killall` if you want to terminate it. You can run it
as a `forking` type systemd user service.
Client Certificates
-------------------
'kike' uses SHA-1 fingerprints of TLS client certificates to authenticate users.
'xC' will use the SASL EXTERNAL method to authenticate using the TLS client
certificate specified by the respective server's `tls_cert` option if you add
`sasl` to the `capabilities` option and the server supports this.
'xD' uses SHA-1 fingerprints of TLS client certificates to authenticate users.
To get the fingerprint from a certificate file in the required form, use:
$ openssl x509 -in public.pem -outform DER | sha1sum
Custom Key Bindings in degesch
------------------------------
The default and preferred frontend used in 'degesch' is GNU Readline. This
means that you can change your bindings by editing '~/.inputrc'. For example:
Custom Key Bindings in xC
-------------------------
The default and preferred frontend used in 'xC' is GNU Readline. This means
that you can change your bindings by editing '~/.inputrc'. For example:
....
# Preload with system-wide settings
$include /etc/inputrc
# Make M-left and M-right reorder buffers
$if degesch
$if xC
"\e\e[C": move-buffer-right
"\e\e[D": move-buffer-left
$endif
@@ -150,9 +154,9 @@ Consult the source code and the GNU Readline manual for a list of available
functions. Also refer to the latter for the exact syntax of this file.
Beware that you can easily break the program if you're not careful.
How do I make degesch look like the screenshot?
-----------------------------------------------
First of all, you must build it with Lua support. With the defaults, degesch
How do I make xC look like the screenshot?
------------------------------------------
First of all, you must build it with Lua support. With the defaults, 'xC'
doesn't look too fancy because I don't want to depend on Lua or 256-colour
terminals. In addition to that, I appear to be one of the few people who use
black on white terminals.
@@ -191,5 +195,5 @@ License
This software is released under the terms of the 0BSD license, the text of which
is included within the package along with the list of authors.
Note that 'degesch' becomes GPL-licensed when you link it against GNU Readline
Note that 'xC' becomes GPL-licensed when you link it against GNU Readline,
but that is not a concern of this source package. The licenses are compatible.

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env guile
ZyklonB calc plugin, basic Scheme evaluator
xB calc plugin, basic Scheme evaluator
Copyright 2016 Přemysl Eric Janouch
See the file LICENSE for licensing information.
@@ -77,7 +77,7 @@
(substring line 0 (- len 1)) line))))
(define (get-config name)
(send "ZYKLONB get_config :" name)
(send "XB get_config :" name)
(car (message-params (parse-message (get-line-crlf irc-input-port)))))
(define (extract-nick prefix)
@@ -216,7 +216,7 @@
; --- Main loop ----------------------------------------------------------------
(define prefix (get-config "prefix"))
(send "ZYKLONB register")
(send "XB register")
(define (process msg)
(when (string-ci=? (message-command msg) "PRIVMSG")

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env tclsh
#
# ZyklonB coin plugin, random number-based utilities
# xB coin plugin, random number-based utilities
#
# Copyright 2012, 2014 Přemysl Eric Janouch
# See the file LICENSE for licensing information.
@@ -37,7 +37,7 @@ proc parse {line} {
proc get_config {key} {
global msg
puts "ZYKLONB get_config :$key"
puts "XB get_config :$key"
gets stdin line
parse $line
return [lindex $msg(param) 0]
@@ -53,7 +53,7 @@ fconfigure stdin -translation crlf -encoding iso8859-1
fconfigure stdout -translation crlf -encoding iso8859-1
set prefix [get_config prefix]
puts "ZYKLONB register"
puts "XB register"
set eightball [list \
"It is certain" \

View File

@@ -1,6 +1,6 @@
#!/usr/bin/awk -f
#
# ZyklonB eval plugin, LISP-like expression evaluator
# xB eval plugin, LISP-like expression evaluator
#
# Copyright 2013, 2014 Přemysl Eric Janouch
# See the file LICENSE for licensing information.
@@ -15,7 +15,7 @@ BEGIN \
prefix = get_config("prefix")
print "ZYKLONB register"
print "XB register"
fflush("")
# All functions have to be in this particular array
@@ -258,7 +258,7 @@ function process_end ()
function get_config (key)
{
print "ZYKLONB get_config :" key
print "XB get_config :" key
fflush("")
getline

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env perl
#
# ZyklonB factoids plugin
# xB factoids plugin
#
# Copyright 2016 Přemysl Eric Janouch <p@janouch.name>
# See the file LICENSE for licensing information.
@@ -24,18 +24,18 @@ sub parse ($) {
}
sub bot_print {
print "ZYKLONB print :${\shift}";
print "XB print :${\shift}";
}
# --- Initialization -----------------------------------------------------------
my %config;
for my $name (qw(prefix)) {
print "ZYKLONB get_config :$name";
print "XB get_config :$name";
$config{$name} = (parse <STDIN>)->{args}->[0];
}
print "ZYKLONB register";
print "XB register";
# --- Database -----------------------------------------------------------------
# Simple map of (factoid_name => [definitions]); all factoids are separated

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env ruby
# coding: utf-8
#
# ZyklonB pomodoro plugin
# xB pomodoro plugin
#
# Copyright 2015 Přemysl Eric Janouch
# See the file LICENSE for licensing information.
@@ -206,7 +206,7 @@ def parse (line)
end
def bot_print (what)
print "ZYKLONB print :#{what}"
print "XB print :#{what}"
end
# --- Initialization -----------------------------------------------------------
@@ -215,12 +215,12 @@ end
# To read it from anywhere else, it has to be done asynchronously
$config = {}
[:prefix].each do |name|
print "ZYKLONB get_config :#{name}"
print "XB get_config :#{name}"
_, _, _, _, args = *parse($stdin.gets.chomp)
$config[name] = args[0]
end
print "ZYKLONB register"
print "XB register"
# --- Plugin logic -------------------------------------------------------------

View File

@@ -1,6 +1,6 @@
#!/usr/bin/tcc -run -lm
//
// ZyklonB scripting plugin, using a custom stack-based language
// xB scripting plugin, using a custom stack-based language
//
// Copyright 2014 Přemysl Eric Janouch
// See the file LICENSE for licensing information.
@@ -1964,12 +1964,12 @@ read_message (void)
// --- Interfacing with the bot ------------------------------------------------
#define BOT_PRINT "ZYKLONB print :script: "
#define BOT_PRINT "XB print :script: "
static const char *
get_config (const char *key)
{
printf ("ZYKLONB get_config :%s\r\n", key);
printf ("XB get_config :%s\r\n", key);
struct message *msg = read_message ();
if (!msg || msg->n_params <= 0)
exit (EXIT_FAILURE);
@@ -2298,7 +2298,7 @@ main (int argc, char *argv[])
printf (BOT_PRINT "%s\r\n", "runtime library initialization failed");
g_prefix = strdup (get_config ("prefix"));
printf ("ZYKLONB register\r\n");
printf ("XB register\r\n");
struct message *msg;
while ((msg = read_message ()))
process_message (msg);

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env lua
--
-- ZyklonB seen plugin
-- xB seen plugin
--
-- Copyright 2016 Přemysl Eric Janouch <p@janouch.name>
-- See the file LICENSE for licensing information.
@@ -26,7 +26,7 @@ function parse (line)
end
function get_config (name)
io.write ("ZYKLONB get_config :", name, "\r\n")
io.write ("XB get_config :", name, "\r\n")
return parse (io.read ()).params[1]
end
@@ -34,7 +34,7 @@ end
io.output ():setvbuf ('line')
local prefix = get_config ('prefix')
io.write ("ZYKLONB register\r\n")
io.write ("XB register\r\n")
local db = {}
local db_filename = "seen.db"

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env perl
# Creates a database for the "seen" plugin from logs for degesch.
# Creates a database for the "seen" plugin from logs for xC.
# The results may not be completely accurate but are good for jumpstarting.
# Usage: ./seen-import-degesch.pl LOG-FILE... > seen.db
# Usage: ./seen-import-xC.pl LOG-FILE... > seen.db
use strict;
use warnings;

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
# ZyklonB YouTube plugin, displaying info about YouTube links
# xB YouTube plugin, displaying info about YouTube links
#
# Copyright 2014 - 2015, Přemysl Eric Janouch <p@janouch.name>
# See the file LICENSE for licensing information.
@@ -27,12 +27,12 @@ class Plugin:
return (nick, user, host, command, args)
def get_config (self, key):
print ("ZYKLONB get_config :%s" % key)
print ("XB get_config :%s" % key)
(_, _, _, _, args) = self.parse (sys.stdin.readline ())
return args[0]
def bot_print (self, what):
print ('ZYKLONB print :%s' % what)
print ('XB print :%s' % what)
class YouTube (Plugin):
re_videos = [re.compile (x) for x in [
@@ -98,7 +98,7 @@ class YouTube (Plugin):
if self.youtube_api_key == "":
self.bot_print ("youtube: missing `youtube_api_key'")
print ("ZYKLONB register")
print ("XB register")
for line in sys.stdin:
self.process_line (line)

View File

@@ -16,7 +16,7 @@
--
local timeout
degesch.setup_config {
xC.setup_config {
timeout = {
type = "integer",
comment = "auto rejoin timeout",
@@ -31,9 +31,9 @@ degesch.setup_config {
},
}
async, await = degesch.async, coroutine.yield
degesch.hook_irc (function (hook, server, line)
local msg = degesch.parse (line)
async, await = xC.async, coroutine.yield
xC.hook_irc (function (hook, server, line)
local msg = xC.parse (line)
if msg.command ~= "KICK" then return line end
local who = msg.prefix:match ("^[^!]*")

View File

@@ -1,7 +1,7 @@
--
-- censor.lua: black out certain users' messages
--
-- Copyright (c) 2016, Přemysl Eric Janouch <p@janouch.name>
-- Copyright (c) 2016 - 2021, Přemysl Eric Janouch <p@janouch.name>
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted.
@@ -38,25 +38,42 @@ local read_masks = function (v)
end
end
degesch.setup_config {
local quote
xC.setup_config {
masks = {
type = "string_array",
default = "\"\"",
comment = "user masks (optionally \"/#channel\") to censor",
on_change = read_masks
},
quote = {
type = "string",
default = "\"\\x0301,01\"",
comment = "formatting prefix for censored messages",
on_change = function (v) quote = v end
},
}
local decolor = function (text)
local rebuilt, last = {""}, 1
for start in text:gmatch ('()\x03') do
table.insert (rebuilt, text:sub (last, start - 1))
local sub = text:sub (start + 1)
last = start + (sub:match ('^%d%d?,%d%d?()') or sub:match ('^%d?%d?()'))
end
return table.concat (rebuilt) .. text:sub (last)
end
local censor = function (line)
-- Taking a shortcut to avoid lengthy message reassembly
local start, text = line:match ("^(.- PRIVMSG .- :)(.*)$")
local ctcp, rest = text:match ("^(\x01%g+ )(.*)")
text = ctcp and ctcp .. "\x0301,01" .. rest or "\x0301,01" .. text
text = ctcp and ctcp .. quote .. decolor (rest) or quote .. decolor (text)
return start .. text
end
degesch.hook_irc (function (hook, server, line)
local msg = degesch.parse (line)
xC.hook_irc (function (hook, server, line)
local msg = xC.parse (line)
if msg.command ~= "PRIVMSG" then return line end
local channel = msg.params[1]:lower ()

View File

@@ -28,15 +28,15 @@
-- background but to really fix that mode, we'd have to fully reimplement it
-- since its alternative prompt very often gets overriden by accident anyway.
degesch.hook_prompt (function (hook)
local current = degesch.current_buffer
xC.hook_prompt (function (hook)
local current = xC.current_buffer
local chan = current.channel
local s = current.server
local bg_color = "255"
local current_n = 0
local active = ""
for i, buffer in ipairs (degesch.buffers) do
for i, buffer in ipairs (xC.buffers) do
if buffer == current then
current_n = i
elseif buffer.new_messages_count ~= buffer.new_unimportant_count then
@@ -61,7 +61,7 @@ degesch.hook_prompt (function (hook)
end
if current.hide_unimportant then x = x .. "<H>" end
local lines, cols = degesch.get_screen_size ()
local lines, cols = xC.get_screen_size ()
x = x .. " " .. active .. string.rep (" ", cols)
-- Readline 7.0.003 seems to be broken and completely corrupts the prompt.

View File

@@ -23,7 +23,7 @@ local cjson = require "cjson"
-- Setup configuration to load last.fm API credentials from
local user, api_key
degesch.setup_config {
xC.setup_config {
user = {
type = "string",
comment = "last.fm username",
@@ -117,7 +117,7 @@ end
local running
-- Initiate a connection to last.fm servers
async, await = degesch.async, coroutine.yield
async, await = xC.async, coroutine.yield
local make_request = function (buffer, action)
if not user or not api_key then
report_error (buffer, "configuration is incomplete")
@@ -159,7 +159,7 @@ local send_song = function (buffer)
end
-- Hook input to simulate new commands
degesch.hook_input (function (hook, buffer, input)
xC.hook_input (function (hook, buffer, input)
if input == "/np" then
make_request (buffer, function (np)
now_playing = np

View File

@@ -15,8 +15,8 @@
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
degesch.hook_irc (function (hook, server, line)
local msg = degesch.parse (line)
xC.hook_irc (function (hook, server, line)
local msg = xC.parse (line)
local start, timeout = line:match ("^(.* :Ping timeout:) (%d+) seconds$")
if msg.command ~= "QUIT" or not start then
return line

View File

@@ -16,7 +16,7 @@
--
local smallest, highlight = 0, "\x1f"
degesch.setup_config {
xC.setup_config {
smallest = {
type = "integer",
default = "0",
@@ -62,7 +62,7 @@ end
-- XXX: sadly it won't typically highlight primes in our own messages,
-- unless IRCv3 echo-message is on
degesch.hook_irc (function (hook, server, line)
xC.hook_irc (function (hook, server, line)
local start, message = line:match ("^(.- PRIVMSG .- :)(.*)$")
return message and start .. do_message (message) or line
end)

View File

@@ -51,7 +51,7 @@ local load_emoji = function (extra)
for k, v in extra:gmatch "([^,]+) ([^,]+)" do emoji[k] = v end
end
degesch.setup_config {
xC.setup_config {
servers = {
type = "string_array",
default = "\"\"",
@@ -74,8 +74,8 @@ degesch.setup_config {
-- We can handle external messages about what we've supposedly sent just fine,
-- so let's get rid of that "[username] some message sent from the web UI" crap
degesch.hook_irc (function (hook, server, line)
local msg, us = degesch.parse (line), server.user
xC.hook_irc (function (hook, server, line)
local msg, us = xC.parse (line), server.user
if not servers[server.name] or msg.command ~= "PRIVMSG" or not us
or msg.params[1]:lower () ~= us.nickname:lower () then return line end
@@ -88,7 +88,7 @@ degesch.hook_irc (function (hook, server, line)
end)
-- Unfuck emoji and :nick!nick@irc.tinyspeck.com MODE #channel +v nick : active
degesch.hook_irc (function (hook, server, line)
xC.hook_irc (function (hook, server, line)
if not servers[server.name] then return line end
if unemojify then
local start, text = line:match ("^(.- PRIVMSG .- :)(.*)$")
@@ -101,7 +101,7 @@ degesch.hook_irc (function (hook, server, line)
end)
-- The gateway simply ignores the NAMES command altogether
degesch.hook_input (function (hook, buffer, input)
xC.hook_input (function (hook, buffer, input)
if not buffer.channel or not servers[buffer.server.name]
or not input:match "^/names%s*$" then return input end
@@ -119,9 +119,9 @@ degesch.hook_input (function (hook, buffer, input)
buffer:log (names)
end)
degesch.hook_completion (function (hook, data, word)
local chan = degesch.current_buffer.channel
local server = degesch.current_buffer.server
xC.hook_completion (function (hook, data, word)
local chan = xC.current_buffer.channel
local server = xC.current_buffer.server
if not chan or not servers[server.name] then return end
-- In /commands there is typically no desire at all to add the at sign

View File

@@ -52,11 +52,11 @@ local do_text = function (text)
return text:gsub ('%f[%g]https?://%g+', do_single_url)
end
degesch.hook_irc (function (hook, server, line)
xC.hook_irc (function (hook, server, line)
local start, message = line:match ("^(.* :)(.*)$")
return message and start .. do_text (message) or line
end)
degesch.hook_input (function (hook, buffer, input)
xC.hook_input (function (hook, buffer, input)
return do_text (input)
end)

8
test
View File

@@ -1,14 +1,14 @@
#!/usr/bin/expect -f
# Very basic end-to-end testing for Travis CI
# Very basic end-to-end testing for CI
# Run the daemon to test against
system ./kike --write-default-cfg
spawn ./kike -d
system ./xD --write-default-cfg
spawn ./xD -d
# 10 seconds is a bit too much
set timeout 5
spawn ./degesch
spawn ./xC
# Fuck this Tcl shit, I want the exit code
expect_after {

26
test-nick-colors Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/sh
# Check whether the terminal colours filtered by our algorithm are legible
export example=$(
tcc "-run -lm" - <<-END
#include <stddef.h>
#include <stdio.h>
#include <math.h>
#define N_ELEMENTS(a) (sizeof (a) / sizeof ((a)[0]))
$(perl -0777 -ne 'print $& if /^.*?\nfilter_color(?s:.*?)^}$/m' \
"$(dirname "$0")"/xC.c)
void main () {
size_t len = 0;
int *table = filter_color_cube_for_acceptable_nick_colors (&len);
for (size_t i = 0; i < len; i++)
printf ("<@\\x1b[38;5;%dmIRCuser\\x1b[m> I'm typing!\n", table[i]);
}
END
)
# Both should give acceptable results,
# which results in a bad compromise that the main author himself needs
xterm -bg black -fg white -e 'echo $example; cat' &
xterm -bg white -fg black -e 'echo $example; cat' &

View File

@@ -1,7 +1,7 @@
#!/bin/sh
# We don't use printf's percent notation with our custom logging mechanism,
# so the compiler cannot check it for us like it usually does
perl -n0777 - "$(dirname "$0")"/degesch.c <<-'END'
perl -n0777 - "$(dirname "$0")"/xC.c <<-'END'
while (/\blog_[^ ]+\s*\([^"()]*"[^"]*%[^%][^"]*"/gm) {
my ($p, $m) = ($`, $&);
printf "$ARGV:%d: suspicious log format string: %s...\n",

View File

@@ -1,20 +1,20 @@
zyklonb(1)
==========
xB(1)
=====
:doctype: manpage
:manmanual: uirc3 Manual
:mansource: uirc3 {release-version}
Name
----
zyklonb - modular IRC bot
xB - modular IRC bot
Synopsis
--------
*zyklonb* [_OPTION_]...
*xB* [_OPTION_]...
Description
-----------
*zyklonb* is a modular IRC bot with a programming language-agnostic plugin
*xB* is a modular IRC bot with a programming language-agnostic plugin
architecture based on co-processes.
Options
@@ -57,24 +57,24 @@ Plugins
-------
Plugins communicate with the bot over their standard input and output streams
using the IRC protocol. (Caveat: the standard C library doesn't automatically
flush FILE streams for pipes on newlines.) A special *ZYKLONB* command is
introduced for RPC, with the following subcommands:
flush FILE streams for pipes on newlines.) A special *XB* command is introduced
for RPC, with the following subcommands:
*ZYKLONB get_config* _key_::
*XB get_config* _key_::
Request the value of the given configuration option. If no such option
exists, the value will be empty. The response will be delivered in
the following format:
+
```
ZYKLONB :value
XB :value
```
+
This is particularly useful for retrieving the *prefix* string.
*ZYKLONB print* _message_::
*XB print* _message_::
Make the bot print the _message_ on its standard output.
*ZYKLONB register*::
*XB register*::
Once a plugin issues this command, it will start receiving all of the bot's
incoming IRC traffic, which includes data from the initialization period.
@@ -82,19 +82,19 @@ All other commands will be forwarded directly to the IRC server.
Files
-----
*zyklonb* follows the XDG Base Directory Specification.
*xB* follows the XDG Base Directory Specification.
_~/.config/zyklonb/zyklonb.conf_::
_~/.config/xB/xB.conf_::
The bot's configuration file. Use the *--write-default-cfg* option
to create a new one for editing.
_~/.local/share/zyklonb/_::
_~/.local/share/xB/_::
The initial working directory for plugins, in which they may create private
databases or other files as needed.
_~/.local/share/zyklonb/plugins/_::
_/usr/local/share/zyklonb/plugins/_::
_/usr/share/zyklonb/plugins/_::
_~/.local/share/xB/plugins/_::
_/usr/local/share/xB/plugins/_::
_/usr/share/xB/plugins/_::
Plugins are searched for in these directories, in order, unless
the *plugin_dir* configuration option overrides this.

View File

@@ -1,5 +1,5 @@
/*
* zyklonb.c: a modular IRC bot
* xB.c: a modular IRC bot
*
* Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name>
*
@@ -17,7 +17,7 @@
*/
#include "config.h"
#define PROGRAM_NAME "ZyklonB"
#define PROGRAM_NAME "xB"
#include "common.c"
@@ -25,9 +25,9 @@
static struct simple_config_item g_config_table[] =
{
{ "nickname", "ZyklonB", "IRC nickname" },
{ "nickname", "xB", "IRC nickname" },
{ "username", "bot", "IRC user name" },
{ "realname", "ZyklonB IRC bot", "IRC real name/e-mail" },
{ "realname", "xB IRC bot", "IRC real name/e-mail" },
{ "irc_host", NULL, "Address of the IRC server" },
{ "irc_port", "6667", "Port of the IRC server" },
@@ -735,7 +735,7 @@ setup_recovery_handler (struct bot_context *ctx, struct error **e)
// --- Plugins -----------------------------------------------------------------
/// The name of the special IRC command for interprocess communication
static const char *plugin_ipc_command = "ZYKLONB";
static const char *plugin_ipc_command = "XB";
static struct plugin *
plugin_find_by_pid (struct bot_context *ctx, pid_t pid)
@@ -1860,7 +1860,7 @@ on_plugin_death (struct plugin *plugin, int status)
struct bot_context *ctx = plugin->ctx;
// TODO: callbacks on children death, so that we may tell the user
// "plugin `name' died like a dirty jewish pig"; use `status'
// "plugin `name' died"; use `status'
if (!plugin->is_zombie && WIFSIGNALED (status))
{
const char *notes = "";

View File

@@ -1,22 +1,22 @@
degesch(1)
==========
xC(1)
=====
:doctype: manpage
:manmanual: uirc3 Manual
:mansource: uirc3 {release-version}
Name
----
degesch - terminal-based IRC client
xC - terminal-based IRC client
Synopsis
--------
*degesch* [_OPTION_]...
*xC* [_OPTION_]...
Description
-----------
*degesch* is a scriptable IRC client for the command line. On the first run
it will welcome you with an introductory message. Should you ever get lost,
use the */help* command to obtain more information on commands or options.
*xC* is a scriptable IRC client for the command line. On the first run it will
welcome you with an introductory message. Should you ever get lost, use the
*/help* command to obtain more information on commands or options.
Options
-------
@@ -26,7 +26,7 @@ Options
database:
+
```
printf '\x02bold\x02\n' | degesch -f
printf '\x02bold\x02\n' | xC -f
```
+
This feature may be used to preview server MOTD files.
@@ -98,18 +98,18 @@ Environment
Files
-----
*degesch* follows the XDG Base Directory Specification.
*xC* follows the XDG Base Directory Specification.
_~/.config/degesch/degesch.conf_::
_~/.config/xC/xC.conf_::
The program's configuration file. Preferrably use internal facilities, such
as the */set* command, to make changes in it.
_~/.local/share/degesch/logs/_::
_~/.local/share/xC/logs/_::
When enabled by *behaviour.logging*, log files are stored here.
_~/.local/share/degesch/plugins/_::
_/usr/local/share/degesch/plugins/_::
_/usr/share/degesch/plugins/_::
_~/.local/share/xC/plugins/_::
_/usr/local/share/xC/plugins/_::
_/usr/share/xC/plugins/_::
Plugins are searched for in these directories, in order.
Bugs

View File

@@ -1,7 +1,7 @@
/*
* degesch.c: a terminal-based IRC client
* xC.c: a terminal-based IRC client
*
* Copyright (c) 2015 - 2020, Přemysl Eric Janouch <p@janouch.name>
* Copyright (c) 2015 - 2021, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@@ -46,11 +46,12 @@ enum
#define print_warning_data ((void *) ATTR_WARNING)
#include "config.h"
#define PROGRAM_NAME "degesch"
#define PROGRAM_NAME "xC"
#include "common.c"
#include "kike-replies.c"
#include "xD-replies.c"
#include <math.h>
#include <langinfo.h>
#include <locale.h>
#include <pwd.h>
@@ -458,6 +459,10 @@ input_rl_start (void *input, const char *program_name)
rl_completer_word_break_characters = NULL;
rl_attempted_completion_function = app_readline_completion;
// We shouldn't produce any duplicates that the library would help us
// autofilter, and we don't generally want alphabetic ordering at all
rl_sort_completion_matches = false;
hard_assert (self->prompt != NULL);
// The inputrc is read before any callbacks are called, so we need to
// register all functions that our user may want to map up front
@@ -1718,8 +1723,10 @@ struct server
char *irc_user_host; ///< Our current user@host
bool autoaway_active; ///< Autoaway is currently active
struct strv cap_ls_buf; ///< Buffer for IRCv3.2 CAP LS
bool cap_echo_message; ///< Whether the server echoes messages
bool cap_away_notify; ///< Whether we get AWAY notifications
bool cap_sasl; ///< Whether SASL is available
// Server-specific information (from RPL_ISUPPORT):
@@ -1730,6 +1737,9 @@ struct server
char *irc_idchan_prefixes; ///< Prefixes for "safe channels"
char *irc_statusmsg; ///< Prefixes for channel targets
char irc_extban_prefix; ///< EXTBAN prefix or \0
char *irc_extban_types; ///< EXTBAN types
char *irc_chanmodes_list; ///< Channel modes for lists
char *irc_chanmodes_param_always; ///< Channel modes with mandatory param
char *irc_chanmodes_param_when_set; ///< Channel modes with param when set
@@ -1778,6 +1788,9 @@ server_init_specifics (struct server *self)
self->irc_idchan_prefixes = xstrdup ("");
self->irc_statusmsg = xstrdup ("");
self->irc_extban_prefix = 0;
self->irc_extban_types = xstrdup ("");
self->irc_chanmodes_list = xstrdup ("b");
self->irc_chanmodes_param_always = xstrdup ("k");
self->irc_chanmodes_param_when_set = xstrdup ("l");
@@ -1796,6 +1809,8 @@ server_free_specifics (struct server *self)
free (self->irc_idchan_prefixes);
free (self->irc_statusmsg);
free (self->irc_extban_types);
free (self->irc_chanmodes_list);
free (self->irc_chanmodes_param_always);
free (self->irc_chanmodes_param_when_set);
@@ -1841,6 +1856,7 @@ server_new (struct poller *poller)
self->irc_user_mode = str_make ();
self->cap_ls_buf = strv_make ();
server_init_specifics (self);
return self;
}
@@ -1887,6 +1903,7 @@ server_destroy (struct server *self)
str_free (&self->irc_user_mode);
free (self->irc_user_host);
strv_free (&self->cap_ls_buf);
server_free_specifics (self);
free (self);
}
@@ -2104,17 +2121,23 @@ filter_color_cube_for_acceptable_nick_colors (size_t *len)
// This is a pure function and we don't use threads, static storage is fine
static int table[6 * 6 * 6];
size_t len_counter = 0;
for (int x = 0; x < 6 * 6 * 6; x++)
for (int x = 0; x < (int) N_ELEMENTS (table); x++)
{
// FIXME this isn't exactly right, the values aren't linear
int r = x / 36;
int g = (x / 6) % 6;
int b = (x % 6);
// Use the luma value of colours within the cube to filter colours that
// look okay-ish on terminals with both black and white backgrounds
double luma = 0.2126 * r / 6. + 0.7152 * g / 6. + 0.0722 * b / 6.;
if (luma >= .3 && luma <= .5)
// The first step is 95/255, the rest are 40/255,
// as an approximation we can double the first step
double linear_R = pow ((r + !!r) / 6., 2.2);
double linear_G = pow ((g + !!g) / 6., 2.2);
double linear_B = pow ((b + !!b) / 6., 2.2);
// Use the relative luminance of colours within the cube to filter
// colours that look okay-ish on terminals with both black and white
// backgrounds (use the test-nick-colors script to calibrate)
double Y = 0.2126 * linear_R + 0.7152 * linear_G + 0.0722 * linear_B;
if (Y >= .25 && Y <= .4)
table[len_counter++] = 16 + x;
}
*len = len_counter;
@@ -2337,7 +2360,7 @@ static struct config_schema g_config_server[] =
.type = CONFIG_ITEM_STRING_ARRAY,
.validate = config_validate_nonjunk_string,
.default_ = "\"multi-prefix,invite-notify,server-time,echo-message,"
"message-tags,away-notify\"" },
"message-tags,away-notify,cap-notify,chghost\"" },
{ .name = "tls",
.comment = "Whether to use TLS",
@@ -3028,6 +3051,20 @@ irc_skip_statusmsg (struct server *s, const char *target)
return target + (*target && strchr (s->irc_statusmsg, *target));
}
static bool
irc_is_extban (struct server *s, const char *target)
{
// Some servers have a prefix, and some support negation
if (s->irc_extban_prefix && *target++ != s->irc_extban_prefix)
return false;
if (*target == '~')
target++;
// XXX: we don't know if it's supposed to have an argument, or not
return *target && strchr (s->irc_extban_types, *target++)
&& strchr (":\0", *target);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// As of 2020, everything should be in UTF-8. And if it's not, we'll decode it
@@ -4031,6 +4068,13 @@ log_full (struct app_context *ctx, struct server *s, struct buffer *buffer,
log_server ((s), (buffer), BUFFER_LINE_STATUS | BUFFER_LINE_UNIMPORTANT, \
"#n is now known as #n", (old), (new_))
#define log_chghost_self(s, buffer, new_) \
log_server ((s), (buffer), BUFFER_LINE_STATUS | BUFFER_LINE_UNIMPORTANT, \
"You are now #N", (new_))
#define log_chghost(s, buffer, old, new_) \
log_server ((s), (buffer), BUFFER_LINE_STATUS | BUFFER_LINE_UNIMPORTANT, \
"#N is now #N", (old), (new_))
#define log_outcoming_notice(s, buffer, who, text) \
log_server_status ((s), (buffer), "#s(#n): #m", "Notice", (who), (text))
#define log_outcoming_privmsg(s, buffer, prefixes, who, text) \
@@ -4083,6 +4127,13 @@ buffer_open_log_file (struct app_context *ctx, struct buffer *buffer)
return;
// TODO: should we try to reopen files wrt. case mapping?
// - Need to read the whole directory and look for matches:
// irc_server_strcmp(buffer->s, d_name, make_log_filename())
// remember to strip the ".log" suffix from d_name, case-sensitively.
// - The tolower_ascii() in make_log_filename() is a perfect overlap,
// it may stay as-is.
// - buffer_get_log_path() will need to return a FILE *,
// or an error that includes the below message.
char *path = buffer_get_log_path (buffer);
if (!(buffer->log_file = fopen (path, "ab")))
log_global_error (ctx, "Couldn't open log file `#s': #l",
@@ -4956,7 +5007,10 @@ irc_destroy_state (struct server *s)
str_reset (&s->irc_user_mode);
cstr_set (&s->irc_user_host, NULL);
strv_reset (&s->cap_ls_buf);
s->cap_away_notify = false;
s->cap_echo_message = false;
s->cap_sasl = false;
// Need to call this before server_init_specifics()
irc_set_casemapping (s, irc_tolower, irc_strxfrm);
@@ -5021,14 +5075,17 @@ irc_initiate_disconnect (struct server *s, const char *reason)
}
static void
initiate_quit (struct app_context *ctx, const char *message)
request_quit (struct app_context *ctx, const char *message)
{
if (!ctx->quitting)
{
log_global_status (ctx, "Shutting down");
ctx->quitting = true;
// Hide the user interface
// Disable the user interface
CALL (ctx->input, hide);
}
// Initiate a connection close
struct str_map_iter iter = str_map_iter_make (&ctx->servers);
struct server *s;
while ((s = str_map_iter_next (&iter)))
@@ -5042,7 +5099,6 @@ initiate_quit (struct app_context *ctx, const char *message)
irc_destroy_connector (s);
}
ctx->quitting = true;
try_finish_quit (ctx);
}
@@ -5689,9 +5745,9 @@ irc_register (struct server *s)
const char *realname = get_config_string (s->config, "realname");
hard_assert (username && realname);
// Start IRCv3.1 capability negotiation;
// Start IRCv3 capability negotiation, with up to 3.2 features;
// at worst the server will ignore this or send a harmless error message
irc_send (s, "CAP LS");
irc_send (s, "CAP LS 302");
const char *password = get_config_string (s->config, "password");
if (password)
@@ -6398,11 +6454,11 @@ irc_handle_mode_user (struct server *s, char **params)
static void
irc_handle_sent_cap (struct server *s, const struct irc_message *msg)
{
if (msg->params.len < 2)
if (msg->params.len < 1)
return;
const char *subcommand = msg->params.vector[1];
const char *args = (msg->params.len > 2) ? msg->params.vector[2] : "";
const char *subcommand = msg->params.vector[0];
const char *args = (msg->params.len > 1) ? msg->params.vector[1] : "";
if (!strcasecmp_ascii (subcommand, "REQ"))
log_server_status (s, s->buffer,
"#s: #S", "Capabilities requested", args);
@@ -6519,6 +6575,20 @@ irc_process_sent_message (const struct irc_message *msg, struct server *s)
// --- Input handling ----------------------------------------------------------
static void
irc_handle_authenticate (struct server *s, const struct irc_message *msg)
{
if (msg->params.len < 1)
return;
// Empty challenge -> empty response for e.g. SASL EXTERNAL,
// abort anything else as it doesn't make much sense to let the user do it
if (!strcmp (msg->params.vector[0], "+"))
irc_send (s, "AUTHENTICATE +");
else
irc_send (s, "AUTHENTICATE *");
}
static void
irc_handle_away (struct server *s, const struct irc_message *msg)
{
@@ -6534,6 +6604,58 @@ irc_handle_away (struct server *s, const struct irc_message *msg)
user->away = !!msg->params.len;
}
static void
irc_process_cap_ls (struct server *s)
{
log_server_status (s, s->buffer,
"#s: #&S", "Capabilities supported", strv_join (&s->cap_ls_buf, " "));
struct strv chosen = strv_make ();
struct strv use = strv_make ();
cstr_split (get_config_string (s->config, "capabilities"), ",", true, &use);
// Filter server capabilities for ones we can make use of
for (size_t i = 0; i < s->cap_ls_buf.len; i++)
{
const char *cap = s->cap_ls_buf.vector[i];
size_t cap_name_len = strcspn (cap, "=");
for (size_t k = 0; k < use.len; k++)
if (!strncasecmp_ascii (use.vector[k], cap, cap_name_len))
strv_append_owned (&chosen, xstrndup (cap, cap_name_len));
}
strv_reset (&s->cap_ls_buf);
char *chosen_str = strv_join (&chosen, " ");
strv_free (&chosen);
strv_free (&use);
// XXX: with IRCv3.2, this may end up being too long for one message,
// and we need to be careful with CAP END. One probably has to count
// the number of sent CAP REQ vs the number of received CAP ACK/NAK.
if (s->state == IRC_CONNECTED)
irc_send (s, "CAP REQ :%s", chosen_str);
free (chosen_str);
}
static void
irc_toggle_cap (struct server *s, const char *cap, bool active)
{
if (!strcasecmp_ascii (cap, "echo-message")) s->cap_echo_message = active;
if (!strcasecmp_ascii (cap, "away-notify")) s->cap_away_notify = active;
if (!strcasecmp_ascii (cap, "sasl")) s->cap_sasl = active;
}
static void
irc_try_finish_cap_negotiation (struct server *s)
{
// It does not make sense to do this post-registration, although it would
// not hurt either, as the server must ignore it in that case
if (s->state == IRC_CONNECTED)
irc_send (s, "CAP END");
}
static void
irc_handle_cap (struct server *s, const struct irc_message *msg)
{
@@ -6559,50 +6681,89 @@ irc_handle_cap (struct server *s, const struct irc_message *msg)
active = false;
cap++;
}
if (!strcasecmp_ascii (cap, "echo-message"))
s->cap_echo_message = active;
if (!strcasecmp_ascii (cap, "away-notify"))
s->cap_away_notify = active;
irc_toggle_cap (s, cap, active);
}
irc_send (s, "CAP END");
if (s->cap_sasl && s->transport == &g_transport_tls)
irc_send (s, "AUTHENTICATE EXTERNAL");
else
irc_try_finish_cap_negotiation (s);
}
else if (!strcasecmp_ascii (subcommand, "NAK"))
{
log_server_error (s, s->buffer,
"#s: #S", "Capabilities not acknowledged", args);
irc_send (s, "CAP END");
irc_try_finish_cap_negotiation (s);
}
else if (!strcasecmp_ascii (subcommand, "DEL"))
{
log_server_error (s, s->buffer,
"#s: #S", "Capabilities deleted", args);
for (size_t i = 0; i < v.len; i++)
irc_toggle_cap (s, v.vector[i], false);
}
else if (!strcasecmp_ascii (subcommand, "LS"))
{
log_server_status (s, s->buffer,
"#s: #S", "Capabilities supported", args);
struct strv chosen = strv_make ();
struct strv use = strv_make ();
cstr_split (get_config_string (s->config, "capabilities"),
",", true, &use);
// Filter server capabilities for ones we can make use of
for (size_t i = 0; i < v.len; i++)
if (msg->params.len > 3 && !strcmp (args, "*"))
cstr_split (msg->params.vector[3], " ", true, &s->cap_ls_buf);
else
{
const char *cap = v.vector[i];
for (size_t k = 0; k < use.len; k++)
if (!strcasecmp_ascii (use.vector[k], cap))
strv_append (&chosen, cap);
strv_append_vector (&s->cap_ls_buf, v.vector);
irc_process_cap_ls (s);
}
char *chosen_str = strv_join (&chosen, " ");
strv_free (&chosen);
strv_free (&use);
irc_send (s, "CAP REQ :%s", chosen_str);
free (chosen_str);
}
strv_free (&v);
}
static void
irc_handle_chghost (struct server *s, const struct irc_message *msg)
{
if (!msg->prefix || msg->params.len < 2)
return;
char *nickname = irc_cut_nickname (msg->prefix);
struct user *user = str_map_find (&s->irc_users, nickname);
free (nickname);
if (!user)
return;
char *new_prefix = xstrdup_printf ("%s!%s@%s", user->nickname,
msg->params.vector[0], msg->params.vector[1]);
if (irc_is_this_us (s, msg->prefix))
{
cstr_set (&s->irc_user_host, xstrdup_printf ("%s@%s",
msg->params.vector[0], msg->params.vector[1]));
log_chghost_self (s, s->buffer, new_prefix);
// Log a message in all open buffers on this server
struct str_map_iter iter = str_map_iter_make (&s->irc_buffer_map);
struct buffer *buffer;
while ((buffer = str_map_iter_next (&iter)))
log_chghost_self (s, buffer, new_prefix);
}
else
{
// Log a message in any PM buffer
struct buffer *buffer =
str_map_find (&s->irc_buffer_map, user->nickname);
if (buffer)
log_chghost (s, buffer, msg->prefix, new_prefix);
// Log a message in all channels the user is in
LIST_FOR_EACH (struct user_channel, iter, user->channels)
{
buffer = str_map_find (&s->irc_buffer_map, iter->channel->name);
hard_assert (buffer != NULL);
log_chghost (s, buffer, msg->prefix, new_prefix);
}
}
free (new_prefix);
}
static void
irc_handle_error (struct server *s, const struct irc_message *msg)
{
@@ -6663,8 +6824,13 @@ irc_handle_join (struct server *s, const struct irc_message *msg)
str_map_set (&s->irc_buffer_map, channel->name, buffer);
buffer_add (s->ctx, buffer);
// XXX: this is annoying, consider only doing it a while after /join
char *input = CALL (s->ctx->input, get_line);
if (!*input)
buffer_activate (s->ctx, buffer);
else
buffer->highlighted = true;
free (input);
}
if (irc_is_this_us (s, msg->prefix))
@@ -7104,6 +7270,18 @@ irc_handle_privmsg_text (struct server *s,
char *prefixes = irc_get_privmsg_prefix
(s, str_map_find (&s->irc_users, nickname), target);
// Make autocomplete offer recent speakers first on partial matches
// (note that freshly joined users also move to the front)
struct user *user;
struct channel_user *channel_user;
if (!irc_is_this_us (s, msg->prefix) && buffer->channel
&& (user = str_map_find (&s->irc_users, nickname))
&& (channel_user = irc_channel_get_user (buffer->channel, user)))
{
LIST_UNLINK (buffer->channel->users, channel_user);
LIST_PREPEND (buffer->channel->users, channel_user);
}
// IRCv3.2 echo-message could otherwise cause us to highlight ourselves
if (irc_is_this_us (s, msg->prefix) || !irc_is_highlight (s, text->str))
{
@@ -7236,8 +7414,10 @@ irc_handle_topic (struct server *s, const struct irc_message *msg)
static struct irc_handler g_irc_handlers[] =
{
// This list needs to stay sorted
{ "AUTHENTICATE", irc_handle_authenticate },
{ "AWAY", irc_handle_away },
{ "CAP", irc_handle_cap },
{ "CHGHOST", irc_handle_chghost },
{ "ERROR", irc_handle_error },
{ "INVITE", irc_handle_invite },
{ "JOIN", irc_handle_join },
@@ -7425,20 +7605,35 @@ channel_user_sort_entry_cmp (const void *entry_a, const void *entry_b)
b->channel_user->user->nickname);
}
static void
irc_sort_channel_users (struct channel *channel)
{
size_t n_users = channel->users_len;
struct channel_user_sort_entry entries[n_users], *p = entries;
LIST_FOR_EACH (struct channel_user, iter, channel->users)
{
p->s = channel->s;
p->channel_user = iter;
p++;
}
qsort (entries, n_users, sizeof *entries, channel_user_sort_entry_cmp);
channel->users = NULL;
while (p-- != entries)
LIST_PREPEND (channel->users, p->channel_user);
}
static char *
make_channel_users_list (struct channel *channel)
{
size_t n_users = 0;
LIST_FOR_EACH (struct channel_user, iter, channel->users)
n_users++;
struct channel_user_sort_entry entries[n_users];
size_t i = 0;
size_t n_users = channel->users_len;
struct channel_user_sort_entry entries[n_users], *p = entries;
LIST_FOR_EACH (struct channel_user, iter, channel->users)
{
entries[i].s = channel->s;
entries[i].channel_user = iter;
i++;
p->s = channel->s;
p->channel_user = iter;
p++;
}
qsort (entries, n_users, sizeof *entries, channel_user_sort_entry_cmp);
@@ -7446,7 +7641,7 @@ make_channel_users_list (struct channel *channel)
// Make names of users that are away italicised, constructing a formatter
// and adding a new attribute seems like unnecessary work
struct str list = str_make ();
for (i = 0; i < n_users; i++)
for (size_t i = 0; i < n_users; i++)
{
struct channel_user *channel_user = entries[i].channel_user;
if (channel_user->user->away) str_append_c (&list, '\x1d');
@@ -7499,6 +7694,9 @@ irc_process_names (struct channel *channel)
struct str_map present = str_map_make (NULL);
present.key_xfrm = channel->s->irc_strxfrm;
// Either that, or there is no other inhabitant, and sorting does nothing
bool we_have_just_joined = channel->users_len == 1;
struct strv *updates = &channel->names_buf;
for (size_t i = 0; i < updates->len; i++)
{
@@ -7522,6 +7720,8 @@ irc_process_names (struct channel *channel)
str_map_free (&present);
strv_reset (&channel->names_buf);
if (we_have_just_joined)
irc_sort_channel_users (channel);
if (!channel->show_names_after_who)
irc_process_names_finish (channel);
}
@@ -7784,6 +7984,16 @@ irc_handle_isupport_statusmsg (struct server *s, char *value)
cstr_set (&s->irc_statusmsg, xstrdup (value));
}
static void
irc_handle_isupport_extban (struct server *s, char *value)
{
s->irc_extban_prefix = 0;
if (*value && *value != ',')
s->irc_extban_prefix = *value++;
cstr_set (&s->irc_extban_types, xstrdup (*value == ',' ? ++value : ""));
}
static void
irc_handle_isupport_chanmodes (struct server *s, char *value)
{
@@ -7840,6 +8050,7 @@ dispatch_isupport (struct server *s, const char *name, char *value)
MATCH ("CHANTYPES", irc_handle_isupport_chantypes);
MATCH ("IDCHAN", irc_handle_isupport_idchan);
MATCH ("STATUSMSG", irc_handle_isupport_statusmsg);
MATCH ("EXTBAN", irc_handle_isupport_extban);
MATCH ("CHANMODES", irc_handle_isupport_chanmodes);
MATCH ("MODES", irc_handle_isupport_modes);
@@ -7932,6 +8143,15 @@ irc_process_numeric (struct server *s,
if (irc_handle_rpl_endofwho (s, msg)) buffer = NULL;
break;
case IRC_ERR_NICKLOCKED:
case IRC_RPL_SASLSUCCESS:
case IRC_ERR_SASLFAIL:
case IRC_ERR_SASLTOOLONG:
case IRC_ERR_SASLABORTED:
case IRC_ERR_SASLALREADY:
irc_try_finish_cap_negotiation (s);
break;
case IRC_RPL_LIST:
case IRC_ERR_UNKNOWNCOMMAND:
@@ -7988,8 +8208,8 @@ irc_process_message (const struct irc_message *msg, struct server *s)
irc_sanitize_cut_off_utf8 (&msg->params.vector[msg->params.len - 1]);
// TODO: make use of IRCv3.2 server-time (with fallback to unixtime_msec())
// -> change all calls to log_{server,nick,outcoming,ctcp}*() to take
// an extra argument specifying time
// -> change all calls to log_{server,nick,chghost,outcoming,ctcp}*()
// to take an extra numeric argument specifying time
struct irc_handler key = { .name = msg->command };
struct irc_handler *handler = bsearch (&key, g_irc_handlers,
N_ELEMENTS (g_irc_handlers), sizeof key, irc_handler_cmp_by_name);
@@ -10479,7 +10699,7 @@ lua_plugin_load (struct app_context *ctx, const char *filename,
luaL_checkversion (L);
// Register the degesch library as a singleton with "plugin" as an upvalue
// Register the xC library as a singleton with "plugin" as an upvalue
// (mostly historical, but rather convenient)
luaL_newmetatable (L, lua_ctx_info.name);
lua_pushlightuserdata (L, plugin);
@@ -10916,45 +11136,38 @@ handle_command_buffer (struct handler_args *a)
return result;
}
static bool
replace_string_array
(struct config_item *item, struct strv *array, struct error **e)
{
char *changed = strv_join (array, ",");
struct str tmp = { .str = changed, .len = strlen (changed) };
bool result = config_item_set_from (item,
config_item_string_array (&tmp), e);
free (changed);
return result;
}
static bool
handle_command_set_add
(struct config_item *item, const char *value, struct error **e)
(struct strv *items, const struct strv *values, struct error **e)
{
struct strv items = strv_make ();
if (item->type != CONFIG_ITEM_NULL)
cstr_split (item->value.string.str, ",", false, &items);
if (items.len == 1 && !*items.vector[0])
strv_reset (&items);
// FIXME: handle multiple items properly
bool result = false;
if (strv_find (&items, value) != -1)
error_set (e, "already present in the array: %s", value);
else
for (size_t i = 0; i < values->len; i++)
{
strv_append (&items, value);
result = replace_string_array (item, &items, e);
const char *value = values->vector[i];
if (strv_find (items, values->vector[i]) != -1)
return error_set (e, "already present in the array: %s", value);
strv_append (items, value);
}
strv_free (&items);
return result;
return true;
}
static bool
handle_command_set_remove
(struct config_item *item, const char *value, struct error **e)
(struct strv *items, const struct strv *values, struct error **e)
{
for (size_t i = 0; i < values->len; i++)
{
const char *value = values->vector[i];
ssize_t i = strv_find (items, value);
if (i == -1)
return error_set (e, "not present in the array: %s", value);
strv_remove (items, i);
}
return true;
}
static bool
handle_command_set_modify
(struct config_item *item, const char *value, bool add, struct error **e)
{
struct strv items = strv_make ();
if (item->type != CONFIG_ITEM_NULL)
@@ -10962,18 +11175,23 @@ handle_command_set_remove
if (items.len == 1 && !*items.vector[0])
strv_reset (&items);
// FIXME: handle multiple items properly
bool result = false;
ssize_t i = strv_find (&items, value);
if (i == -1)
error_set (e, "not present in the array: %s", value);
else
struct strv values = strv_make ();
cstr_split (value, ",", false, &values);
bool result = add
? handle_command_set_add (&items, &values, e)
: handle_command_set_remove (&items, &values, e);
if (result)
{
strv_remove (&items, i);
result = replace_string_array (item, &items, e);
char *changed = strv_join (&items, ",");
struct str tmp = { .str = changed, .len = strlen (changed) };
result = config_item_set_from (item,
config_item_string_array (&tmp), e);
free (changed);
}
strv_free (&items);
strv_free (&values);
return result;
}
@@ -10992,10 +11210,8 @@ handle_command_set_assign_item (struct app_context *ctx,
config_item_set_from (item, config_item_clone (new_), &e);
else if (item->schema->type != CONFIG_ITEM_STRING_ARRAY)
error_set (&e, "not a string array");
else if (add)
handle_command_set_add (item, new_->value.string.str, &e);
else if (remove)
handle_command_set_remove (item, new_->value.string.str, &e);
else
handle_command_set_modify (item, new_->value.string.str, add, &e);
if (e)
{
@@ -11241,6 +11457,20 @@ handle_command_notice (struct handler_args *a)
return true;
}
static bool
handle_command_squery (struct handler_args *a)
{
if (!*a->arguments)
return false;
char *target = cut_word (&a->arguments);
if (!*a->arguments)
log_server_error (a->s, a->s->buffer, "No text to send");
else
irc_send (a->s, "SQUERY %s :%s", target, a->arguments);
return true;
}
static bool
handle_command_ctcp (struct handler_args *a)
{
@@ -11280,7 +11510,7 @@ handle_command_me (struct handler_args *a)
static bool
handle_command_quit (struct handler_args *a)
{
initiate_quit (a->ctx, *a->arguments ? a->arguments : NULL);
request_quit (a->ctx, *a->arguments ? a->arguments : NULL);
return true;
}
@@ -11405,7 +11635,8 @@ handle_command_topic (struct handler_args *a)
if (*a->arguments)
// FIXME: there's no way to start the topic with whitespace
// FIXME: there's no way to unset the topic;
// we could adopt the Tcl style of "-switches" with "--" sentinels
// we could adopt the Tcl style of "-switches" with "--" sentinels,
// or we could accept "strings" in the config format
irc_send (a->s, "TOPIC %s :%s", a->channel_name, a->arguments);
else
irc_send (a->s, "TOPIC %s", a->channel_name);
@@ -11484,8 +11715,7 @@ mass_channel_mode_mask_list
for (size_t i = 0; i < v.len; i++)
{
char *target = v.vector[i];
// TODO: support EXTBAN and leave those alone, too
if (strpbrk (target, "!@*?"))
if (strpbrk (target, "!@*?") || irc_is_extban (a->s, target))
continue;
v.vector[i] = xstrdup_printf ("%s!*@*", target);
@@ -11769,11 +11999,17 @@ static bool
handle_command_channel_mode
(struct handler_args *a, bool adding, char mode_char)
{
if (!*a->arguments)
const char *targets = a->arguments;
if (!*targets)
{
if (adding)
return false;
targets = a->s->irc_user->nickname;
}
struct strv v = strv_make ();
cstr_split (a->arguments, " ", true, &v);
cstr_split (targets, " ", true, &v);
mass_channel_mode (a->s, a->channel_name, adding, mode_char, &v);
strv_free (&v);
return true;
@@ -11856,6 +12092,9 @@ g_command_handlers[] =
{ "notice", "Send notice to a nick or channel",
"<target> <message>",
handle_command_notice, HANDLER_SERVER | HANDLER_NEEDS_REG },
{ "squery", "Send a message to a service",
"<service> <message>",
handle_command_squery, HANDLER_SERVER | HANDLER_NEEDS_REG },
{ "ctcp", "Send a CTCP query",
"<target> <tag>",
handle_command_ctcp, HANDLER_SERVER | HANDLER_NEEDS_REG },
@@ -11877,13 +12116,13 @@ g_command_handlers[] =
"<nick>...",
handle_command_op, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
{ "deop", "Remove channel operator status",
"<nick>...",
"[<nick>...]",
handle_command_deop, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
{ "voice", "Give voice",
"<nick>...",
handle_command_voice, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
{ "devoice", "Remove voice",
"<nick>...",
"[<nick>...]",
handle_command_devoice, HANDLER_SERVER | HANDLER_CHANNEL_FIRST },
{ "mode", "Change mode",
@@ -12025,8 +12264,9 @@ handle_command_help (struct handler_args *a)
if (!*a->arguments)
return show_command_list (ctx);
// TODO: we should probably also accept commands names with a leading slash
char *command = cut_word (&a->arguments);
const char *word = cut_word (&a->arguments);
const char *command = word + (*word == '/');
for (size_t i = 0; i < N_ELEMENTS (g_command_handlers); i++)
{
struct command_handler *handler = &g_command_handlers[i];
@@ -12034,13 +12274,13 @@ handle_command_help (struct handler_args *a)
return show_command_help (ctx, handler);
}
if (try_handle_command_help_option (ctx, command))
if (try_handle_command_help_option (ctx, word))
return true;
if (str_map_find (get_aliases_config (ctx), command))
log_global_status (ctx, "/#s is an alias", command);
else
log_global_error (ctx, "#s: #s", "No such command or option", command);
log_global_error (ctx, "#s: #s", "No such command or option", word);
return true;
}
@@ -12115,6 +12355,12 @@ expand_alias_escape (const char *p, const char *arguments, struct str *output)
cstr_split (arguments, " ", true, &words);
// TODO: eventually also add support for argument ranges
// - Can use ${0}, ${0:}, ${:0}, ${1:-1} with strtol, dispose of $1 syntax
// (default aliases don't use numeric arguments).
// - Start numbering from zero, since we'd have to figure out what to do
// in case we encounter a zero if we keep the current approach.
// - Ignore the sequence altogether if no closing '}' can be found,
// or if the internal format doesn't fit the above syntax.
if (*p >= '1' && *p <= '9')
{
size_t offset = *p - '1';
@@ -13466,7 +13712,7 @@ app_editline_init (struct input_el *self)
static const char *g_first_time_help[] =
{
"",
"\x02Welcome to degesch!",
"\x02Welcome to xC!",
"",
"To get a list of all commands, type \x02/help\x02. To obtain",
"more information on a command or option, simply add it as",
@@ -13722,12 +13968,15 @@ on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx)
while (try_reap_child (ctx))
;
if (g_termination_requested && !ctx->quitting)
initiate_quit (ctx, NULL);
if (g_termination_requested)
{
g_termination_requested = false;
request_quit (ctx, NULL);
}
if (g_winch_received)
{
redraw_screen (ctx);
g_winch_received = false;
redraw_screen (ctx);
}
}
@@ -14097,13 +14346,9 @@ main (int argc, char *argv[])
static const char *g_logo[] =
{
" __ __ ",
" __/ /___________________/ / ",
" / / , / / , / __/ __/ _ \\ ",
"/ / / __/ / / __/_ / /_/ // / ",
"\\__/\\__/_ /\\__/___/\\__/_//_/ " PROGRAM_VERSION,
" /___/",
""
"",
"\x02" PROGRAM_NAME "\x02 " PROGRAM_VERSION,
"",
};
static void
@@ -14130,7 +14375,7 @@ format_input_and_die (struct app_context *ctx)
int
main (int argc, char *argv[])
{
// We include a generated file from kike including this array we don't use;
// We include a generated file from xD including this array we don't use;
// let's just keep it there and silence the compiler warning instead
(void) g_default_replies;

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -2,7 +2,7 @@
LC_ALL=C exec awk '
BEGIN {
# The message catalog is a by-product
msg = "kike.msg"
msg = "xD.msg"
print "$quote \"" > msg;
print "$set 1" > msg;
}

View File

@@ -85,3 +85,9 @@
482 IRC_ERR_CHANOPRIVSNEEDED "%s :You're not channel operator"
501 IRC_ERR_UMODEUNKNOWNFLAG ":Unknown MODE flag"
502 IRC_ERR_USERSDONTMATCH ":Cannot change mode for other users"
902 IRC_ERR_NICKLOCKED ":You must use a nick assigned to you"
903 IRC_RPL_SASLSUCCESS ":SASL authentication successful"
904 IRC_ERR_SASLFAIL ":SASL authentication failed"
905 IRC_ERR_SASLTOOLONG ":SASL message too long"
906 IRC_ERR_SASLABORTED ":SASL authentication aborted"
907 IRC_ERR_SASLALREADY ":You have already authenticated using SASL"

View File

@@ -1,20 +1,20 @@
kike(1)
=======
xD(1)
=====
:doctype: manpage
:manmanual: uirc3 Manual
:mansource: uirc3 {release-version}
Name
----
kike - IRC daemon
xD - IRC daemon
Synopsis
--------
*kike* [_OPTION_]...
*xD* [_OPTION_]...
Description
-----------
*kike* is a basic IRC daemon for single-server networks, suitable for testing
*xD* is a basic IRC daemon for single-server networks, suitable for testing
and private use. When run without a configuration file, it will start listening
on the standard port 6667 and the "any" address.
@@ -40,10 +40,10 @@ contrary to what you might expect from a server.
Files
-----
*kike* follows the XDG Base Directory Specification.
*xD* follows the XDG Base Directory Specification.
_~/.config/kike/kike.conf_::
_/etc/xdg/kike/kike.conf_::
_~/.config/xD/xD.conf_::
_/etc/xdg/xD/xD.conf_::
The daemon's configuration file. Use the *--write-default-cfg* option
to create a new one for editing.

View File

@@ -1,5 +1,5 @@
/*
* kike.c: an IRC daemon
* xD.c: an IRC daemon
*
* Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name>
*
@@ -17,11 +17,11 @@
*/
#include "config.h"
#define PROGRAM_NAME "kike"
#define PROGRAM_NAME "xD"
#define WANT_SYSLOG_LOGGING
#include "common.c"
#include "kike-replies.c"
#include "xD-replies.c"
#include <nl_types.h>
enum { PIPE_READ, PIPE_WRITE };