552 Commits

Author SHA1 Message Date
71e1a744c5 xP: embed web resources, tame browser caching
All checks were successful
Alpine 3.21 Success
Arch Linux AUR Success
OpenBSD 7.6 Success
2025-07-09 22:15:13 +02:00
80af5c22d6 Add an xC relay protocol analyzer
All checks were successful
Alpine 3.21 Success
Arch Linux AUR Success
OpenBSD 7.6 Success
2025-05-15 14:14:53 +02:00
7ba17a0161 Make the relay acknowledge all received commands
All checks were successful
Alpine 3.21 Success
Arch Linux AUR Success
OpenBSD 7.6 Success
To that effect, bump liberty and the xC relay protocol version.
Relay events have been reordered to improve forward compatibility.

Also prevent use-after-free when serialization fails.

xP now slightly throttles activity notifications,
and indicates when there are unacknowledged commands.
2025-05-10 12:08:51 +02:00
4cf8c394b9 xA: bump Fyne to 2.6.0
All checks were successful
Arch Linux AUR Success
OpenBSD 7.6 Success
Alpine 3.21 Success
Alpine 3.20 Abandoned
Not much has actually changed.
2025-04-26 15:04:49 +02:00
e225306419 CMakeLists.txt: actually name an option 2025-01-12 11:02:41 +01:00
858734384b xC: regard more characters as highlight delimiters
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
Almost 10 years of a poor decision.
2025-01-08 06:40:53 +01:00
278a7f95a9 Port the integration test from expect to wdye 2025-01-08 06:40:52 +01:00
a8575ab875 Bump version, update NEWS
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
A few annoying bugs have been fixed.
2024-12-19 03:15:34 +01:00
081525f5be Update README.adoc 2024-12-19 03:08:14 +01:00
6ac2ac5511 xT: improve MSYS2 build
The static Qt 6 package is unusable.
2024-12-19 03:08:13 +01:00
f6483489c2 xC: fix crash with too many topic formatting items
All checks were successful
Arch Linux AUR Success
OpenBSD 7.5 Success
Alpine 3.20 Success
Manually constructed formatters have no sentinel value.

This is a one-line change in relay_prepare_channel_buffer_update(),
however the whole block of "Relay output" code has been moved down,
resolving one TODO and rendering two function prototypes unnecessary.
2024-12-18 11:48:17 +01:00
ed5ac1815b xT: figure out basic packaging
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
2024-12-17 06:34:45 +01:00
509cb9f4dd xC: fix newer Readline, allow stdin streaming
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
Also update NEWS.
2024-12-17 03:31:00 +01:00
b3684c4d9f Bump liberty
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
2024-12-16 09:37:29 +01:00
918c589c65 Add a Qt Widgets frontend to xC
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
This is very much a work in progress, though functional.

Qt Widgets are basically non-working on Android,
though Qt Quick requires a radically different approach.
2024-12-15 06:57:28 +01:00
21095a11d6 xM: fix build regression
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
2024-11-26 13:10:05 +01:00
a22baa4b55 xA: prevent sound playback GC
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
The beep sound could be cut short.
2024-11-14 16:48:44 +01:00
b3e545e0bb xP: bump copyright years
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
2024-11-14 16:27:56 +01:00
cd76702ab2 xA/xW: dehighlight current buffer appropriately 2024-11-14 16:14:54 +01:00
977b073b58 xA: enforce internal icon from the start 2024-11-14 16:14:49 +01:00
46be4836df xW: print the separator line at the end of buffer 2024-11-14 13:50:51 +01:00
05a41b2629 xA/xM/xW: refresh renamed buffers correctly
Rendering takes the current buffer into account,
so change its value before using it, not afterwards.

The order happened to not matter on at least Windows,
because we just queue a message.
2024-11-14 11:41:09 +01:00
a62ed5bbac xA/xM: refresh buffer list on dehighlight 2024-11-14 11:41:08 +01:00
9c9776bacd xA: make the log effectively read-only
All checks were successful
Arch Linux AUR Success
OpenBSD 7.5 Success
Alpine 3.20 Success
2024-11-13 10:29:11 +01:00
086b879ab8 xA: add a "generate" target to the Makefile
So that Fyne tools can be run without building the default binary.
2024-11-12 17:11:23 +01:00
214c349869 xA: limit buffer length
All checks were successful
Arch Linux AUR Success
OpenBSD 7.5 Success
Alpine 3.20 Success
2024-11-12 16:19:53 +01:00
3d975c9437 xA: downgrade Go version requirement
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
We need 1.22 for the "for" loop variable scope change.
2024-11-12 13:53:55 +01:00
fce8fd40cc Bump xP dependencies
Some checks failed
Alpine 3.20 Scripts failed
Arch Linux AUR Success
OpenBSD 7.5 Success
2024-11-12 12:59:33 +01:00
3597ab9420 Update README.adoc 2024-11-12 12:41:09 +01:00
1635a730e8 Add a Fyne frontend for xC
Some checks failed
Alpine 3.20 Scripts failed
Arch Linux AUR Scripts failed
OpenBSD 7.5 Success
It is fairly mediocre all around, but also generally usable,
natively covering mobile platforms.
2024-11-12 12:02:10 +01:00
a64b1152a1 Bump liberty 2024-11-11 21:42:28 +01:00
a011b57ce2 Bump liberty
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
2024-11-07 11:07:36 +01:00
b1ee295345 xP: update variable name 2024-11-04 07:40:14 +01:00
872f2d7c59 Fix calloc argument order
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
2024-08-08 09:13:25 +02:00
f15d887dcd Bump liberty
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
2024-08-08 09:02:25 +02:00
841bc00c51 xP: cleanup
I had forgotten about the auto-redraw system.
2024-07-28 13:42:28 +02:00
8afe4f8aad Improve wording in the last NEWS entry 2024-07-28 13:26:24 +02:00
73cc8f448a Bump version, update NEWS
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
2024-07-28 07:29:15 +02:00
4565afe294 xC: expand a comment 2024-07-28 07:15:41 +02:00
3ad8c79de8 xC: handle multiline server commands properly
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
Additional lines might have been passed to the server intact
as part of an argument, but we have /quote for that.
2024-07-28 04:10:30 +02:00
12fc3c228a xP: reset highlight state once reaching buffer end
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
2024-07-28 03:44:37 +02:00
175533a5e9 xP: don't interrupt IME composition
All checks were successful
Alpine 3.20 Success
Arch Linux AUR Success
OpenBSD 7.5 Success
On Vivaldi/macOS, pressing Enter would send the input and still keep
editing it as it was.
2024-07-04 20:06:59 +02:00
a9b46141a9 xS/xN: add test targets
All checks were successful
Alpine 3.19 Success
Arch Linux AUR Success
OpenBSD 7.3 Success
2024-04-10 13:59:33 +02:00
c38cca3b92 Bump liberty
All checks were successful
Arch Linux AUR Success
Alpine 3.19 Success
2024-04-09 17:08:40 +02:00
aee7540faa Update README.adoc and xN usage output 2024-04-04 21:25:17 +02:00
53ba996ec9 Add a simple IRC notifier utility
All checks were successful
Arch Linux AUR Success
2024-04-03 15:56:33 +02:00
d450c6cc5f xP: do not send the Referrer header 2024-03-04 16:15:22 +01:00
f8ea1634c4 Bump liberty 2024-03-04 16:15:22 +01:00
ef257cd575 xP: avoid expensive updates/refreshes 2024-01-06 23:44:11 +01:00
69eccc7065 xP: don't let buffers grow indefinitely
Primarily for performance reasons.
2024-01-06 21:17:18 +01:00
13d2ff115b xM: improve the bundle icon a bit 2023-09-04 07:06:03 +02:00
9e4692bb09 xM: generate and use a bundle icon 2023-09-03 02:13:14 +02:00
1c4343058d Add a Cocoa frontend for xC
Some work remains to be done to get it to be even as good
as the Win32 frontend, but it's generally usable.
2023-09-01 01:12:51 +02:00
e5156cddbf xW: render leaked lines a bit more accurately
There is no need to reset all text attributes, just the colour.
2023-08-25 22:48:31 +02:00
34521e61c1 xP/xW: fix buffer rename handling
Maintaining string pointers to the current/last buffer
means that renames invalidate them.
2023-08-25 22:48:31 +02:00
c22dd67fc1 xC: send missing relay events for newly added servers 2023-08-25 22:48:27 +02:00
274d5f03e7 xC: give the /away command a proper handler
Multiple words should be passed to the server as a single argument.
2023-08-25 22:46:43 +02:00
2f19e5a733 xW: improve command sending 2023-07-29 02:15:24 +02:00
b9cdabca5d xC: fix relay handling of missing log files
Intermediate error messages would trash the prepared static buffer.
2023-07-28 04:30:45 +02:00
f60ca43156 xW: do not unnecessarily enter compatibility mode 2023-07-28 04:30:45 +02:00
afe4e61f08 xW: mark a footgun
These messages are used by IsDialogMessage(), and use the WM_USER range.
2023-07-27 23:08:16 +02:00
8d9d1c60ec xW: make Up/Down go through input history
The input field isn't multiline, so this doesn't pose an issue.
Otherwise, we'd have to check if we're on the top line first.
2023-07-27 16:35:54 +02:00
8c1464822b xW: don't delay sending out pongs 2023-07-27 16:19:32 +02:00
fcd1b8e011 xW: improve beeping
This adds yet another build dependency,
but it's better than the alternatives of handling it in code.
2023-07-27 16:06:41 +02:00
3d345987c3 xW: cleanup 2023-07-27 02:37:20 +02:00
3e37efd9cd xW: show a connect dialog when run without args 2023-07-27 01:28:52 +02:00
efb25b8aae xW: un-highlight the icon when activating buffers 2023-07-26 16:07:21 +02:00
e72793e315 xW: make newline before unread marker conditional 2023-07-26 16:07:20 +02:00
5a412ab6e2 xW: handle WM_SYSCOLORCHANGE 2023-07-26 16:07:20 +02:00
81bc578773 xW: add missing date change handling 2023-07-26 03:59:25 +02:00
100de5ac2d xC: fix Readline 6.3 compatibility 2023-07-24 07:59:22 +02:00
c157d3369f xP: make Page Up/Down in editor scroll the buffer
Just like in xW recently.  It is unlikely that the user would want
to use these keys to move the cursor.  Ctrl+Home/End still work,
as does holding Up/Down arrows.

Also stop using the deprecated and somewhat cryptic keyCode.
2023-07-23 00:20:32 +02:00
8b5ea67aff xW: fix Clang build 2023-07-21 12:37:01 +02:00
6f02af814f xW: store the largest program icon in PNG format
This shaves off about half a megabyte.
2023-07-16 08:35:39 +02:00
90859107c8 xW: set version information 2023-07-15 23:35:46 +02:00
0219dbd026 Add a Win32 frontend for xC
This has been more of an exercise.  The performance of Msftedit.dll
is rather abysmal, and its interface isn't the most accomodating.

That said, the frontend is quite usable, at least on Windows 10+.
2023-07-15 17:00:21 +02:00
1da4699a7a Cleanup 2023-07-09 09:35:16 +02:00
9e993c50e6 xC: don't crash with unknown terminals
It would be possible to avoid using cur_term fields in this case,
but the program would likely be of little use anyway.
2023-07-07 10:43:15 +02:00
b3e9218b23 Fix Cygwin build warnings 2023-07-07 09:53:20 +02:00
bc8867eb22 Fix the integration test
IRCv3 capabilities broke it a bit.

Also change it so that it doesn't destroy existing configuration.
2023-07-05 00:16:55 +02:00
ec33adba35 Update README.adoc 2023-07-04 23:50:01 +02:00
6f596f1dcb Move project version to file, add xS manual page
So far Go applications remain independent to handle Nix's inability
to easily combine them with the CMake part.

There is also no "install" target, because any packagers will want to
adjust installation paths manually, and there is no configure step.
2023-07-04 23:26:05 +02:00
abcff46dc4 xC: fix an OpenBSD build warning
sys/cdefs.h makes _XOPEN_SOURCE cause _POSIX_C_SOURCE to be overriden.
2023-07-04 06:31:02 +02:00
8d9ce92758 README.adoc: update package information 2023-07-01 22:02:10 +02:00
4bb9449e47 Fix the static analysis test
Adjust its query so that it doesn't cause a particular false positive.
2023-06-16 19:45:12 +02:00
50f70f93bb xC: fix a harmless copy-paste error 2023-06-13 09:02:20 +02:00
3f9a365d36 xC: improve the --format mode
Avoid having formatting spill over the rest of the line,
by placing the automatic formatting reset before newlines.

Also handle longer lines properly.
2023-05-22 04:44:01 +02:00
9932b35a10 xP: highlight hovered buffer list items
To make it apparent which one would be closed by a middle click.
2023-04-14 10:58:19 +02:00
af5f209c53 xP: make middle click close buffers
As if they were tabs, to save pointless typing.
2023-04-13 04:26:40 +02:00
6bfe577f1b xP: make the buffer list selectable by Vimium 2023-04-05 23:10:41 +02:00
1079189381 xP: render date changes as they happen 2023-01-25 00:31:57 +01:00
c58b772905 xP: use the correct log function 2023-01-25 00:28:03 +01:00
26ed2dbc77 xC: fully synchronize input history with frontends
The missing parts were:

 - frontends to client
 - client to frontends after the initial sync
 - frontend to other frontends
2022-10-05 00:55:59 +02:00
4b7258cba0 xP: fix ESC H detection on Macintosh systems 2022-10-04 20:17:31 +02:00
9dc3dd02f3 xP: disable WebSocket compression on Safari
Wildly known to be broken.
2022-10-04 01:17:35 +02:00
a7c3ed7cc1 xC: clean up 2022-09-30 18:30:03 +02:00
807a8c37d9 Bump liberty, improve fallback manual page output 2022-09-30 18:17:23 +02:00
c4707e2803 xC/xP: send buffer input history during sync
This transfer is currenly quite simplistic,
but it paves the way for further extensions.
2022-09-30 17:36:29 +02:00
46d68eacce Move protocol code generators to liberty
This part of the project is now more or less stable.
2022-09-30 03:24:24 +02:00
86278c154c Clean up protocol code generators 2022-09-30 03:24:13 +02:00
941ee2f10c xP: fix automatic scrolling down
Showing channel logs cancelled the AbortController forever.
Thus store it within vnodes.
2022-09-28 21:29:08 +02:00
5b57e9b41b xC/xP: fix unseen message counting
xC: advance unread message counters even with leaked messages,
and don't unnecessarily set the highlighted flag.  Plus clean up.

xP: make leaked non-unimportant messages advance the counter
for unimportant messages, so that the buffer doesn't get emboldened.
2022-09-28 21:20:59 +02:00
4d99690b89 xS: parse project version from CMakeLists.txt 2022-09-27 23:48:12 +02:00
7c74e6615d xD: use SHA-256 for certificate fingerprints
Just like xS.  2.0.0 is the ideal time for such a breaking change.
2022-09-26 13:58:08 +02:00
614fd98fc1 Update README 2022-09-26 13:42:45 +02:00
5863040f93 Update documentation, clean up 2022-09-26 13:24:24 +02:00
f891e5ca63 Merge hid IRCd from haven as xS
Given that this project already contains a Go binary,
it only makes sense to put the IRCds back together.
2022-09-26 12:41:47 +02:00
8344b09c4f hid: rename to xS before merge into xK 2022-09-26 12:23:58 +02:00
4d50ed111a Bump liberty, make use of its new asciiman.awk 2022-09-25 21:02:51 +02:00
e15c9cba43 xP: use a dotted border for button focus
It's visible enough.
2022-09-25 11:08:50 +02:00
66370387bc Update documentation 2022-09-23 20:35:56 +02:00
2bc3ac7b0d Update screenshots
It's been five years since the previous xC screenshot was made.
2022-09-23 19:00:31 +02:00
2c54f5a5dd xP: make the overlay actually overlay 2022-09-23 19:00:31 +02:00
f2d8de3ab9 xP: support adding formatting from keyboard
Just like in xC, only with some indication.
2022-09-23 09:42:24 +02:00
67d52a2d89 xP: fix up link detection
Allow balanced parantheses at the end of a link.
2022-09-23 09:42:23 +02:00
ef3d1cc409 xP: add formatting buttons
And fix autoscroll autoenabler, as well as toolbar padding.

Only add the basic toggles, which should be well supported.
2022-09-23 09:41:29 +02:00
e6bf88673f xP: produce a custom font for IRC formatting
Given that the generated file needs a manual adjustment,
its small size, and the dependencies involved,
it will be checked in to the repository.
2022-09-22 20:18:55 +02:00
4a2740313c Give up on the X11 frontend for now
There seem to be only a few things it could bring to the table,
compared to xP, making it barely worth the effort:

 - saner keyboard controls,
 - GVIM integration,
 - slightly improved resource usage.
2022-09-21 18:30:25 +02:00
d3628928b9 xC/xP: relay and render channel modes 2022-09-21 16:32:35 +02:00
1f0e0b1ce4 xP: only care about RPC results if requested
This prevents "No response" errors from firing for most commands.
2022-09-21 14:31:16 +02:00
919b12510b xC/xP: relay and render channel topics 2022-09-21 12:15:27 +02:00
414859d309 xP: improve paging 2022-09-21 07:34:17 +02:00
5a7f2d16df xP: clean up DOM attributes 2022-09-21 06:23:59 +02:00
b8061b665d Silence spurious compiler warnings 2022-09-21 05:21:45 +02:00
807bdea2ea Update README 2022-09-20 19:08:14 +02:00
8a689c98b4 xC: fix autocomplete in server buffers 2022-09-20 17:34:01 +02:00
9327333813 xC/xP: show own user's info in frontends 2022-09-20 17:24:30 +02:00
7806d4bd4e xC/xP: improve rendering of highlighting actions 2022-09-20 15:30:07 +02:00
c0e1cd57b2 Make sure to always find installed plugins 2022-09-19 04:26:35 +02:00
00184811ea xP: make the prompt look more xC-like 2022-09-19 03:18:31 +02:00
ec20fdef7b xP: show all completion options 2022-09-18 05:54:23 +02:00
21e5d80ab1 xC: improve Readline completion
The autocomplete for /set used to be extremely annoying,
and menu-complete-display-prefix also prevents mistaken highlights.

One downside is that using plain Tab in channels no longer
just inserts the last-talking nickname, one needs to press it twice.
2022-09-18 02:48:28 +02:00
ff243c1d11 xP: implement Readline's M-l, M-u, M-c 2022-09-18 01:59:11 +02:00
e2ef7d668c xP: implement Readline's M-b and M-f 2022-09-18 01:10:03 +02:00
b979257c3a xP: implement Readline's M-< and M-> 2022-09-18 01:10:03 +02:00
840b646700 xC: reorganize relay code, improve logging
Even with one forward function declaration down,
it was possible to move most code to a more convenient location.

Most logging has thus been fixed to go to buffers.
2022-09-17 00:32:14 +02:00
126105fa4f xC: don't abort on accept() failure
Just disable the relay.
2022-09-17 00:31:23 +02:00
e2f3fc2e79 xC: clean up 2022-09-17 00:31:23 +02:00
b55bae50df Update some documentation 2022-09-16 03:52:49 +02:00
430968e5d5 xP: make non-connected states more apparent 2022-09-16 03:19:48 +02:00
d5153fe354 xC/xP: implement M-H in the web frontend 2022-09-16 02:52:16 +02:00
ee76186bef xP: abandon the idea of a configuration file 2022-09-16 01:21:35 +02:00
6f39aa6615 xP: use the binary protocol for incoming events
And batch event messages together as much as possible.

JSON has proven itself to be really slow
(for example, encoding/json.Marshaler is a slow interface),
and browsers have significant overhead per WS message.

Commands are still sent as JSON, sending them in binary
would be a laborious rewrite without measurable merits.

The xP server now only prints debug output when requested,
because that was another source of major slowdowns.
2022-09-16 00:51:11 +02:00
e87cc90b5e xP: improve comments in protocol code generator 2022-09-15 05:12:07 +02:00
98b0a4ef3d xP: further optimize JSON marshalling 2022-09-15 03:16:16 +02:00
9cf44aa4dd xP: speed up log decoding 2022-09-15 02:32:58 +02:00
b53fc1918f xP: fix log JSON serialization 2022-09-15 01:51:40 +02:00
92f2f6895b xP: use buffered reads 2022-09-14 07:11:05 +02:00
c1d2e38840 xP: generate our own JSON marshallers
For non-trivial types, which are expensive to serialize
with encoding/json's struct reflection.
2022-09-14 06:56:36 +02:00
f89f21a47f xP: pass all events through one handler
This is a mild clean up.
2022-09-14 06:56:36 +02:00
fa85ea8208 xP: parallelize event reception and sending
Still trying to make the frontend load tolerably fast,
still unsuccessfully.
2022-09-14 06:56:36 +02:00
b728235b6c xP: move to a WebSocket package with compression
Compression happens to be broken in Safari,
though luckily there are friendlier browsers one can use.
2022-09-14 06:56:35 +02:00
d31ab67268 xC: mildly optimize relay traffic 2022-09-14 01:01:19 +02:00
b2b3093e0e xP: remove debugging protocol logs from JS 2022-09-14 01:01:10 +02:00
a551e911ab xP: adjust buffer list iteration and styling
M-a and M-! should iterate, rather than keep jumping back
to the same buffers.

The current item wasn't visible enough,
and it jumped around in my 1.5-scale Firefox.
2022-09-13 03:21:41 +02:00
a61789637a xP: deal with macOS/Blink for good 2022-09-12 16:45:29 +02:00
8968100a28 xP: improve favicon behaviour
Make it black when disconnected, and orange when the document
is hidden but the current tab is highlighted.
2022-09-12 03:49:29 +02:00
3b6c29d676 xC: silence some compiler warnings 2022-09-11 22:16:35 +02:00
b4ee523628 xP: bind buffer iteration to M-PageUp/PageDown 2022-09-11 21:50:09 +02:00
c3a52b9e4c xP: indicate hidden buffer lines 2022-09-11 21:50:09 +02:00
96fc12bc4c xC/xP: send buffer type and server state
Also make PM highlighting behaviour consistent.
2022-09-11 21:50:08 +02:00
1493d9998b xC: clean up 2022-09-11 19:11:47 +02:00
36f77e74fb xP: change the favicon when highlighted 2022-09-11 19:10:41 +02:00
23deca45c9 xP: fix non-ASCII text completion 2022-09-11 19:10:40 +02:00
62773acaa0 xP: beep on highlight
800 Hz seems like it could match a POST beep.
2022-09-11 03:42:08 +02:00
7e3919e25d xP: add basic buffer input history
Bind M-p and M-n as in xC.

Also make all our bindings reachable on macOS.
2022-09-11 03:10:23 +02:00
4bc2f736f2 xC: make terminal attributes abstract
And translate them for frontends.

This is very long overdue, and a rather significant cleanup.

Bump liberty.
2022-09-11 01:20:18 +02:00
add670212f xP: remember buffer input selections 2022-09-11 01:01:53 +02:00
95aa89ee97 xP: bind M-h to toggle history, and adjust focus 2022-09-10 20:39:03 +02:00
0bc2c12eec xP: handle the M-Tab binding from xC 2022-09-10 19:36:49 +02:00
3330683ad6 xP: handle M-a and M-! bindings from xC 2022-09-10 19:34:01 +02:00
0015d26dc8 xC/xP: support hiding unimportant messages at all 2022-09-10 19:01:42 +02:00
7d5e63be1f xC: deal with so far unexpected multiline messages
And get rid of an outdated unmarked TODO comment.
2022-09-10 18:51:27 +02:00
e7d0f2380e xC: split Command.BUFFER_INPUT on newlines 2022-09-10 18:51:27 +02:00
36529a46fd xP: also scroll to bottom on window resize 2022-09-10 18:10:08 +02:00
632ac992ab xC/xP: only send buffer stats in the initial sync
The client and frontends track these separately,
there is no need for hard synchronization.
2022-09-10 17:38:33 +02:00
d29e2cbfe8 xP: detect links in the log 2022-09-10 17:18:22 +02:00
240fac4d90 xP: only allow vertical textarea resizing 2022-09-10 17:08:14 +02:00
c06894b291 xP: fix command sequence number generation 2022-09-10 17:05:39 +02:00
9eaf78f823 xP: open links in a new tab/window 2022-09-10 17:05:39 +02:00
5f02dddd11 xP: advance unread marker when the log is visible 2022-09-10 17:05:39 +02:00
6f4a3f4657 xP: advance unread marker in an inactive tab 2022-09-10 17:05:39 +02:00
6387145adc xP: improve line wrapping 2022-09-10 17:05:38 +02:00
f3cc137342 xC-gen-proto: reduce enums to single bytes
That's already way more than we can possibly use.
2022-09-10 16:06:35 +02:00
8c8e06b015 xP: enhance mobile experience
The left column used to jump around, and phones were near-unusable.
2022-09-08 17:11:10 +02:00
d7b6967b6f xP: allow setting a fixed WS URI
For reverse proxies.
2022-09-08 17:11:01 +02:00
8c3ee80b21 xC/xP: finalize and implement Event.PING 2022-09-08 02:45:37 +02:00
3a165a595b xC: use the relay protocol's RPC for pings 2022-09-08 01:48:46 +02:00
4ba28c6ed3 xC/xP: mark highlights and buffer activity
And more or less finalize out the protocol for this use case.
2022-09-08 01:28:51 +02:00
45aa0e8dfb xP: remember to differentiate today 2022-09-07 19:23:47 +02:00
a2d5995cf5 xC: don't autoactivate buffers on forced JOINs 2022-09-07 19:10:49 +02:00
2075c38fd1 xP: use an industry-standard name for a button 2022-09-07 17:33:38 +02:00
88a7b1a2d9 xP: resolve various issues, mostly in styling 2022-09-07 17:26:43 +02:00
2341228efd xP: implement buffer line leakage
Rather than on redisplay, these get cleared on reconnect.
2022-09-07 15:34:52 +02:00
2e3005d88b xP: abort autocomplete when no longer applicable 2022-09-07 15:10:17 +02:00
2b13f891c9 xP: clean up 2022-09-07 14:45:44 +02:00
d55402234c xP: add a temporary lock for autoscroll 2022-09-07 13:53:28 +02:00
e3149b9abf xP: support showing buffer logs 2022-09-07 13:53:28 +02:00
976e7bfbb4 xP: separate input buffers 2022-09-07 13:04:30 +02:00
5fd76ba6f9 xC: add a trivial /relay command
For there is otherwise no way of getting that information.
2022-09-07 13:01:34 +02:00
41878a587f xC: use liberty logging less
These messages cannot be relayed to frontends (they could be,
but it's useful to keep them distinct rather than redirected).
2022-09-07 13:01:30 +02:00
80089a4d65 xC: describe general.editor parse errors 2022-09-07 13:01:29 +02:00
93b66b6a26 xP: scroll to bottom and focus the input on switch 2022-09-06 22:33:00 +02:00
ee1750c23c xP: clean up 2022-09-06 22:33:00 +02:00
f5104c807d xP: indicate connection state 2022-09-06 20:17:40 +02:00
2c49a72d94 Update README 2022-09-06 19:59:22 +02:00
8cd94b30f6 xP: implement tab completion
Currently it only goes for the longest common prefix.

Refactor WebSocket handling into an abstraction for our protocol.

The Go code generater finally needed fixing.
2022-09-06 19:41:05 +02:00
2d30b6d115 xC: define critical bindings after el_source()
And use ^C rather than ^G.
2022-09-06 17:02:36 +02:00
cf14cb8122 xC: implement buffer completion in the relay
And actually support completion with non-UTF-8 locales.
We used to ignore the encoding conversion result.
2022-09-06 17:02:02 +02:00
31e9c6d2d5 xC/xP: pass timestamps with millisecond precision
Future-proofing the protocol.
2022-09-06 14:39:01 +02:00
d2af6cf64c xP: convert links to link elements 2022-09-06 14:36:30 +02:00
d7b0b447b7 xC/xP: turn the action asterisk into a rendition 2022-09-05 23:22:09 +02:00
25ad5ae0ec xC/xP: fix colour values, and render them with CSS 2022-09-05 23:22:09 +02:00
10f6072da9 xC: also force monospace for RPL_MOTDSTART
It tends to looks inconsistent without.
2022-09-05 23:07:20 +02:00
aceac26cbb Fix up xP's module path, mention the licence 2022-09-05 23:07:20 +02:00
e250ae8255 Fix up README 2022-09-05 23:07:19 +02:00
1639235a48 Start X11 and web frontends for xC
For this, we needed a wire protocol.  After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK.  It now has two backends, per each of:

 - xF, the X11 frontend, is in C, and is meant to be the primary
   user interface in the future.

 - xP, the web frontend, relies on a protocol proxy written in Go,
   and is meant for use on-the-go (no pun intended).

They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-09-05 14:26:00 +02:00
2160d03794 xC: slightly clean up character encodings 2022-09-02 14:05:03 +02:00
36f8c7639f xC: clean up logging
Don't treat rendition as flags, separate the two.

Also treat join and part arrows as rendition.
2022-09-02 12:31:42 +02:00
74470f1aa4 CMakeLists.txt: improve dependencies of xD-replies 2022-09-02 12:25:37 +02:00
3af1765261 xC: make alias creation tolerant to prefixed names
Those would not work, so skip the first forward slash.

Note that liberty can save arbitrary alias names since 6e93119,
making the removed comment about checking outdated.
2022-08-29 15:22:11 +02:00
b454920c81 xC: deal with conflicts when renaming buffers 2022-08-29 15:05:02 +02:00
ef8f25d1dd xC: deal with any identifier conflicts
Invalid UTF-8 converted to UTF-8 may conflict with that
which was valid UTF-8 in the first place.
2022-08-29 14:41:23 +02:00
313a65180e xC: fix some corner cases around terminal handling 2022-08-29 14:05:33 +02:00
91db8e6e54 xC: use the correct way of resetting libedit
The only remaining major annoyance is incremental search
seemingly not giving back control.
2022-08-29 10:30:45 +02:00
dbe95fa298 xC: make libedit history switching more reliable 2022-08-29 09:20:56 +02:00
9d5e57a501 xC: improve libedit multiline input handling 2022-08-29 08:31:44 +02:00
4ed6693f57 xC: erase remaining mentions of a "backlog helper" 2022-08-29 08:22:09 +02:00
bea8d13227 xC: don't autosave when nothing changed 2022-08-29 08:22:09 +02:00
ecebeace0e Don't wrap xD-gen-replies in a shell script
AWK doesn't seem to be that friendly to shebangs, so let env,
also required for changing LC_ALL, locate it in PATH.
2022-08-29 06:07:49 +02:00
ca33adeeee Update README
Stop pretending that xD has a future.
2022-08-27 16:53:56 +02:00
b31e079256 Update README 2022-08-27 16:18:14 +02:00
57597bf8a2 xC: move TEXT_* constants where they belong 2022-08-27 15:06:28 +02:00
c0996fcbe7 xC: normalize BSD Editline's history behaviour
Now it's a realistically useful frontend.
2022-08-27 15:06:27 +02:00
03d8ea4c5a xC: general.save_on_quit -> general.autosave
Power outages and similar situations make the former unreliable,
so get rid of any false promise it might seem to give.
2022-08-27 09:15:38 +02:00
dc002a2db4 xC: revise configuration options
This commit constitutes a breaking change to old configurations.

All behaviour.* options have now become general.*, with the following
few renames as exceptions:

 - editor_command -> editor
 - backlog_helper -> pager
 - backlog_helper_strip_formatting -> pager_strip_formatting
2022-08-27 09:15:37 +02:00
a32916ffcf xC: label code sections better
Introduce tildes as a new sublevel of markers.
2022-08-27 09:15:37 +02:00
f7be510d26 xC: make fancy-prompt.lua alignment more reliable
And generally clean up that script.
2022-08-27 09:15:37 +02:00
83764d1e1b Fix xB.adoc parsing with current libasciidoc 2022-08-24 03:17:05 +02:00
a717782480 Build with AsciiDoc as well as Asciidoctor 2022-08-24 00:13:51 +02:00
c50c959f4d Bump copyright years 2022-08-17 18:27:52 +02:00
0dd7536b5a Update README 2022-08-15 15:49:59 +02:00
0750096827 xC: expand behaviour.editor_command examples 2022-08-14 20:27:30 +02:00
49d9980662 xC: improve backlog helper capabilities
Snippets now receive positional parameters in the form of the buffer's
name in the locale encoding, and a filename if applicable
(we keep passing stdin along with the filename, which happens to
work out well for less(1)).

The default value of the configuration option also no longer uses
the "long prompt", which used to unhelpfully tell position in terms
of lines, but rather sets its own prompt that counts pages,
and makes sure to indicate the source buffer.

The main motivation behind this change is to make the 'v' command
work in less(1).  LESSSECURE must be omitted from the snippet
for this to work.

Bump liberty to receive a config parser that allows for less
convoluted escaping.
2022-08-14 18:52:26 +02:00
2f7fbcdc5d CMakeLists.txt: fix a typo 2022-08-12 13:21:46 +02:00
ef0cbe9a59 Rename the project
It is about to see some extensions, obsoleting the number three.
2022-08-07 10:40:42 +02:00
1238233556 hid: bump the FD limit 2022-08-02 22:10:31 +02:00
2d8808d795 utm-filter.lua: mention the passing of fbclid 2022-07-18 17:59:28 +02:00
9c31fb69df hid: make note of a deprecation 2022-03-16 12:57:00 +01:00
a51c247d69 hid: add WebIRC support
Such clients can only be identified through STATS L.

It's a bit weird to abuse the "port" field this way,
but right now, it serves its purpose.
2022-03-15 19:57:31 +01:00
f26e6361f3 hid: implement WALLOPS 2022-02-05 00:31:34 +01:00
60d52ad479 xC, xD: add basic WALLOPS support 2022-02-04 22:48:54 +01:00
b358f53ec3 Bump version, update NEWS 2021-12-21 05:58:34 +01:00
2eb315f5c4 utm-filter.lua: add Facebook to the filter 2021-12-20 14:36:41 +01:00
851c2ee548 CMakeLists.txt: fix macOS build 2021-11-02 15:34:51 +01:00
f9848ed627 Update README 2021-10-31 05:16:57 +01:00
686a39df38 CMakeLists.txt: slightly modernize 2021-10-31 04:30:04 +01:00
9cea3fca91 Update NEWS 2021-10-30 14:25:13 +02:00
5165f76b7c xC: quote text coming from a bracketed paste
Not having this has caused me much annoyance over the years.
2021-10-30 09:27:32 +02:00
92ac13f3c6 xC: allow passing the cursor position to editors
Add a configuration option to set a custom editor command,
different from EDITOR or VISUAL--those remain as defaults.

Implement substitutions allowing to convey cursor information
to VIM and Emacs (the latter of which is fairly painful to cater to),
and put usage hints in the configuration option's description.

This should make the editing experience a bit more seamless
for users, even though the position is carried over in one way only.

No sophisticated quoting capabilities were deemed necessary,
it is a lot of code already.  The particular syntax is inspired
by .desktop files and systemd.

["/bin/sh", "-c", "vim +$2go \"$1\"", filename, position, line, column]
would be a slightly simpler but cryptic way of implementing this.
2021-10-30 09:02:35 +02:00
df4ca74580 xC: make libedit autocomplete less miserable
Omitting even this hack was a huge hit to overall usability.
2021-10-30 08:29:16 +02:00
9e297244a4 Update .gitignore 2021-10-30 03:37:22 +02:00
d32ba133c0 Add clang-format configuration, clean up 2021-10-30 02:55:19 +02:00
ce3976e1ec xC: normalize ^J behaviour to follow Readline
For some reason Editline inserts it verbatim,
but in a more broken manner than it has with ^V^J.
2021-10-28 08:49:01 +02:00
e5ed89646b xC: fix newer libedit (2021-08-29) 2021-10-28 08:23:52 +02:00
5e728f6d31 Bump version, update NEWS 2021-10-06 14:05:23 +02:00
766f68e070 Bump liberty 2021-10-06 13:52:59 +02:00
3dc5242d43 Bump liberty
Importing some minor unimportant fixes.
2021-09-26 08:55:46 +02:00
fd9d5db1d2 xD: bump the soft file descriptor limit
By default it's a mere thousand connections, which is unnecessarily
crippling our advertised ability to handle lots of them.

Thanks for the advice, Lennart.
2021-09-23 20:32:00 +02:00
cb480b4c71 xC: show orphan outcoming actions differently
It's hard to think of anything actually good here.

This would be an exceptionally rare thing to do, anyway.
2021-09-05 02:51:05 +02:00
59cc423694 xC: abandon Freenode, embrace IRCnet
You're not fucking supposed to require a fucking registration
on fucking IRC networks.
2021-08-29 15:18:20 +02:00
9323089d66 xC: mIRC didn't invent all IRC formatting
So let's not confuse ourselves.
2021-08-29 12:12:52 +02:00
de7df1f60d xC: refactor parsing of IRC formatting 2021-08-29 12:06:53 +02:00
b082e82b62 xC: fix displaying IRC colours above 16
First, we indexed the colour array without a required offset.
Second, the data type was too small and overflowed negative.

Detected during a refactor, which this is a part of.
2021-08-28 18:25:03 +02:00
b8dbc70a9c xC: respect text formatting when autosplitting 2021-08-28 18:24:20 +02:00
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
4073b7329f hid: reflect the original project's new name
Better keep all schizophreny in my own head, rather than all projects.
2021-08-06 17:31:32 +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
2336340ad8 Bump version, update NEWS 2020-10-31 23:50:32 +01:00
8f5dec0456 degesch: buffer creation cleanup 2020-10-31 23:44:18 +01:00
3dc6ee9a5b degesch: sanitize IRC nicknames/channel names
Don't trust the IRCd to have them in a subset of UTF-8.
2020-10-31 23:25:08 +01:00
821ce04915 degesch: implement autocompletion for /set
It was super annoying to just slightly modify strings and
string arrays, now you can have existing values filled in.

complete_word() looks a bit cleaner now as well.
2020-10-31 23:18:31 +01:00
2fe3b95ecd README.adoc: improve backlog helper invocation
When fancy-prompt.lua is enabled, tho prompt is two-lined
and a simple PageUp would skip one line of content.

It works slightly better than it should: when there's under
a page of content to scroll, there is no shift at all.
2020-10-31 20:00:23 +01:00
32c99c9d66 kike: avoid crash with a wildcard address
A most unfortunate 06d3b3b regression, mostly stemming from
forgetting why the `break` was in place and not documenting it.
2020-10-31 17:34:32 +01:00
cd7133e173 README.adoc: minor documentation update 2020-10-31 16:06:13 +01:00
b4ed52015a degesch: mark some issues for later resolution 2020-10-31 16:06:12 +01:00
271689da99 fancy-prompt.lua: allow non-ASCII buffer names
It may theoretically bite us in the ass with non-UTF-8-compliant
IRC servers, and certainly with double-width characters.
2020-10-31 16:05:15 +01:00
38c23d0d38 degesch: fix fancy-prompt.lua with libedit
Partly by unifying the interface for prompt hooks to match GNU Readline.
2020-10-31 16:04:30 +01:00
439af8884c degesch: make PageUp actually scroll a page up
Now that the input to the backlog helper is wrapped the same way
as what we display.  There's a slight issue always triggered by
fancy-prompt.lua where a multiline prompt/command line makes less(1)
go too high up but it's nothing too important.
2020-10-31 16:00:55 +01:00
8ccf38ad76 Minor rebranding
There's nothing experimental about this project anymore.  It's stable.

Maybe we should add a photo of Hitler or something.
2020-10-31 13:42:56 +01:00
47a4c8beca CMakeLists.txt: clean up OpenBSD support
A few things might have changed.
2020-10-29 15:27:09 +01:00
1de4a2ae34 Bump version, update NEWS 2020-10-29 03:03:07 +01:00
53cc52e320 Add real manual pages
To some extent they duplicate the README but from a different angle.
2020-10-29 02:46:40 +01:00
cbe4009308 degesch: fix Lua 5.3 build
Regression from the last release.
2020-10-29 02:44:40 +01:00
06d3b3bd2b kike: ensure NULL binds to both IPv4 and IPv6 2020-10-29 00:39:57 +01:00
132e4a38b8 kike: document the "operators" setting usefully
Now our user just needs to be able to guess that it's a hex string.
2020-10-28 23:53:03 +01:00
8429995cb6 ZyklonB: don't look for plugins in /usr/lib
It's quite unlikely that this project will ever see compiled plugins.
2020-10-28 17:17:48 +01:00
03ed097353 ZyklonB: use XDG paths by default
Install plugins to /usr/share rather than /usr/lib since they're
arch-independent.  Many precedents can be found for scripted plugins
in /usr/share and fewer for /usr/lib.

Look for plugins in all XDG data directories and repurpose
the "plugin_dir" setting to override this behaviour.

This adds some complexity to the bot but unifies the project.
It might make sense to remove the "plugin_dir" setting.
2020-10-28 17:17:34 +01:00
b68e5ceedc README.adoc: fix GPL notice 2020-10-28 13:11:46 +01:00
0d0d0b6863 CMakeLists.txt: omit end{if,foreach} expressions
Their usefulness was almost negative.
2020-10-28 11:30:37 +01:00
577fd1b446 CMakeLists.txt: clean up and fix build rules
The multiple-output custom command ran separately for each binary.
2020-10-28 11:23:39 +01:00
500c83231f Bump minimum CMake version to 3.0
A nice, round number.
2020-10-27 12:02:47 +01:00
4b7649211a Bump copyright years 2020-10-27 12:02:46 +01:00
9afa4944b6 Bump liberty
Testing in production is discouraged.
2020-10-24 19:10:55 +02:00
e58ce1f02e Bump liberty, test UTF-8 sanitization 2020-10-21 05:44:27 +02:00
98e95de90e degesch: add a hidden LOMEM compile option 2020-10-20 02:02:09 +02:00
383f6af344 Improve OpenSSL integration
Ensure the error stack is cleared after errors are processed.

Also handle NULL returns safely.

Makes the debug mode spew more data, though almost none of
the contexts is in reaction to network peer data.
2020-10-20 01:55:46 +02:00
13c85aa361 degesch: comment about improving word wrapper 2020-10-19 23:37:19 +02:00
419b02e9f7 degesch: slightly cut down memory usage
The worst offenders are actually OpenSSL and Lua, this is
mostly about a preventable surprise.

This is more correct because we mix escape sequences for
attributes with text, however in practice no one will use
shit-jizz with degesch.

It is also a clean-up: "struct line_char" has been almost
halved in size.  We used to use it as a cache and now we
recompute the multibyte sequence.

Of course, it'd be best to get rid of the linked list but
it would take a very long time to rewrite the algorithm.
Plus, it's not certain that it could be improved by much.

The change in "struct line_char_attrs" is merely cosmetical.
2020-10-19 06:38:31 +02:00
c89032e4e0 degesch: silence the compiler 2020-10-19 05:17:41 +02:00
474657c7b3 degesch: fix processing WHO replies
We don't want to print the reply for ourselves
nor for unknown or PM-only users.
2020-10-19 04:21:52 +02:00
323a372389 degesch: update an outdated comment 2020-10-16 23:29:05 +02:00
76f4e6faa6 degesch: cleanup
Channels now need a reference to the server,
so don't pass it to functions.
2020-10-16 21:17:57 +02:00
2c48bc9959 degesch: watch away statuses with away-notify/WHO
We're not going to implement polling.  Polling is complex.
Freenode supports away-notify.
2020-10-16 21:17:57 +02:00
e1a4fab40d degesch: don't eat NAMES for unknown channels 2020-10-16 17:59:51 +02:00
1ff80ddd10 degesch: stubplement TAGMSG 2020-10-16 17:59:50 +02:00
12c8ace6a1 degesch: clarify handling of unexpected JOINs
I got confused about safety.
2020-10-16 17:59:50 +02:00
49706efe86 degesch: improve a function name
3_3_3_3_4 looks awful and it wasn't even precise.
2020-10-16 17:59:43 +02:00
9d8a7a10d0 Tolerate cut-off UTF-8 messages
I've had this happen to me on Russian channels and it's highly
annoying because you lose the entire message.  On the contrary,
this at worst screws up the last few characters of it.

Closes #2
2020-10-12 23:45:27 +02:00
73c3ca3633 Bump liberty 2020-10-12 23:00:43 +02:00
559232ccb5 kike: fix up debug messages 2020-10-12 04:33:39 +02:00
6837fdb7c4 Bump liberty
We've moved most of our configuration test in there.
2020-10-12 04:08:09 +02:00
2759c311fa kike: use read/write rather than recv/send
read/write support non-sockets, otherwise they're the same here.

This is in preparation for fuzzing.
2020-10-12 04:04:06 +02:00
529a46ad41 degesch: add support for crossed-out text
Assuming that sgr0 includes rmxx behaviour, which should be true.
2020-10-11 18:07:26 +02:00
f9ef123171 degesch: support more colours 2020-10-11 17:49:31 +02:00
f51dd936f5 degesch: prefer British spelling in comments
Let's say the rest is in Oxford spelling, not sure about it.
2020-10-11 17:48:57 +02:00
7ce1615021 prime.lua: skip colour sequences, add config
Colour sequence skipping is somewhat involved, we might want to
add a helper generator to the "degesch" Lua library, in the form of
{substring, is_formatting}.

formatter_parse_mirc() isn't useful, a pure Lua implementation
would be more appropriate (where do we put that?)
2020-10-11 16:54:15 +02:00
270d9017e9 degesch: improve ad-hoc IRC parsers in plugins 2020-10-10 17:58:33 +02:00
ee5cac4f21 degesch: add a plugin to highlight prime numbers 2020-10-10 17:55:14 +02:00
59ac02d91f Bump liberty
resolve_relative_runtime_unique_filename() used to have a bug.
2020-10-10 04:37:08 +02:00
d78cf10f04 degesch: fix prompt not showing up after change
When a backlog helper was running and the prompt changed,
it failed to restore within input_rl_show().

Since before input_rl_show() is called the prompt is empty
and in input_rl__restore() it will be changed to the new
version, just skip invoking any Readline functions within
input_rl_set_prompt() when the prompt is hidden.  Simple
and straight-forward.

This bug is what I hinted at in the previous commit.
2020-10-06 13:42:27 +02:00
572a7cb804 README.adoc: update degesch instructions
There is still one outstanding issue with the backlog helper, though...
2020-10-04 12:27:17 +02:00
03e8ad0a3e degesch: enable wrapping in the backlog by default
The main issue has been eliminated.
2020-10-04 12:17:09 +02:00
f665f147ff degesch: resolve the issue with less(1) and SO/SI
Now that I've learnt what exactly these characters are and how they
ended up in attribute strings, we can just eliminate them and disable
`backlog_helper_strip_formatting`.  Saner defaults, again.

I've also added skipping of terminfo delay sequences, so now it's less
of an issue to pipe raw attribute sequences into backlog helpers.
2020-10-04 12:04:24 +02:00
9819b75b64 degesch: make the unread marker look a bit fancier
Upstreamed after who knows how long, in a slightly modified form.
The marker looks fairly ugly without this and defaults should be
desirable.

It's possible to get the previous behaviour by resetting the separator
character in the configuration to an empty string.  It might be
a better idea in general to just disallow this value with a special
validation callback, so that there's only one way to do it.

However given that without fancy-prompt.lua, an optional plugin,
the long line stands out considerably, it might actually be a good
idea to keep the old behaviour as the default.  I'm torn.

Right now we don't care about the situation where the string occupies
more than one terminal cell or is some Unicode BS.  User's problem.
2020-10-04 10:08:30 +02:00
f716e7601f degesch: fix a typo 2020-10-04 08:44:16 +02:00
eea761d9f7 degesch: make use of arguments in _new() functions 2020-10-04 08:32:15 +02:00
dd8e543a20 degesch: save some memory on channel users
`struct str` was mostly unnecessary, we can save 16+ bytes,
while performance and code readability is mostly unchanged.
2020-10-04 08:28:07 +02:00
dc8b580574 degesch: expand comment about character encoding 2020-10-02 07:09:58 +02:00
2d9856cca8 Bump liberty, use iscntrl_ascii() 2020-10-02 06:52:11 +02:00
289193dd1a kike: silence an annoying build warning 2020-09-20 13:43:59 +02:00
405848deeb degesch: remove unnecessary quotes from macro defs
The behaviour is defined by the standard.
2020-09-20 13:43:36 +02:00
b9991d4766 degesch: update comment to reflect reality 2020-09-20 13:43:10 +02:00
1ff82ee907 Update NEWS, bump version 2020-09-02 20:00:12 +02:00
57e92fbb85 Update copyright years 2020-09-02 20:00:11 +02:00
a04dfc59fe README: improve libasciidoc compatibility 2020-09-02 20:00:11 +02:00
7f69655c54 README: discourage from using libedit 2020-09-02 20:00:10 +02:00
444f97b357 degesch: work around a libedit attribute issue 2020-09-02 20:00:10 +02:00
ed7130a664 degesch: fix a libedit crash 2020-09-02 20:00:10 +02:00
ba1c2357af degesch: fix Lua 5.4 build
Not sure about how well it works yet.

Lua 5.3 is still made preferential by the order of pkgconfig lookup.
2020-09-02 20:00:09 +02:00
a48023553e degesch: fix a pointer operation in the libedit layer 2020-09-02 20:00:09 +02:00
d29317b29c Bump liberty 2020-09-02 20:00:09 +02:00
deb096a0e9 Name change 2020-09-02 19:37:29 +02:00
722fc48a30 CMakeLists.txt: add a comment 2020-09-02 19:37:26 +02:00
6421892ef3 Name change 2020-08-01 14:01:58 +02:00
6287e20919 degesch: fix log reopening after a buffer rename 2020-03-23 00:41:08 +01:00
07d59db5ab degesch: clean up unused functions 2020-03-22 02:00:57 +01:00
2909b017fb Fix handling terminal resizes while the terminal is suspended
GNU Readline has a misfeature.
2020-03-21 22:02:02 +01:00
64d4009427 degesch: fix getpwuid usage
The "entry not found" case doesn't have to touch errno.
2019-12-07 21:18:20 +01:00
a1994865a9 hid: mention Go 1.12 alternative to TLS autodetection 2019-02-27 02:36:04 +01:00
4179a9bd49 Update NEWS, bump version 2018-10-21 05:44:39 +02:00
aa4e86c2a0 degesch: add a comment about ENOTCONN 2018-10-21 05:40:24 +02:00
5bbe9ceef8 Update NEWS 2018-10-21 05:40:24 +02:00
f80226620c kike: fix wildcard handling in WHOIS 2018-10-21 05:40:24 +02:00
2fccfb10f7 kike: allow STATS with no parameters
We were in plain conflict with RFC 2812 for no apparent reason.
2018-10-21 05:40:16 +02:00
c285f3a266 hid: clean up/finalize logging 2018-08-06 20:47:33 +02:00
e2c34afbc6 hid: move off of the log package
We don't spam with useless messages without -debug any longer.
2018-08-06 19:52:39 +02:00
e2c8fb6e33 hid: port logging facilities
Though the regular mode now has timestamps and a new mode for systemd
has been added.
2018-08-06 19:49:06 +02:00
5c7ac9a92b hid: cleanups
No functional changes.
2018-08-06 12:31:31 +02:00
3fee7e8051 hid: port IRC tests from liberty, fix tag parsing 2018-08-06 12:09:18 +02:00
09d7a10b69 hid: rename connCloseWrite to connCloseWriter 2018-08-06 12:06:42 +02:00
e9bcd0fa53 hid: add the first tests
This has actually revealed a problem in the SSL 2.0 detection.
2018-08-06 12:06:20 +02:00
3815795d59 hid: fix SSL 2.0 autodetection 2018-08-04 21:13:28 +02:00
fd1538251a hid: add support for customized replies 2018-08-03 21:45:53 +02:00
ffad1f15a5 hid: unify exit codes with the flag package 2018-08-03 21:45:53 +02:00
765b741a67 hid: cleanups 2018-08-03 21:45:52 +02:00
ab66a60703 hid: fix listener shutdown 2018-08-03 10:55:22 +02:00
9ee07873ea hid: fix nickname verification in the user MODE message 2018-08-02 18:42:32 +02:00
7ee7dc5f9b hid: port default formatting strings to fmt 2018-08-02 12:51:22 +02:00
fea801ac7a hid: ircSendToRoommates -> ircNotifyRoommates
Should be clearer.
2018-08-01 20:39:37 +02:00
cbdbfc3d64 hid: figured out how to port timeouts 2018-08-01 20:39:37 +02:00
3610f98d67 hid: another round of general code cleanups 2018-08-01 17:45:56 +02:00
b9eddabedd kike: explicit conversion from pointer to boolean
In practice the values in the map may only be 1 or 0, so it doesn't
matter, but in C it is better to be safe than sorry.
2018-08-01 09:22:59 +02:00
50ed74a740 kike: break out properly on errors in MODE processing
We used to only abort the inner loop, which was insufficient.
2018-08-01 09:21:37 +02:00
3ca08badc2 kike: reset user modes while processing USER
Since the processing always succeeds and registration cannot be undone,
this doesn't seem to fix any real issue.
2018-08-01 09:17:45 +02:00
b0f5b8c10d kike: do nothing on equivalent renicks 2018-08-01 09:17:12 +02:00
d87d533078 kike: code cleanups 2018-08-01 09:16:45 +02:00
3c47e5b354 kike: fix grammar in hostname validation
This has an entry in RFC 2812 errata, although it's held for document
update.  We can afford the strictness.
2018-08-01 09:16:45 +02:00
54d3406175 kike: fix grammar in config item description 2018-08-01 09:16:44 +02:00
f79dd027e9 kike: add a comment about identifier encoding 2018-08-01 09:16:44 +02:00
e77495f316 hid: bringup of what we have this far 2018-07-31 23:11:54 +02:00
2f841d214f hid: port configuration and initialization
All the basic elements should be there now, we just need to port PING
timers and fix some remaining issues and we're basically done.
2018-07-31 20:53:23 +02:00
051bbedc2f hid: port IRC 3.2 message tag parsing, unused 2018-07-30 17:50:27 +02:00
404aa8c9cc hid: use time.Time and time.Duration
It improves the code significantly over explicit int64 conversions.

Despite carrying unnecessary timezone information, time.Time also
carries a monotonic reading of time, which allows for more precise
measurement of time differences.
2018-07-30 10:07:02 +02:00
90129ee2bc hid: port MODE, STATS, LINKS, KILL
Now all the commands have been ported but we desperately need to parse
a configuration file for additional settings yet.
2018-07-30 09:46:59 +02:00
50e7f7dca5 hid: port PART, KICK, INVITE, JOIN, AWAY, ISON, ADMIN, DIE 2018-07-29 17:49:57 +02:00
3322fe2851 hid: port PRIVMSG, NOTICE, NAMES, WHO, WHOIS/WAS, TOPIC, SUMMON, USERS 2018-07-29 15:57:39 +02:00
208a8fcc7e hid: first round of mixed fixes and cleanups 2018-07-29 08:14:07 +02:00
2d287752d4 hid: add a work in progress IRC daemon
The port is more than viable but it's also sort of all-or-nothing
and versioning needs have come before I've had a chance to finish it.
2018-07-28 16:21:34 +02:00
fa78831cbd Update NEWS, bump version 2018-06-22 00:59:41 +02:00
94b0ec80cf fancy-prompt.lua: workaround a Readline UTF-8 bug 2018-06-22 00:55:17 +02:00
300f9a9708 Bump liberty 2018-06-22 00:03:21 +02:00
b1a89f313a degesch: add static analysis for the logger
Caught two more occurences than I was able to find by just scanning
the source, so the effort wasn't in vain.
2018-06-21 23:46:03 +02:00
fab5115cd0 Remove .travis.yml
We don't depend on any proprietary services no longer.  I'll have to
make my own replacements with blackjack and hookers.  Until then,
the file stays in the commit log as an example.
2018-06-21 23:46:03 +02:00
d0cb3c1ac6 Update README 2018-06-21 23:46:03 +02:00
a0e9ede3e3 Relicense to 0BSD, update mail address
I've come to the conclusion that copyright mostly just stands in the way
of software development.  In my jurisdiction I cannot give up my own
copyright and 0BSD seems to be the closest thing to public domain.

The updated mail address, also used in my author/committer lines,
is shorter and looks nicer.  People rarely interact anyway.
2018-06-21 23:46:03 +02:00
787569e653 Update submodule URL for liberty 2018-06-21 23:45:55 +02:00
5d353b0721 Prepare NEWS for the next release 2018-06-21 23:26:16 +02:00
006d34eeae degesch: fix some log messages
We could use some static analysis for these.
2018-06-21 23:25:45 +02:00
19400ee8b7 kike: disable TLS session reuse 2018-01-09 06:25:16 +01:00
674ffb2f6d kike: handle accept() errors better
Might prevent some denial of service attacks.
2018-01-09 05:48:36 +01:00
6c30452b28 kike: thorough review, no functional changes 2018-01-09 05:47:37 +01:00
670e1c5770 kike: introduce cstr_set() 2018-01-08 23:16:14 +01:00
4586b0e1e4 degesch: introduce cstr_set() 2018-01-08 22:19:28 +01:00
b4507b56af degesch: thorough review, no functional changes 2018-01-08 22:19:23 +01:00
bf6d507bb2 degesch: fix IPv6:port in irc_split_host_port() 2018-01-08 22:19:02 +01:00
099a49e6d5 degesch: fix a minor bug in buffer_merge()
The pointer to the last item in the linked list wasn't always fixed,
although nothing really touched it afterwards.
2018-01-08 22:17:02 +01:00
4627ee82dd degesch: simplify a popular assertion 2018-01-08 22:16:57 +01:00
682f90e989 degesch: simplify the configuration dumper 2018-01-08 22:16:52 +01:00
277af83100 degesch: show an error message on log write failure
Running out of space and I/O errors seem like the most likely causes.
2018-01-08 22:16:36 +01:00
a5a0078def degesch: make buffer index computation easier to follow 2018-01-08 22:16:10 +01:00
868e34d15c degesch: fix a Lua error message 2018-01-08 22:16:06 +01:00
dc47b16034 Bump liberty, avoid fall-through warnings 2018-01-07 17:37:19 +01:00
d0f19f8be3 Update README
"Edgy" is actually a fitting word.  And we've lost OBS!
2017-12-06 23:49:18 +01:00
ddb45a1cc4 Update README 2017-12-02 13:06:39 +01:00
3974919741 Update README
So be it, SJWHub, at least I have a reason to move.
2017-12-02 11:06:48 +01:00
36be830bfc degesch: better shift state encoding handling
I don't know, probably didn't matter.
2017-07-07 20:55:25 +02:00
f7dce5e861 slack.lua: add a feature to undo emoji 2017-07-03 06:45:46 +02:00
757047bd20 CMakeLists.txt: fix variable name 2017-07-03 06:44:15 +02:00
a2611cdc3c Rework constructors/destructors 2017-06-22 22:56:24 +02:00
68bc297809 Bump liberty 2017-06-22 22:39:39 +02:00
933760c2a2 kike: fix two memory leaks 2017-06-22 20:36:21 +02:00
156ea32a90 slack.lua: support @here and @channel 2017-05-18 10:44:36 +02:00
f744681b17 slack.lua: improve input hook matching 2017-05-17 15:05:51 +02:00
bdc6334aec slack.lua: more unfucking
And now it's already fairly usable.
2017-05-17 00:32:54 +02:00
96864517c6 Fix licensing notice in README 2017-05-14 22:13:00 +02:00
0bdcd4aa8b fancy-prompt.lua: remove unnecessary local variable 2017-05-13 20:04:21 +02:00
b18a8048c1 degesch: add a slack plugin
Slack's IRC gateway is crap but it doesn't need to be *such* crap.
2017-05-13 20:04:21 +02:00
c3d62b8799 Avoid the "poller_fd::closed" feature
Reliability enhancement for Linux.

This feature was created for ponymap, however we don't care about an
extra syscall in most places.  Doing it right even saves lines.
2017-05-06 21:35:44 +02:00
ec842db0fb Update copyright years 2017-04-22 19:41:27 +02:00
0981df485a degesch: simplify quitting
- send a QUIT on C-c, too
 - shut down the connection on /disconnect, too

Connection management is one of the few fucked up parts
that remain in that state for historical reasons.
2017-04-20 20:55:49 +02:00
9f0c18cc41 degesch: fix confusing message
It seemed like we were connecting albeit we were connected already.
2017-04-20 20:26:04 +02:00
1313a712df degesch: make a second /disconnect always succeed 2017-04-20 20:25:21 +02:00
f45f9ab873 Travis CI: brevify notifications 2017-02-03 23:17:15 +01:00
9e5725662f Bump liberty 2017-01-23 23:50:27 +01:00
0785a6f417 degesch: Lua is no longer experimental
But rather essential to me.
2017-01-23 23:41:14 +01:00
cb9957cd64 Travis CI: try adding the PPA back 2016-12-30 14:47:50 +01:00
40bb2497f7 Travis CI: try removing a dead PPA 2016-12-30 14:42:06 +01:00
d7960b463f Fix LibreSSL compatibility 2016-12-30 08:51:49 +01:00
3c048f0d56 Bump version 2016-12-30 08:15:44 +01:00
8e668ff31a Various fixes related to channel modes
Bugs unnoticed for so long.
2016-12-30 08:08:34 +01:00
eb70bf3fbc Cleanup 2016-12-28 12:44:27 +01:00
d86a68f510 Add support for OpenSSL 1.1.0 2016-12-28 12:40:47 +01:00
d6be22291d degesch: /query w/o arguments just opens the query 2016-12-06 13:51:16 +01:00
a813babb89 fancy-prompt.lua: fix parametrized modes 2016-12-02 12:28:55 +01:00
b666ce6926 fancy-prompt.lua: change background on highlight 2016-12-02 12:28:55 +01:00
e2bb051bd3 degesch: replace degesch.connect with async.dial
Halfway there, looks much saner.
2016-11-04 22:02:26 +01:00
52d1ded7df degesch: move the Lua async code within the file 2016-11-04 20:44:23 +01:00
cb9f187f80 degesch: get rid of Lua timer hooks
Since they were the exception and have been replaced with the async API.
2016-11-04 20:21:46 +01:00
0247c4667a degesch: Lua coroutine safety 2016-11-04 20:12:28 +01:00
572f4e2ea3 degesch: implement Lua coroutine async basics 2016-11-04 20:11:59 +01:00
50599e09bd Update README, add a screenshot for degesch 2016-10-30 18:52:20 +01:00
b24bb0aded degesch: fix join/part hiding in the backlog 2016-10-30 16:24:23 +01:00
7c6cf42075 thin-cursor.lua: update comments 2016-10-30 01:50:21 +02:00
414a525c4d degesch: add a thin-cursor plugin 2016-10-30 00:00:48 +02:00
6cee7159f2 degesch: clean up
Caught by Coverity, however it is quite harmless.
2016-10-29 21:08:15 +02:00
568f9b7123 degesch: tiny fixes for the prompt hook
It should return valid UTF-8.

Also remember to refresh the prompt upon hook removal.
2016-10-29 20:03:31 +02:00
0d499dd125 degesch: avoid senseless indirection in hooks
It's always been one function call only this far.
2016-10-29 19:51:54 +02:00
37e49b54cf degesch: rename things around terminal attributes 2016-10-29 18:07:28 +02:00
742d590b8d degesch: simplify "attribute_printer"
Now that the line wrapper took over some of the state.
2016-10-29 17:53:06 +02:00
b6528c73e3 degesch: microoptimization 2016-10-28 18:16:21 +02:00
1e79aaec26 degesch: refresh the prompt when a hook is set 2016-10-28 13:58:37 +02:00
0995da3900 degesch: don't consider all mode changes important 2016-10-28 13:32:29 +02:00
c8a826f016 degesch: optimize Lua weak refs 2016-10-28 13:09:50 +02:00
95c7ababc3 degesch: add a "fancy-prompt" plugin
So that the client looks at least a tiny bit decent if needed.
2016-10-28 12:53:18 +02:00
a0d733fdb9 Update NEWS, README 2016-10-28 12:47:11 +02:00
557a39c6c8 degesch: export server state as a string to Lua 2016-10-28 12:47:11 +02:00
745e758394 degesch: add Lua API for screen size retrieval 2016-10-28 04:12:06 +02:00
b60bdf119a degesch: add a prompt hook 2016-10-28 04:12:06 +02:00
278e2b236b degesch: add introspection for refs within str_maps
This required some fixes to the design.
2016-10-28 04:12:05 +02:00
2f758bbdb9 degesch: allow lists of refs in introspection 2016-10-28 04:12:05 +02:00
911276b263 degesch: add introspection for "app_context" 2016-10-28 04:12:05 +02:00
cb5ad675a6 degesch: add introspection for "str" and "str_map" 2016-10-28 04:12:05 +02:00
9408dfc67c degesch: create Lua refs through introspection 2016-10-28 04:12:05 +02:00
fed8b06aff degesch: begin work on direct introspection 2016-10-28 04:12:05 +02:00
7e64fd9886 degesch: cleanup 2016-10-28 04:12:05 +02:00
6928184a3d degesch: defer prompt refreshing
Now that we do it each time we receive a message from the server.
2016-10-23 17:34:52 +02:00
f7155f3919 degesch: allow hiding join/part messages 2016-10-23 17:14:24 +02:00
f032466307 degesch: comments, no functional change 2016-10-23 17:14:24 +02:00
c0f4b554ef degesch: show channel user count in the status 2016-10-23 17:14:24 +02:00
639da7a9a7 degesch: accept Word shortcuts for formatting
Because why not.
2016-10-23 13:40:04 +02:00
230b04014f Bump liberty, add consts to some arguments 2016-10-23 13:38:46 +02:00
4848354bb9 Get rid of the remaining FAILs 2016-10-11 12:05:17 +02:00
8028c7fa47 Bump liberty 2016-10-11 10:52:49 +02:00
43de836b91 degesch: exit with error when arguments are given 2016-09-29 13:40:15 +02:00
16d10f574b degesch: simplify highlight detection 2016-09-25 14:11:30 +02:00
4cefa5ab1b degesch: fix highlight detection in colored text 2016-09-23 23:46:26 +02:00
92a4d4b5a7 Better support for the KILL command 2016-09-23 22:50:30 +02:00
26f94d2459 degesch: add a "censor" plugin
So far this approach screws up highlights, which is actually a bug.
2016-09-23 18:59:37 +02:00
0be43691d0 Update README 2016-07-23 20:29:25 +02:00
483ab39e3c degesch: die on configuration parse errors
Seems more sensible.
2016-07-23 20:00:40 +02:00
beaf1a1f82 degesch: fix Ctrl-J in Readline 2016-07-23 19:13:55 +02:00
5613c326c9 degesch: fix CTCP handling
In `/me :\` practically no client bothers to escape the backslash but we
used to interpret it as the start of an escape sequence anyway.

Silly us, no one respects any standards.
2016-07-09 22:55:26 +02:00
db17223df0 Bump version; update NEWS, README 2016-04-28 23:46:08 +02:00
2474b5f3f5 calc: fix usage of (substring) 2016-04-28 23:25:29 +02:00
d97f28e7f7 ZyklonB: add a seen plugin 2016-04-24 21:05:53 +02:00
d6a9e1dca1 degesch: customizable date change messages
Now also in the backlog.
2016-04-21 23:50:05 +02:00
c8e4833086 degesch: add a NOWRAP flag to formatter_flush()
--format should work as before now.

It is now also possible to rebind PageUp to show a wrapped backlog.
2016-04-21 23:50:05 +02:00
99595c0d81 degesch: update comments 2016-04-21 23:50:05 +02:00
75c4645f10 degesch: add an auto-rejoin.lua plugin 2016-04-21 22:12:33 +02:00
fa5e005728 degesch: refactor Lua weak objects 2016-04-21 22:09:35 +02:00
a9b77b3206 degesch: expose channels and users to Lua 2016-04-21 22:09:35 +02:00
29418e5e55 ping-timeout.lua: fix message parsing 2016-04-21 22:09:35 +02:00
4665807d09 degesch: expose message parsing to Lua 2016-04-21 22:09:35 +02:00
1180255e7b calc: comment updates, import fixes 2016-04-20 22:55:40 +02:00
6f85490fa3 Update NEWS 2016-04-16 20:11:11 +02:00
e97c60245c ZyklonB: add a calc plugin 2016-04-16 20:11:11 +02:00
3a8d70de66 degesch: fix crash on invalid cp1252 characters
We don't even really need iconv here.
2016-04-03 04:05:04 +02:00
695d615225 ZyklonB, kike: Use pledge(2) in OpenBSD
degesch has something like "stdio wpath cpath inet tty proc exec"
but given that it's user-extensible and very annoying for users to
have it crash, I'm leaving it unrestricted for now.
2016-03-30 00:50:44 +02:00
8a3144f0ac degesch: update program logo
I've noticed that the old one wasn't very pleasant to look at.
2016-03-28 21:08:04 +02:00
48423aa4af Update README 2016-03-28 21:07:56 +02:00
102 changed files with 22468 additions and 3163 deletions

32
.clang-format Normal file
View File

@@ -0,0 +1,32 @@
# clang-format is fairly limited, and these rules are approximate:
# - array initializers can get terribly mangled with clang-format 12.0,
# - sometimes it still aligns with space characters,
# - struct name NL { NL ... NL } NL name; is unachievable.
BasedOnStyle: GNU
ColumnLimit: 80
IndentWidth: 4
TabWidth: 4
UseTab: ForContinuationAndIndentation
BreakBeforeBraces: Allman
SpaceAfterCStyleCast: true
AlignAfterOpenBracket: DontAlign
AlignOperands: DontAlign
AlignConsecutiveMacros: Consecutive
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
IndentGotoLabels: false
# IncludeCategories has some potential, but it may also break the build.
# Note that the documentation says the value should be "Never".
SortIncludes: false
# This is a compromise, it generally works out aesthetically better.
BinPackArguments: false
# Unfortunately, this can't be told to align to column 40 or so.
SpacesBeforeTrailingComments: 2
# liberty-specific macro body wrappers.
MacroBlockBegin: "BLOCK_START"
MacroBlockEnd: "BLOCK_END"
ForEachMacros: ["LIST_FOR_EACH"]

10
.gitignore vendored
View File

@@ -3,7 +3,9 @@
# Qt Creator files
/CMakeLists.txt.user*
/uirc3.config
/uirc3.files
/uirc3.creator*
/uirc3.includes
/xK.config
/xK.files
/xK.creator*
/xK.includes
/xK.cflags
/xK.cxxflags

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "liberty"]
path = liberty
url = git://github.com/pjanouch/liberty.git
url = https://git.janouch.name/p/liberty.git

View File

@@ -1,44 +0,0 @@
sudo: required
dist: trusty
language: c
notifications:
irc:
channels: "irc.janouch.name#dev"
use_notice: true
skip_join: true
env:
global:
- secure: "ck6keK5tTbVCN7VGyKglS890hjovUNt2zyOydiyFtQDciaB/rvEwkKy4anMCEdZHFpGAPE9iBmNYaGUsD1Y+KifhhImVMbuThe2D8MLv5crSLRheYPbbmhO8MWPAxmQnuQhpwsUKZlHvUfX8nh+d0juNdqXklvhVml78Gi99QFw="
matrix:
- readline=ON libedit=OFF
- readline=OFF libedit=ON
addons:
coverity_scan:
project:
name: "pjanouch/uirc3"
description: "Experimental IRC client, daemon and bot"
notification_email: p.janouch@gmail.com
build_command_prepend: "cmake .. -DCMAKE_BUILD_TYPE=Release"
build_command: "make"
branch_pattern: coverity_scan
compiler:
- clang
- gcc
before_install:
# We need this PPA for a recent version of libedit
- sudo add-apt-repository ppa:ondrej/php5-5.6 -y
# We need this PPA for Lua 5.3
- sudo add-apt-repository ppa:vbernat/haproxy-1.6 -y
- sudo apt-get update -qq
install:
- sudo apt-get install -y libncursesw5-dev libreadline-dev libedit-dev
liblua5.3-dev libffi-dev help2man expect
before_script:
- mkdir build
- cd build
script:
- cmake .. -DCMAKE_INSTALL_PREFIX=/usr
-DWANT_READLINE=$readline -DWANT_LIBEDIT=$libedit
- make all test
- cpack -G DEB
- ../test

View File

@@ -1,22 +1,32 @@
project (uirc3 C)
cmake_minimum_required (VERSION 2.8.11)
# Ubuntu 18.04 LTS and OpenBSD 6.4
cmake_minimum_required (VERSION 3.10)
file (READ xK-version project_version)
configure_file (xK-version xK-version.tag COPYONLY)
string (STRIP "${project_version}" project_version)
project (xK VERSION "${project_version}"
DESCRIPTION "IRC daemon, bot, TUI client and its web frontend" LANGUAGES C)
# Options
option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
option (WANT_LIBEDIT "Use BSD libedit for the UI" OFF)
option (WANT_XF "Build xF" OFF)
# Moar warnings
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
# -Wunused-function is pretty annoying here, as everything is static
set (CMAKE_C_FLAGS
"${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra -Wno-unused-function")
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
set (CMAKE_C_STANDARD 99)
set (CMAKE_C_STANDARD_REQUIRED ON)
set (CMAKE_C_EXTENSIONS OFF)
# Version
set (project_version "0.9.3")
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
# -Wunused-function is pretty annoying here, as everything is static
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-function")
endif ()
# Try to append commit ID if it follows a version tag. It might be nicer if
# we could also detect dirty worktrees but that's very hard to get right.
# If we didn't need this for CPack, we could use add_custom_command to generate
# a version source/include file.
find_package (Git)
set (git_head "${PROJECT_SOURCE_DIR}/.git/HEAD")
if (GIT_FOUND AND EXISTS "${git_head}")
@@ -26,8 +36,8 @@ if (GIT_FOUND AND EXISTS "${git_head}")
set (git_ref "${PROJECT_SOURCE_DIR}/.git/${CMAKE_MATCH_1}")
if (EXISTS "${git_ref}")
configure_file ("${git_ref}" git-ref.tag COPYONLY)
endif (EXISTS "${git_ref}")
endif (git_head_content MATCHES "^ref: ([^\r\n]+)")
endif ()
endif ()
execute_process (COMMAND ${GIT_EXECUTABLE} describe --tags --match v*
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
@@ -35,8 +45,8 @@ if (GIT_FOUND AND EXISTS "${git_head}")
OUTPUT_VARIABLE git_describe OUTPUT_STRIP_TRAILING_WHITESPACE)
if (NOT git_describe_result)
string (REGEX REPLACE "^v" "" project_version "${git_describe}")
endif (NOT git_describe_result)
endif (GIT_FOUND AND EXISTS "${git_head}")
endif ()
endif ()
# Dashes make filenames confusing and upset packaging software
string (REPLACE "-" "+" project_version_safe "${project_version}")
@@ -52,21 +62,22 @@ include_directories (${libssl_INCLUDE_DIRS})
link_directories (${libssl_LIBRARY_DIRS})
if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
include_directories (/usr/local/include)
link_directories (/usr/local/lib)
# Need this for SIGWINCH in FreeBSD and OpenBSD respectively;
# our POSIX version macros make it undefined
add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1)
endif ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
elseif (APPLE)
add_definitions (-D_DARWIN_C_SOURCE)
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}})
endif (extra_lib_${extra})
endforeach (extra)
endif ()
endforeach ()
include (CheckCSourceRuns)
set (CMAKE_REQUIRED_LIBRARIES ${project_libraries})
@@ -76,53 +87,59 @@ 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})
# FIXME: other Lua versions may be acceptable, don't know yet
pkg_search_module (lua lua53 lua5.3 lua-5.3 lua>=5.3)
option (WITH_LUA "Enable experimental support for Lua plugins" ${lua_FOUND})
# XXX: other Lua versions may be acceptable, don't know yet
pkg_search_module (lua lua53 lua5.3 lua-5.3 lua54 lua5.4 lua-5.4 lua>=5.3)
option (WITH_LUA "Enable support for Lua plugins" ${lua_FOUND})
if (WITH_LUA)
if (NOT lua_FOUND)
message (FATAL_ERROR "Lua library not found")
endif (NOT lua_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 (WITH_LUA)
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 (CURSES_FOUND)
else ()
message (SEND_ERROR "Curses not found")
endif (ncursesw_FOUND)
endif ()
if ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT))
message (SEND_ERROR "You have to choose either GNU Readline or libedit")
elseif (WANT_READLINE)
pkg_check_modules (readline readline)
# OpenBSD's default readline is too old
if ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD")
include_directories (/usr/local/include/ereadline)
list (APPEND degesch_libraries ereadline)
else ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD")
list (APPEND degesch_libraries readline)
endif ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD")
include_directories (${OPENBSD_LOCALBASE}/include/ereadline)
list (APPEND xC_libraries ereadline)
elseif (readline_FOUND)
list (APPEND xC_libraries ${readline_LIBRARIES})
include_directories (${readline_INCLUDE_DIRS})
link_directories (${readline_LIBRARY_DIRS})
else ()
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 ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT))
endif ()
# Generate a configuration file
set (HAVE_READLINE "${WANT_READLINE}")
@@ -130,57 +147,74 @@ set (HAVE_EDITLINE "${WANT_LIBEDIT}")
set (HAVE_LUA "${WITH_LUA}")
include (GNUInstallDirs)
# ZyklonB is currently an odd duck but degesch follows normal XDG rules
set (zyklonb_plugin_dir ${CMAKE_INSTALL_LIBDIR}/zyklonb/plugins)
configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)
set (project_config ${PROJECT_BINARY_DIR}/config.h)
configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${project_config})
include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR})
# Project source files
set (common_sources)
set (common_headers ${PROJECT_BINARY_DIR}/config.h)
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
# Generate IRC replies--we need a custom target because of the multiple outputs
add_custom_command (OUTPUT xD-replies.c xD.msg
COMMAND env LC_ALL=C awk
-f ${PROJECT_SOURCE_DIR}/xD-gen-replies.awk
${PROJECT_SOURCE_DIR}/xD-replies > xD-replies.c
DEPENDS
${PROJECT_SOURCE_DIR}/xD-gen-replies.awk
${PROJECT_SOURCE_DIR}/xD-replies
COMMENT "Generating files from the list of server numerics")
set_source_files_properties (${PROJECT_BINARY_DIR}/kike-replies.c
PROPERTIES HEADER_FILE_ONLY TRUE)
add_custom_target (replies DEPENDS ${PROJECT_BINARY_DIR}/xD-replies.c)
add_custom_command (OUTPUT xC-proto.c
COMMAND env LC_ALL=C awk
-f ${PROJECT_SOURCE_DIR}/liberty/tools/lxdrgen.awk
-f ${PROJECT_SOURCE_DIR}/liberty/tools/lxdrgen-c.awk
-v PrefixCamel=Relay
${PROJECT_SOURCE_DIR}/xC.lxdr > xC-proto.c
DEPENDS
${PROJECT_SOURCE_DIR}/liberty/tools/lxdrgen.awk
${PROJECT_SOURCE_DIR}/liberty/tools/lxdrgen-c.awk
${PROJECT_SOURCE_DIR}/xC.lxdr
COMMENT "Generating xC relay protocol code" VERBATIM)
add_custom_target (xC-proto DEPENDS ${PROJECT_BINARY_DIR}/xC-proto.c)
# Build
add_executable (zyklonb zyklonb.c ${common_sources} ${common_headers})
target_link_libraries (zyklonb ${project_libraries})
add_threads (zyklonb)
foreach (name xB xC xD)
add_executable (${name} ${name}.c ${project_config})
target_link_libraries (${name} ${project_libraries})
add_threads (${name})
endforeach ()
add_executable (degesch degesch.c kike-replies.c
${common_sources} ${common_headers})
target_link_libraries (degesch ${project_libraries} ${degesch_libraries})
add_threads (degesch)
add_dependencies (xD replies)
add_dependencies (xC replies xC-proto)
target_link_libraries (xC ${xC_libraries})
add_executable (kike kike.c kike-replies.c ${common_sources} ${common_headers})
target_link_libraries (kike ${project_libraries})
add_threads (kike)
if (WANT_XF)
pkg_check_modules (x11 REQUIRED x11 xrender xft fontconfig)
include_directories (${x11_INCLUDE_DIRS})
link_directories (${x11_LIBRARY_DIRS})
add_executable (xF xF.c ${project_config})
add_dependencies (xF xC-proto)
target_link_libraries (xF ${x11_LIBRARIES} ${project_libraries})
add_threads (xF)
endif ()
# Tests
function (make_tests_for target_name)
get_target_property (sources ${target_name} SOURCES)
get_target_property (libraries ${target_name} LINK_LIBRARIES)
get_target_property (options ${target_name} COMPILE_OPTIONS)
set (test test-${target_name})
add_executable (${test} ${sources})
target_link_libraries (${test} ${libraries})
set_target_properties (${test} PROPERTIES
COMPILE_DEFINITIONS TESTING
COMPILE_OPTIONS "${options}")
add_test (NAME ${test} COMMAND ${test})
endfunction (make_tests_for)
include (CTest)
if (BUILD_TESTING)
make_tests_for (degesch)
endif (BUILD_TESTING)
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-xC COMMAND test-xC)
add_test (NAME custom-static-analysis
COMMAND ${PROJECT_SOURCE_DIR}/test-static)
endif ()
option (BUILD_TESTING_WDYE "Build the integration test" OFF)
if (BUILD_TESTING_WDYE)
add_subdirectory (liberty/tools/wdye)
add_test (NAME integration COMMAND wdye "${PROJECT_SOURCE_DIR}/test.lua")
endif ()
# Various clang-based diagnostics, loads of fake positives and spam
file (GLOB clang_tidy_sources *.c)
@@ -197,28 +231,50 @@ 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})
install (DIRECTORY plugins/zyklonb/
DESTINATION ${zyklonb_plugin_dir} 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 program help
find_program (HELP2MAN_EXECUTABLE help2man)
if (NOT HELP2MAN_EXECUTABLE)
message (FATAL_ERROR "help2man not found")
endif (NOT HELP2MAN_EXECUTABLE)
# Generate documentation from text markup
find_program (ASCIIDOCTOR_EXECUTABLE asciidoctor)
find_program (A2X_EXECUTABLE a2x)
if (NOT ASCIIDOCTOR_EXECUTABLE AND NOT A2X_EXECUTABLE)
message (WARNING "Neither asciidoctor nor a2x were found, "
"falling back to a substandard manual page generator")
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}
COMMAND ${HELP2MAN_EXECUTABLE} -N
"${PROJECT_BINARY_DIR}/${page}" -o ${page_output}
DEPENDS ${page}
COMMENT "Generating man page for ${page}" VERBATIM)
endforeach (page)
if (ASCIIDOCTOR_EXECUTABLE)
add_custom_command (OUTPUT ${page_output}
COMMAND ${ASCIIDOCTOR_EXECUTABLE} -b manpage
-a release-version=${project_version}
-o "${page_output}"
"${PROJECT_SOURCE_DIR}/${page}.adoc"
DEPENDS ${page}.adoc
COMMENT "Generating man page for ${page}" VERBATIM)
elseif (A2X_EXECUTABLE)
add_custom_command (OUTPUT ${page_output}
COMMAND ${A2X_EXECUTABLE} --doctype manpage --format manpage
-a release-version=${project_version}
-D "${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/${page}.adoc"
DEPENDS ${page}.adoc
COMMENT "Generating man page for ${page}" VERBATIM)
else ()
set (ASCIIMAN ${PROJECT_SOURCE_DIR}/liberty/tools/asciiman.awk)
add_custom_command (OUTPUT ${page_output}
COMMAND env LC_ALL=C asciidoc-release-version=${project_version}
awk -f ${ASCIIMAN} "${PROJECT_SOURCE_DIR}/${page}.adoc"
> ${page_output}
DEPENDS ${page}.adoc ${ASCIIMAN}
COMMENT "Generating man page for ${page}" VERBATIM)
endif ()
endforeach ()
add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES})
@@ -226,13 +282,12 @@ foreach (page ${project_MAN_PAGES})
string (REGEX MATCH "\\.([0-9])$" manpage_suffix "${page}")
install (FILES "${page}"
DESTINATION "${CMAKE_INSTALL_MANDIR}/man${CMAKE_MATCH_1}")
endforeach (page)
endforeach ()
# CPack
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Experimental IRC client, daemon and bot")
set (CPACK_PACKAGE_VERSION "${project_version_safe}")
set (CPACK_PACKAGE_VENDOR "Premysl Janouch")
set (CPACK_PACKAGE_CONTACT "Přemysl Janouch <p.janouch@gmail.com>")
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")
set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
set (CPACK_GENERATOR "TGZ;ZIP")

View File

@@ -1,8 +1,7 @@
Copyright (c) 2014 - 2015, Přemysl Janouch <p.janouch@gmail.com>
Copyright (c) 2014 - 2025, 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, provided that the above
copyright notice and this permission notice appear in all copies.
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF

346
NEWS
View File

@@ -1,37 +1,313 @@
Unreleased
* xC: added more characters as nickname delimiters,
so that @nick works as a highlight
* xC: prevented rare crashes in relay code
* xP: added a network lag indicator to the user interface
* xP: started embedding the necessary web resources,
and making sure that the files have unique paths after change,
so that stale copies are not cached by browsers indefinitely
* Bumped relay protocol version
2.1.0 (2024-12-19) "Bunnyrific"
* xC: fixed a crash when the channel topic had too many formatting items
* xC: fixed keyboard EOF behaviour with Readline >= 8.0
* xC: made it possible to stream commands into the binary
* xM/xW: various bugfixes
* Added a Fyne frontend for xC called xA
* Added a Qt Widgets frontend for xC called xT
2.0.0 (2024-07-28) "Perfect Is the Enemy of Good"
* xD: now using SHA-256 for client certificate fingerprints
* xD: implemented WALLOPS, choosing to make it target even non-operators
* xC: made it show WALLOPS messages, as PRIVMSG for the server buffer
* xC: all behaviour.* configuration options have been renamed to general.*,
with the exception of editor_command/editor, backlog_helper/pager,
and backlog_helper_strip_formatting/pager_strip_formatting
* xC: all attributes.* configuration options have been made abstract in
a subset of the git-config(1) format, and renamed to theme.*,
with the exception of attributes.reset, which has no replacement
* xC: replaced behaviour.save_on_quit with general.autosave
* xC: the servers.*.command configuration option now supports multiple lines
* xC: improved pager integration capabilities
* xC: unsolicited JOINs will no longer automatically activate the buffer
* xC: made Readline insert the longest common completion prefix first,
and prevented the possible-completions command from duplicating the prompt
* xC: normalized editline's history behaviour, making it a viable frontend
* xC: various bugfixes
* xC: added a relay interface, enabled through the general.relay_bind option
* Added a web frontend for xC called xP
* Added a Win32 frontend for xC called xW
* Added a Cocoa frontend for xC called xM
* Added a Go port of xD called xS
* Added a simple notifier called xN
1.5.0 (2021-12-21) "The Show Must Go On"
* xC: made it possible to pass the cursor position to external editors,
in particular VIM and Emacs
* xC: started quoting text coming from bracketed pastes,
to minimize the risk of trying to execute filesystem paths as commands
* xC: fixed to work with post-2021-08-29 editline
* xC: extended editline's autocomplete to show all options
* utm-filter.lua: added Facebook's tracking parameter to the filter
1.4.0 (2021-10-06) "Call Me Scruffy Scruffington"
* xC: made message autosplitting respect text formatting
* xC: fixed displaying IRC colours above 16
* xC: offer IRCnet as an IRC network to connect to,
rather than the lunatic new Freenode
* xD: started bumping the soft limit on file descriptors to the hard one
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'?"
* xC: made fancy-prompt.lua work with libedit
* xD: fixed a regression with an unspecified "bind_host"
* Miscellaneous minor improvements
1.0.0 (2020-10-29) "We're Finally There!"
* Coming with real manual pages instead of help2man-generated stubs
* xC: added support for more IRC colours and strike-through text (M-m x)
* xC: now tolerating all UTF-8 messages cut off by the server
* xC: disabled "behaviour.backlog_helper_strip_formatting" by default
since the relevant issue with ACS terminfo entries has been resolved
* xC: enabled word wrapping in the backlog by default
* xC: made the unread marker span the whole line, with a configurable
character; the previous behaviour can be obtained by setting it empty
* xC: fixed the prompt not showing back up after exiting a backlog helper
when an external event has provoked an attempt to change it
* xC: now watching fellow channel users' away status when the server
supports the away-notify capability; indicated by italicised nicknames
* xC: added a plugin to highlight prime numbers in incoming messages
* xD: make sure an unspecified "bind_host" binds to both IPv4 and IPv6
* 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"
* xC: fixed a crash and prompt attribute output in libedit 20191231-3.1,
though users are officially discouraged from using this library
* xC: fixed Lua 5.4 build, so far the support is experimental
* Miscellaneous little fixes
0.9.7 (2018-10-21) "Business as Usual"
* xD: fix wildcard handling in WHOIS
* xD: properly handle STATS without parametetrs
* xD: abort earlier when an invalid mode character is detected while
processing channel MODE messages
* xD: do not send NICK notifications when the nickname doesn't really change
* xD: fix hostname string verification (only used for "server_name")
0.9.6 (2018-06-22) "I've Been Sitting Here All This Time"
* Code has been relicensed to 0BSD and moved to a private git hosting
* Fix LibreSSL compatibility
* xC: a second /disconnect cuts the connection by force
* xC: send a QUIT message to the IRC server on Ctrl-C
* xC: add a Slack plugin (even though the gateway's now defunct)
* xC: show an error message on log write failure
* xC: fix parsing of literal IPv6 addresses with port numbers
* xC: fix some error messages
* xC: workaround a Readline bug in the fancy-prompt.lua plugin
* xD: fix two memory leaks
* xD: improve error handling for incoming connections
* xD: disable TLS session reuse
0.9.5 (2016-12-30) "It's Time"
* Better support for the KILL command
* xC: export many more fields to the Lua API, add a prompt hook
* xC: show channel user count in the prompt
* xC: allow hiding join/part messages and other noise (Meta-Shift-H)
* xC: allow autojoining channels with keys
* xC: rejoin channels with keys on reconnect
* xC: make /query without arguments just open the buffer
* xC: add a censor plugin
* xC: die on configuration parse errors
* xC: request channel modes also on rejoin
* xC: don't show remembered channel modes on parted channels
* xC: fix highlight detection in colored text
* xC: fix CTCP handling for the real world and don't decode X-QUOTEs
* xC: add support for OpenSSL 1.1.0
0.9.4 (2016-04-28) "Oops"
* xC: fix crash on characters invalid in Windows-1252
* xC: add an auto-rejoin plugin
* xC: better date change messages with customizable formatting;
now also used in the backlog, so it looks closer to regular output
* xB: add a calc plugin providing a basic Scheme REPL
* xB: add a seen plugin
* 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
@@ -40,28 +316,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)
@@ -72,23 +348,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

@@ -1,167 +1,260 @@
uirc3
=====
:compact-option:
xK
==
The unethical IRC trinity. This project consists of an experimental 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.
'xK' (chat kit) is an IRC software suite consisting of a daemon, bot, notifier,
terminal client, and web/Windows/macOS/Linux/FreeBSD/Android/iOS frontends
for the client. It's all you're ever going to need for chatting, so long as
you can make do with slightly minimalist software.
All of them have these potentially interesting properties:
They're all lean on dependencies, and offer a maximally permissive licence.
- full IPv6 support
- TLS support, including client certificates
- lean on dependencies (with the exception of 'degesch')
- compact and arguably easy to hack on
- permissive license
xC
--
The IRC client, and the core of 'xK'. It is largely defined by building on top
of GNU Readline or BSD Editline that have been hacked to death. Its interface
should feel somewhat familiar for weechat or irssi users.
degesch
-------
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::xC.webp[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,
a powerful configuration system, integrated help, text formatting, CTCP queries,
automatic splitting of overlong messages, autocomplete, logging to file,
auto-away, command aliases and rudimentary support for Lua scripting.
It has most features you'd expect of an IRC client, such as being multiserver,
a powerful configuration system, integrated help, text formatting, automatic
message splitting, multiline editing, bracketed paste support, word wrapping
that doesn't break links, autocomplete, logging, CTCP queries, auto-away,
command aliases, SOCKS proxying, SASL EXTERNAL authentication using TLS client
certificates, a remote relay interface, or basic support for Lua scripting.
As a unique bonus, you can launch a full text editor from within.
kike
----
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
do it just fine.
xP
--
The web frontend for 'xC', making use of its networked relay interface.
It intentionally differs in that it uses a sans-serif font, and it shows
the list of all buffers in a side panel. Otherwise it is a near replica,
including link:xC.adoc#_key_bindings[keyboard shortcuts].
Notable features:
image::xP.webp[align="center"]
- TLS autodetection (why doesn't everyone have this?), using secure defaults
- IRCop authentication via TLS client certificates
- epoll/kqueue support; this means that it should be able to handle quite
a number of concurrent user connections
- partial IRCv3 support
xA, xT, xW, xM
--------------
Fyne, Qt Widgets, Win32, Cocoa frontends for 'xC'.
Using them is not recommended.
Not supported:
xD
--
The IRC daemon. It is designed for use as a regular user application rather
than a system-wide daemon, and follows the XDG Base Directory Specification.
If all you want is a decent, minimal IRCd for testing purposes or a small
network of respectful users (or bots), this one will do it just fine.
- 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
be used to implement this feature if needed
- limits of almost any kind, just connections and mode `+l`
It autodetects TLS on incoming connections (I'm still wondering why everyone
doesn't have this), authenticates operators via TLS client certificate
fingerprints, and supports a number of IRCv3 capabilities.
ZyklonB
-------
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.
What it notably doesn't support is online changes to configuration, any limits
besides the total number of connections and mode `+l`, or server linking
(which also means no services).
While originally intended to be a simple rewrite of the original AWK bot in C,
it fairly quickly became a playground, and it eventually got me into writing
the rest of the package.
xS
--
The IRC daemon again, this time ported to Go, additionally supporting WEBIRC,
and thus ideal for pairing with, e.g.,
https://github.com/kiwiirc/webircgateway[].
Any further development, such as P10 or TS6 linking for IRC services,
or plugin support for arbitrary bridges, will happen here.
It survives crashes, server disconnects and timeouts, and also has native SOCKS
support (even though socksify can add that easily to any program).
xN
--
The IRC notifier, should you ever need to send automated messages from a script.
xB
--
The IRC bot. While originally intended to be a simple rewrite of my old GNU AWK
bot in C, it fairly quickly became a playground, and it eventually got me into
writing the rest of this package.
Its main characteristic is that it runs plugins as coprocesses, allowing for
enhanced reliability and programming language freedom. Moreover, it recovers
from any crashes, and offers native SOCKS support (even though socksify can add
that easily to any program).
Packages
--------
Regular releases are sporadic. git master should be stable enough. You can get
a package with the latest development version from Archlinux's AUR, or from
openSUSE Build Service for the rest of mainstream distributions. Consult the
list of repositories and their respective links at:
https://build.opensuse.org/project/repositories/home:pjanouch:git
Regular releases are sporadic. git master should be stable enough.
You can get a package with the latest development version using Arch Linux's
https://aur.archlinux.org/packages/xk-git[AUR],
or as a https://git.janouch.name/p/nixexprs[Nix derivation].
Building
--------
Build dependencies: CMake, pkg-config, help2man, awk, sh, liberty (included) +
Runtime dependencies: openssl +
Additionally for degesch: curses, libffi, lua >= 5.3 (optional),
readline >= 6.0 or libedit >= 2013-07-12
Build-only dependencies: CMake, pkg-config, awk, liberty (included),
asciidoctor or asciidoc (recommended but optional) +
Common runtime dependencies: openssl +
Additionally for 'xC': curses, libffi, readline >= 6.0 or libedit >= 2013-07-12,
lua >= 5.3 (optional) +
$ git clone --recursive https://github.com/pjanouch/uirc3.git
$ mkdir uirc3/build
$ cd uirc3/build
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug \
-DWANT_READLINE=ON -DWANT_LIBEDIT=OFF -DWANT_LUA=ON
$ git clone --recursive https://git.janouch.name/p/xK.git
$ mkdir xK/build
$ cd xK/build
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DWANT_READLINE=ON -DWANT_LIBEDIT=OFF -DWITH_LUA=ON
$ make
To install the application, you can do either the usual:
# make install
Or you can try telling CMake to make a package for you. For Debian it is:
Or you can try telling CMake to make a package for you:
$ cpack -G DEB
# dpkg -i uirc3-*.deb
Note that for versions of CMake before 2.8.9, you need to prefix `cpack` with
`fakeroot` or file ownership will end up wrong.
$ cpack -G DEB # also supported: RPM, FreeBSD
# dpkg -i xK-*.deb
Usage
-----
'degesch' has in-program configuration. Just run it and read the instructions.
'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.
xP
~~
The precondition for running 'xC' frontends is enabling its relay interface:
/set general.relay_bind = "127.0.0.1:9000"
To build the web server, install the Go compiler, and run `make`
from the _xP_ directory. Then start the resulting binary, and navigate to
the adress you give it as its first argument--in the following example,
that would be http://localhost:8080[]:
$ ./xP 127.0.0.1:8080 127.0.0.1:9000
For remote use, it's recommended to put 'xP' behind a reverse proxy, with TLS,
and some form of HTTP authentication. Pass the external URL of the WebSocket
endpoint as the third command line argument in this case.
xA
~~
The Fyne frontend supports all of Linux, FreeBSD, Windows, macOS, Android, and
iOS natively, albeit somewhat poorly. Only use `fyne` or `fyne-cross` after
running `make generate` first.
xT
~~
The Qt Widgets frontend is a separate CMake subproject. It generally supports
all desktop operating systems. To avoid having to specify the relay address
each time you run it, pass it on the command line.
xW
~~
The Win32 frontend is a separate CMake subproject that should be compiled
using MinGW-w64. To avoid having to specify the relay address each time you
run it, create a shortcut for the executable and include the address in its
_Target_ field:
C:\...\xW.exe 127.0.0.1 9000
It works reasonably well starting with Windows 7.
xM
~~
The Cocoa frontend is a separate CMake subproject that requires Xcode to build.
It is currently not that usable. The relay address can either be passed on
the command line, or preset in the _defaults_ database:
$ defaults write name.janouch.xM relayHost 127.0.0.1
$ defaults write name.janouch.xM relayPort 9000
Client Certificates
-------------------
'kike' uses SHA1 fingerprints of TLS client certificates to authenticate users.
To get the fingerprint from a certificate file in the required form, use:
'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.
$ openssl x509 -in public.pem -outform DER | sha1sum
'xD' and 'xS' use SHA-256 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 | sha256sum
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:
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:
....
# 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
....
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 xC look like the screenshot?
------------------------------------------
With the defaults, 'xC' doesn't look too fancy because I don't want to have
a hard dependency on either Lua for the bundled script that provides an easily
adjustable enhanced prompt, or on 256-colour terminals. Moreover, it's nearly
impossible to come up with a colour theme that would work well with both
black-on-white and white-on-black terminals, or anything wild in between.
Assuming that your build supports Lua plugins, and that you have a decent,
properly set-up terminal emulator, it suffices to run:
/set general.pager = Press Tab here and change +Gb to +Gb1d
/set general.date_change_line = "%a %e %b %Y"
/set general.plugin_autoload += "fancy-prompt.lua"
/set theme.userhost = "109"
/set theme.join = "108"
/set theme.part = "138"
/set theme.external = "248"
/set theme.timestamp = "250 255"
/set theme.read_marker = "202"
Configuration profiles
----------------------
Even though the applications don't directly support configuration profiles,
they conform to the XDG standard, and thus you can change the location they
load configuration from via XDG_CONFIG_HOME (normally '~/.config') and the
location where store their data via XDG_DATA_HOME (normally '~/.local/share').
It would be relatively easy to make the applications assume whatever name you
run them under (for example by using symbolic links), and load different
configurations accordingly, but I consider it rather messy and unnecessary.
Contributing and Support
------------------------
Use this project's GitHub to report any bugs, request features, or submit pull
requests. If you want to discuss this project, or maybe just hang out with
the developer, feel free to join me at irc://irc.janouch.name, channel #dev.
Use https://git.janouch.name/p/xK to report any bugs, request features,
or submit pull requests. `git send-email` is tolerated. If you want to discuss
the project, feel free to join me at ircs://irc.janouch.name, channel #dev.
Disclaimer
----------
I am not an antisemitist, I'm just being an offensive asshole with the naming.
And no, I'm not going to change the names.
Bitcoin donations are accepted at: 12r5uEWEgcHC46xd64tt3hHt9EUvYYDHe9
License
-------
'uirc3' is written by Přemysl Janouch <p.janouch@gmail.com>.
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.
You may use the software under the terms of the ISC license, the text of which
is included within the package, or, at your option, you may relicense the work
under the MIT or the Modified BSD License, as listed at the following site:
http://www.gnu.org/licenses/license-list.html
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.

301
common.c
View File

@@ -1,11 +1,10 @@
/*
* common.c: common functionality
*
* Copyright (c) 2014 - 2015, Přemysl Janouch <p.janouch@gmail.com>
* Copyright (c) 2014 - 2022, 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, provided that the above
* copyright notice and this permission notice appear in all copies.
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
@@ -23,28 +22,129 @@
#define LIBERTY_WANT_PROTO_IRC
#ifdef WANT_SYSLOG_LOGGING
#define print_fatal_data ((void *) LOG_ERR)
#define print_error_data ((void *) LOG_ERR)
#define print_warning_data ((void *) LOG_WARNING)
#define print_status_data ((void *) LOG_INFO)
#define print_debug_data ((void *) LOG_DEBUG)
#define print_fatal_data ((void *) LOG_ERR)
#define print_error_data ((void *) LOG_ERR)
#define print_warning_data ((void *) LOG_WARNING)
#define print_status_data ((void *) LOG_INFO)
#define print_debug_data ((void *) LOG_DEBUG)
#endif // WANT_SYSLOG_LOGGING
#include "liberty/liberty.c"
#include <arpa/inet.h>
#include <netinet/tcp.h>
/// Shorthand to set an error and return failure from the function
#define FAIL(...) \
BLOCK_START \
error_set (e, __VA_ARGS__); \
return 0; \
BLOCK_END
static void
init_openssl (void)
{
#if OPENSSL_VERSION_NUMBER < 0x10100000L || LIBRESSL_VERSION_NUMBER
SSL_library_init ();
// XXX: this list is probably not complete
atexit (EVP_cleanup);
SSL_load_error_strings ();
atexit (ERR_free_strings);
#else
// Cleanup is done automatically via atexit()
OPENSSL_init_ssl (0, NULL);
#endif
}
static char *
gai_reconstruct_address (struct addrinfo *ai)
{
char host[NI_MAXHOST] = {}, port[NI_MAXSERV] = {};
int err = getnameinfo (ai->ai_addr, ai->ai_addrlen,
host, sizeof host, port, sizeof port,
NI_NUMERICHOST | NI_NUMERICSERV);
if (err)
{
print_debug ("%s: %s", "getnameinfo", gai_strerror (err));
return xstrdup ("?");
}
return format_host_port_pair (host, port);
}
static bool
accept_error_is_transient (int err)
{
// OS kernels may return a wide range of unforeseeable errors.
// Assuming that they're either transient or caused by
// a connection that we've just extracted from the queue.
switch (err)
{
case EBADF:
case EINVAL:
case ENOTSOCK:
case EOPNOTSUPP:
return false;
default:
return true;
}
}
/// Destructively tokenize an address into a host part, and a port part.
/// The port is only overwritten if that part is found, allowing for defaults.
static const char *
tokenize_host_port (char *address, const char **port)
{
// Unwrap IPv6 addresses in format_host_port_pair() format.
char *rbracket = strchr (address, ']');
if (*address == '[' && rbracket)
{
if (rbracket[1] == ':')
{
*port = rbracket + 2;
return *rbracket = 0, address + 1;
}
if (!rbracket[1])
return *rbracket = 0, address + 1;
}
char *colon = strchr (address, ':');
if (colon)
{
*port = colon + 1;
return *colon = 0, address;
}
return address;
}
// --- To be moved to liberty --------------------------------------------------
// FIXME: in xssl_get_error() we rely on error reasons never being NULL (i.e.,
// all loaded), which isn't very robust.
// TODO: check all places where this is used and see if we couldn't gain better
// information by piecing together some other subset of data from the error
// stack. Most often, this is used in an error_set() context, which would
// allow us to allocate memory instead of returning static strings.
static const char *
xerr_describe_error (void)
{
unsigned long err = ERR_get_error ();
if (!err)
return "undefined error";
const char *reason = ERR_reason_error_string (err);
do
// Not thread-safe, not a concern right now--need a buffer
print_debug ("%s", ERR_error_string (err, NULL));
while ((err = ERR_get_error ()));
if (!reason)
return "cannot retrieve error description";
return reason;
}
static struct str
str_from_cstr (const char *cstr)
{
struct str self;
self.alloc = (self.len = strlen (cstr)) + 1;
self.str = memcpy (xmalloc (self.alloc), cstr, self.alloc);
return self;
}
static ssize_t
str_vector_find (const struct str_vector *v, const char *s)
strv_find (const struct strv *v, const char *s)
{
for (size_t i = 0; i < v->len; i++)
if (!strcmp (v->vector[i], s))
@@ -56,129 +156,15 @@ static time_t
unixtime_msec (long *msec)
{
#ifdef _POSIX_TIMERS
struct timespec tp;
hard_assert (clock_gettime (CLOCK_REALTIME, &tp) != -1);
*msec = tp.tv_nsec / 1000000;
struct timespec tp;
hard_assert (clock_gettime (CLOCK_REALTIME, &tp) != -1);
*msec = tp.tv_nsec / 1000000;
#else // ! _POSIX_TIMERS
struct timeval tp;
hard_assert (gettimeofday (&tp, NULL) != -1);
*msec = tp.tv_usec / 1000;
struct timeval tp;
hard_assert (gettimeofday (&tp, NULL) != -1);
*msec = tp.tv_usec / 1000;
#endif // ! _POSIX_TIMERS
return tp.tv_sec;
}
/// This differs from the non-unique version in that we expect the filename
/// to be something like a pattern for mkstemp(), so the resulting path can
/// reside in a system-wide directory with no risk of a conflict.
static char *
resolve_relative_runtime_unique_filename (const char *filename)
{
struct str path;
str_init (&path);
const char *runtime_dir = getenv ("XDG_RUNTIME_DIR");
const char *tmpdir = getenv ("TMPDIR");
if (runtime_dir && *runtime_dir == '/')
str_append (&path, runtime_dir);
else if (tmpdir && *tmpdir == '/')
str_append (&path, tmpdir);
else
str_append (&path, "/tmp");
str_append_printf (&path, "/%s/%s", PROGRAM_NAME, filename);
// Try to create the file's ancestors;
// typically the user will want to immediately create a file in there
const char *last_slash = strrchr (path.str, '/');
if (last_slash && last_slash != path.str)
{
char *copy = xstrndup (path.str, last_slash - path.str);
(void) mkdir_with_parents (copy, NULL);
free (copy);
}
return str_steal (&path);
}
static bool
xwrite (int fd, const char *data, size_t len, struct error **e)
{
size_t written = 0;
while (written < len)
{
ssize_t res = write (fd, data + written, len - written);
if (res >= 0)
written += res;
else if (errno != EINTR)
FAIL ("%s", strerror (errno));
}
return true;
}
// --- Simple network I/O ------------------------------------------------------
// TODO: move to liberty and remove from dwmstatus.c as well
#define SOCKET_IO_OVERFLOW (8 << 20) ///< How large a read buffer can be
enum socket_io_result
{
SOCKET_IO_OK, ///< Completed successfully
SOCKET_IO_EOF, ///< Connection shut down by peer
SOCKET_IO_ERROR ///< Connection error
};
static enum socket_io_result
socket_io_try_read (int socket_fd, struct str *rb, struct error **e)
{
// We allow buffering of a fair amount of data, however within reason,
// so that it's not so easy to flood us and cause an allocation failure
ssize_t n_read;
while (rb->len < SOCKET_IO_OVERFLOW)
{
str_ensure_space (rb, 4096);
n_read = recv (socket_fd, rb->str + rb->len,
rb->alloc - rb->len - 1 /* null byte */, 0);
if (n_read > 0)
{
rb->str[rb->len += n_read] = '\0';
continue;
}
if (n_read == 0)
return SOCKET_IO_EOF;
if (errno == EAGAIN)
return SOCKET_IO_OK;
if (errno == EINTR)
continue;
error_set (e, "%s", strerror (errno));
return SOCKET_IO_ERROR;
}
return SOCKET_IO_OK;
}
static enum socket_io_result
socket_io_try_write (int socket_fd, struct str *wb, struct error **e)
{
ssize_t n_written;
while (wb->len)
{
n_written = send (socket_fd, wb->str, wb->len, 0);
if (n_written >= 0)
{
str_remove_slice (wb, 0, n_written);
continue;
}
if (errno == EAGAIN)
return SOCKET_IO_OK;
if (errno == EINTR)
continue;
error_set (e, "%s", strerror (errno));
return SOCKET_IO_ERROR;
}
return SOCKET_IO_OK;
return tp.tv_sec;
}
// --- Logging -----------------------------------------------------------------
@@ -343,7 +329,7 @@ struct socks_connector
SOCKS_DATA_CB (socks_4a_finish)
{
uint8_t null, status;
uint8_t null = 0, status = 0;
hard_assert (msg_unpacker_u8 (unpacker, &null));
hard_assert (msg_unpacker_u8 (unpacker, &status));
@@ -446,7 +432,7 @@ SOCKS_DATA_CB (socks_5_request_domain)
SOCKS_DATA_CB (socks_5_request_finish)
{
uint8_t version, status, reserved, type;
uint8_t version = 0, status = 0, reserved = 0, type = 0;
hard_assert (msg_unpacker_u8 (unpacker, &version));
hard_assert (msg_unpacker_u8 (unpacker, &status));
hard_assert (msg_unpacker_u8 (unpacker, &reserved));
@@ -523,7 +509,7 @@ socks_5_request_start (struct socks_connector *self)
SOCKS_DATA_CB (socks_5_userpass_finish)
{
uint8_t version, status;
uint8_t version = 0, status = 0;
hard_assert (msg_unpacker_u8 (unpacker, &version));
hard_assert (msg_unpacker_u8 (unpacker, &status));
@@ -558,7 +544,7 @@ socks_5_userpass_start (struct socks_connector *self)
SOCKS_DATA_CB (socks_5_auth_finish)
{
uint8_t version, method;
uint8_t version = 0, method = 0;
hard_assert (msg_unpacker_u8 (unpacker, &version));
hard_assert (msg_unpacker_u8 (unpacker, &method));
@@ -762,7 +748,7 @@ socks_try_fill_read_buffer (struct socks_connector *self, size_t n)
return true;
ssize_t received;
str_ensure_space (&self->read_buffer, remains);
str_reserve (&self->read_buffer, remains);
do
received = recv (self->socket_fd,
self->read_buffer.str + self->read_buffer.len, remains, 0);
@@ -786,8 +772,8 @@ socks_call_on_data (struct socks_connector *self)
if (self->read_buffer.len < to_consume)
return true;
struct msg_unpacker unpacker;
msg_unpacker_init (&unpacker, self->read_buffer.str, self->read_buffer.len);
struct msg_unpacker unpacker =
msg_unpacker_make (self->read_buffer.str, self->read_buffer.len);
bool result = self->on_data (self, &unpacker);
str_remove_slice (&self->read_buffer, 0, to_consume);
return result;
@@ -852,16 +838,16 @@ socks_connector_init (struct socks_connector *self, struct poller *poller)
{
memset (self, 0, sizeof *self);
poller_fd_init (&self->socket_event, poller, (self->socket_fd = -1));
self->socket_event = poller_fd_make (poller, (self->socket_fd = -1));
self->socket_event.dispatcher = (poller_fd_fn) socks_connector_on_ready;
self->socket_event.user_data = self;
poller_timer_init (&self->timeout, poller);
self->timeout = poller_timer_make (poller);
self->timeout.dispatcher = (poller_timer_fn) socks_connector_on_timeout;
self->timeout.user_data = self;
str_init (&self->read_buffer);
str_init (&self->write_buffer);
self->read_buffer = str_make ();
self->write_buffer = str_make ();
}
static void
@@ -962,8 +948,8 @@ static struct ctcp_chunk *
ctcp_chunk_new (void)
{
struct ctcp_chunk *self = xcalloc (1, sizeof *self);
str_init (&self->tag);
str_init (&self->text);
self->tag = str_make ();
self->text = str_make ();
return self;
}
@@ -1023,6 +1009,13 @@ ctcp_intra_decode (const char *chunk, size_t len, struct str *output)
}
}
// According to the original CTCP specification we should use
// ctcp_intra_decode() on all parts, however no one seems to use that
// and it breaks normal text with backslashes
#ifndef SUPPORT_CTCP_X_QUOTES
#define ctcp_intra_decode(s, len, output) str_append_data (output, s, len)
#endif
static void
ctcp_parse_tagged (const char *chunk, size_t len, struct ctcp_chunk *output)
{
@@ -1045,15 +1038,11 @@ ctcp_parse_tagged (const char *chunk, size_t len, struct ctcp_chunk *output)
static struct ctcp_chunk *
ctcp_parse (const char *message)
{
struct str m;
str_init (&m);
struct str m = str_make ();
ctcp_low_level_decode (message, &m);
struct ctcp_chunk *result = NULL, *result_tail = NULL;
// According to the original CTCP specification we should use
// ctcp_intra_decode() on all parts, however no one seems to
// use that and it breaks normal text with backslashes
size_t start = 0;
bool in_ctcp = false;
for (size_t i = 0; i < m.len; i++)
@@ -1077,7 +1066,7 @@ ctcp_parse (const char *message)
if (my_is_ctcp)
ctcp_parse_tagged (m.str + my_start, i - my_start, chunk);
else
str_append_data (&chunk->text, m.str + my_start, i - my_start);
ctcp_intra_decode (m.str + my_start, i - my_start, &chunk->text);
LIST_APPEND_WITH_TAIL (result, result_tail, chunk);
}
@@ -1091,7 +1080,7 @@ ctcp_parse (const char *message)
chunk->is_partial = true;
}
else
str_append_data (&chunk->text, m.str + start, m.len - start);
ctcp_intra_decode (m.str + start, m.len - start, &chunk->text);
LIST_APPEND_WITH_TAIL (result, result_tail, chunk);
}

View File

@@ -2,7 +2,9 @@
#define CONFIG_H
#define PROGRAM_VERSION "${project_version}"
#define ZYKLONB_PLUGIN_DIR "${CMAKE_INSTALL_PREFIX}/${zyklonb_plugin_dir}"
// We use the XDG Base Directory Specification, but may be installed anywhere.
#define PROJECT_DATADIR "${CMAKE_INSTALL_FULL_DATADIR}"
#cmakedefine HAVE_READLINE
#cmakedefine HAVE_EDITLINE

View File

@@ -1,28 +0,0 @@
#!/bin/sh
LC_ALL=C exec awk '
BEGIN {
# The message catalog is a by-product
msg = "kike.msg"
print "$quote \"" > msg;
print "$set 1" > msg;
}
/^[0-9]+ *IRC_(ERR|RPL)_[A-Z]+ *".*"$/ {
match($0, /".*"/);
ids[$1] = $2;
texts[$2] = substr($0, RSTART, RLENGTH);
print $1 " " texts[$2] > msg
}
END {
printf("enum\n{")
for (i in ids) {
if (seen_first)
printf(",")
seen_first = 1
printf("\n\t%s = %s", ids[i], i)
}
print "\n};\n"
print "static const char *g_default_replies[] =\n{"
for (i in ids)
print "\t[" ids[i] "] = " texts[ids[i]] ","
print "};"
}'

Submodule liberty updated: 365aed456e...31ae400852

241
plugins/xB/calc Executable file
View File

@@ -0,0 +1,241 @@
#!/usr/bin/env guile
xB calc plugin, basic Scheme evaluator
Copyright 2016 Přemysl Eric Janouch
See the file LICENSE for licensing information.
!#
(import (rnrs (6)))
(use-modules ((rnrs) :version (6)))
; --- Message parsing ----------------------------------------------------------
(define-record-type message (fields prefix command params))
(define (parse-message line)
(let f ([parts '()] [chars (string->list line)])
(define (take-word w chars)
(if (or (null? chars) (eqv? (car chars) #\x20))
(f (cons (list->string (reverse w)) parts)
(if (null? chars) chars (cdr chars)))
(take-word (cons (car chars) w) (cdr chars))))
(if (null? chars)
(let ([data (reverse parts)])
(when (< (length data) 2)
(error 'parse-message "invalid message"))
(make-message (car data) (cadr data) (cddr data)))
(if (null? parts)
(if (eqv? (car chars) #\:)
(take-word '() (cdr chars))
(f (cons #f parts) chars))
(if (eqv? (car chars) #\:)
(f (cons (list->string (cdr chars)) parts) '())
(take-word '() chars))))))
; --- Utilities ----------------------------------------------------------------
(define (display-exception e port)
(define (puts . x)
(for-all (lambda (a) (display a port)) x)
(newline port))
(define (record-fields rec)
(let* ([rtd (record-rtd rec)]
[v (record-type-field-names rtd)]
[len (vector-length v)])
(map (lambda (k i) (cons k ((record-accessor rtd i) rec)))
(vector->list v)
(let c ([i len] [ls '()])
(if (= i 0) ls (c (- i 1) (cons (- i 1) ls)))))))
(puts "Caught " (record-type-name (record-rtd e)))
(for-all
(lambda (subtype)
(puts " " (record-type-name (record-rtd subtype)))
(for-all
(lambda (field) (puts " " (car field) ": " (cdr field)))
(record-fields subtype)))
(simple-conditions e)))
; XXX - we have to work around Guile's lack of proper eol-style support
(define xc (make-transcoder (latin-1-codec) 'lf 'replace))
(define irc-input-port (transcoded-port (standard-input-port) xc))
(define irc-output-port (transcoded-port (standard-output-port) xc))
(define (send . message)
(for-all (lambda (x) (display x irc-output-port)) message)
(display #\return irc-output-port)
(newline irc-output-port)
(flush-output-port irc-output-port))
(define (get-line-crlf port)
(define line (get-line port))
(if (eof-object? line) line
(let ([len (string-length line)])
(if (and (> len 0) (eqv? (string-ref line (- len 1)) #\return))
(substring line 0 (- len 1)) line))))
(define (get-config name)
(send "XB get_config :" name)
(car (message-params (parse-message (get-line-crlf irc-input-port)))))
(define (extract-nick prefix)
(do ([i 0 (+ i 1)] [len (string-length prefix)])
([or (= i len) (char=? #\! (string-ref prefix i))]
[substring prefix 0 i])))
(define (string-after s start)
(let ([s-len (string-length s)] [with-len (string-length start)])
(and (>= s-len with-len)
(string=? (substring s 0 with-len) start)
(substring s with-len s-len))))
; --- Calculator ---------------------------------------------------------------
; Evaluator derived from the example in The Scheme Programming Language.
;
; Even though EVAL with a carefully crafted environment would also do a good
; job at sandboxing, it would probably be impossible to limit execution time...
(define (env-new formals actuals env)
(cond [(null? formals) env]
[(symbol? formals) (cons (cons formals actuals) env)]
[else (cons (cons (car formals) (car actuals))
(env-new (cdr formals) (cdr actuals) env))]))
(define (env-lookup var env) (cdr (assq var env)))
(define (env-assign var val env) (set-cdr! (assq var env) val))
(define (check-reductions r)
(if (= (car r) 0)
(error 'check-reductions "reduction limit exceeded")
(set-car! r (- (car r) 1))))
; TODO - think about implementing more syntactical constructs,
; however there's not much point in having anything else in a calculator...
(define (exec expr r env)
(check-reductions r)
(cond [(symbol? expr) (env-lookup expr env)]
[(pair? expr)
(case (car expr)
[(quote) (cadr expr)]
[(lambda) (lambda vals
(let ([env (env-new (cadr expr) vals env)])
(let loop ([exprs (cddr expr)])
(if (null? (cdr exprs))
(exec (car exprs) r env)
(begin (exec (car exprs) r env)
(loop (cdr exprs)))))))]
[(if) (if (exec (cadr expr) r env)
(exec (caddr expr) r env)
(exec (cadddr expr) r env))]
[(set!) (env-assign (cadr expr) (exec (caddr expr) r env) env)]
[else (apply (exec (car expr) r env)
(map (lambda (x) (exec x r env)) (cdr expr)))])]
[else expr]))
(define-syntax forward
(syntax-rules ()
[(_) '()]
[(_ a b ...) (cons (cons (quote a) a) (forward b ...))]))
; ...which can't prevent me from simply importing most of the standard library
(define base-library
(forward
; Equivalence, procedure predicate, booleans
eqv? eq? equal? procedure? boolean? boolean=? not
; numbers, numerical input and output
number? complex? real? rational? integer? exact? inexact? exact inexact
real-valued? rational-valued? integer-valued? number->string string->number
; Arithmetic
= < > <= >= zero? positive? negative? odd? even? finite? infinite? nan?
min max + * - / abs div-and-mod div mod div0-and-mod0 div0 mod0
gcd lcm numerator denominator floor ceiling truncate round
rationalize exp log sin cos tan asin acos atan sqrt expt
make-rectangular make-polar real-part imag-part magnitude angle
; Pairs and lists
map for-each cons car cdr caar cadr cdar cddr
caaar caadr cadar caddr cdaar cdadr cddar cdddr
caaaar caaadr caadar caaddr cadaar cadadr caddar cadddr
cdaaar cdaadr cdadar cdaddr cddaar cddadr cdddar cddddr
pair? null? list? list length append reverse list-tail list-ref
; Symbols
symbol? symbol=? symbol->string string->symbol
; Characters
char? char=? char<? char>? char<=? char>=? char->integer integer->char
; Strings; XXX - omitted make-string - can cause OOM
string? string=? string<? string>? string<=? string>=?
string string-length string-ref substring
string-append string->list list->string string-for-each string-copy
; Vectors; XXX - omitted make-vector - can cause OOM
vector? vector vector-length vector-ref vector-set!
vector->list list->vector vector-fill! vector-map vector-for-each
; Control features
apply call/cc values call-with-values dynamic-wind))
(define extended-library
(forward
char-upcase char-downcase char-titlecase char-foldcase
char-ci=? char-ci<? char-ci>? char-ci<=? char-ci>=?
char-alphabetic? char-numeric? char-whitespace?
char-upper-case? char-lower-case? char-title-case?
string-upcase string-downcase string-titlecase string-foldcase
string-ci=? string-ci<? string-ci>? string-ci<=? string-ci>=?
find for-all exists filter partition fold-left fold-right
remp remove remv remq memp member memv memq assp assoc assv assq cons*
list-sort vector-sort vector-sort!
bitwise-not bitwise-and bitwise-ior bitwise-xor bitwise-if
bitwise-bit-count bitwise-length bitwise-first-bit-set bitwise-bit-set?
bitwise-copy-bit bitwise-bit-field bitwise-copy-bit-field
bitwise-arithmetic-shift bitwise-rotate-bit-field bitwise-reverse-bit-field
bitwise-arithmetic-shift-left bitwise-arithmetic-shift-right
set-car! set-cdr! string-set! string-fill!))
(define (interpret expr)
(exec expr '(2000) (append base-library extended-library)))
; We could show something a bit nicer but it would be quite Guile-specific
(define (error-string e)
(map (lambda (x) (string-append " " (symbol->string x)))
(filter (lambda (x) (not (member x '(&who &message &irritants &guile))))
(map (lambda (x) (record-type-name (record-rtd x)))
(simple-conditions e)))))
(define (calc input respond)
(define (stringify x)
(call-with-string-output-port (lambda (port) (write x port))))
(guard (e [else (display-exception e (current-error-port))
(apply respond "caught" (error-string e))])
(let* ([input (open-string-input-port input)]
[data (let loop ()
(define datum (get-datum input))
(if (eof-object? datum) '() (cons datum (loop))))])
(call-with-values
(lambda () (interpret (list (append '(lambda ()) data))))
(lambda message
(for-all (lambda (x) (respond (stringify x))) message))))))
; --- Main loop ----------------------------------------------------------------
(define prefix (get-config "prefix"))
(send "XB register")
(define (process msg)
(when (string-ci=? (message-command msg) "PRIVMSG")
(let* ([nick (extract-nick (message-prefix msg))]
[target (car (message-params msg))]
[response-begin
(apply string-append "PRIVMSG "
(if (memv (string-ref target 0) (string->list "#&!+"))
`(,target " :" ,nick ": ") `(,nick " :")))]
[respond (lambda args (apply send response-begin args))]
[text (cadr (message-params msg))]
[input (or (string-after text (string-append prefix "calc "))
(string-after text (string-append prefix "= ")))])
(when input (calc input respond)))))
(let main-loop ()
(define line (get-line-crlf irc-input-port))
(unless (eof-object? line)
(guard (e [else (display-exception e (current-error-port))])
(unless (string=? "" line)
(process (parse-message line))))
(main-loop)))

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env tclsh
#
# ZyklonB coin plugin, random number-based utilities
# xB coin plugin, random number-based utilities
#
# Copyright 2012, 2014 Přemysl Janouch
# 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,8 +1,8 @@
#!/usr/bin/awk -f
#
# ZyklonB eval plugin, LISP-like expression evaluator
# xB eval plugin, LISP-like expression evaluator
#
# Copyright 2013, 2014 Přemysl Janouch
# 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,8 +1,8 @@
#!/usr/bin/env perl
#
# ZyklonB factoids plugin
# xB factoids plugin
#
# Copyright 2016 Přemysl Janouch <p.janouch@gmail.com>
# 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,9 +1,9 @@
#!/usr/bin/env ruby
# coding: utf-8
#
# ZyklonB pomodoro plugin
# xB pomodoro plugin
#
# Copyright 2015 Přemysl Janouch
# 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,8 +1,8 @@
#!/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 Janouch
// Copyright 2014 Přemysl Eric Janouch
// See the file LICENSE for licensing information.
//
// Just compile this file as usual (sans #!) if you don't feel like using TCC.
@@ -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);

160
plugins/xB/seen Executable file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env lua
--
-- xB seen plugin
--
-- Copyright 2016 Přemysl Eric Janouch <p@janouch.name>
-- See the file LICENSE for licensing information.
--
function parse (line)
local msg = { params = {} }
line = line:match ("[^\r]*")
for start, word in line:gmatch ("()([^ ]+)") do
local colon = word:match ("^:(.*)")
if start == 1 and colon then
msg.prefix = colon
elseif not msg.command then
msg.command = word
elseif colon then
table.insert (msg.params, line:sub (start + 1))
break
elseif start ~= #line then
table.insert (msg.params, word)
end
end
return msg
end
function get_config (name)
io.write ("XB get_config :", name, "\r\n")
return parse (io.read ()).params[1]
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
io.output ():setvbuf ('line')
local prefix = get_config ('prefix')
io.write ("XB register\r\n")
local db = {}
local db_filename = "seen.db"
local db_garbage = 0
function remember (who, where, when, what)
if not db[who] then db[who] = {} end
if db[who][where] then db_garbage = db_garbage + 1 end
db[who][where] = { tonumber (when), what }
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
local db_file, e = io.open (db_filename, "a+")
if not db_file then error ("cannot open database: " .. e, 0) end
function db_store (who, where, when, what)
db_file:write (string.format
(":%s %s %s %s :%s\n", who, "PRIVMSG", where, when, what))
end
function db_compact ()
db_file:close ()
-- Unfortunately, default Lua doesn't have anything like mkstemp()
local db_tmpname = db_filename .. "." .. os.time ()
db_file, e = io.open (db_tmpname, "a+")
if not db_file then error ("cannot save database: " .. e, 0) end
for who, places in pairs (db) do
for where, data in pairs (places) do
db_store (who, where, data[1], data[2])
end
end
db_file:flush ()
local ok, e = os.rename (db_tmpname, db_filename)
if not ok then error ("cannot save database: " .. e, 0) end
db_garbage = 0
end
for line in db_file:lines () do
local msg = parse (line)
remember (msg.prefix, table.unpack (msg.params))
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function seen (who, where, args)
local respond = function (...)
local privmsg = function (target, ...)
io.write ("PRIVMSG ", target, " :", table.concat { ... }, "\r\n")
end
if where:match ("^[#&!+]") then
privmsg (where, who, ": ", ...)
else
privmsg (who, ...)
end
end
local whom, e, garbage = args:match ("^(%S+)()%s*(.*)")
if not whom or #garbage ~= 0 then
return respond ("usage: <name>")
elseif who:lower () == whom:lower () then
return respond ("I can see you right now.")
end
local top = {}
-- That is, * acts like a wildcard, otherwise everything is escaped
local pattern = "^" .. whom:gsub ("[%^%$%(%)%%%.%[%]%+%-%?]", "%%%0")
:gsub ("%*", ".*"):lower () .. "$"
for name, places in pairs (db) do
if places[where] and name:lower ():match (pattern) then
local when, what = table.unpack (places[where])
table.insert (top, { name = name, when = when, what = what })
end
end
if #top == 0 then
return respond ("I have not seen \x02" .. whom .. "\x02 here.")
end
-- Get all matching nicknames ordered from the most recently active
-- and make the list case insensitive (remove older duplicates)
table.sort (top, function (a, b) return a.when > b.when end)
for i = #top, 2, -1 do
if top[i - 1].name:lower () == top[i].name:lower () then
table.remove (top, i)
end
end
-- Hopefully the formatting mess will disrupt highlights in clients
for i = 1, math.min (#top, 3) do
local name = top[i].name:gsub ("^.", "%0\x02\x02")
respond (string.format ("\x02%s\x02 -> %s -> %s",
name, os.date ("%c", top[i].when), top[i].what))
end
end
function handle (msg)
local who = msg.prefix:match ("^[^!@]*")
local where, what = table.unpack (msg.params)
local when = os.time ()
local what_log = what:gsub ("^\x01ACTION", "*"):gsub ("\x01$", "")
remember (who, where, when, what_log)
db_store (who, where, when, what_log)
-- Comment out to reduce both disk load and reliability
db_file:flush ()
if db_garbage > 5000 then db_compact () end
if what:sub (1, #prefix) == prefix then
local command = what:sub (#prefix + 1)
local name, e = command:match ("^(%S+)%s*()")
if name == 'seen' then seen (who, where, command:sub (e)) end
end
end
for line in io.lines () do
local msg = parse (line)
if msg.command == "PRIVMSG" then handle (msg) end
end

39
plugins/xB/seen-import-xC.pl Executable file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env perl
# 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-xC.pl LOG-FILE... > seen.db
use strict;
use warnings;
use File::Basename;
use Time::Piece;
my $db = {};
for (@ARGV) {
my $where = (basename($_) =~ /\.(.*).log/)[0];
unless ($where) {
print STDERR "Invalid filename: $_\n";
next;
}
open my $fh, '<', $_ or die "Failed to open log file: $!";
while (<$fh>) {
my ($when, $who, $who_action, $what) =
/^(.{19}) (?:<[~&@%+]*(.*?)>| \* (\S+)) (.*)/;
next unless $when;
if ($who_action) {
$who = $who_action;
$what = "* $what";
}
$db->{$who}->{$where} =
[Time::Piece->strptime($when, "%Y-%m-%d %T")->epoch, $what];
}
}
while (my ($who, $places) = each %$db) {
while (my ($where, $data) = each %$places) {
my ($when, $what) = @$data;
print ":$who PRIVMSG $where $when :$what\n";
}
}

View File

@@ -1,8 +1,8 @@
#!/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 Janouch <p.janouch@gmail.com>
# 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

@@ -0,0 +1,48 @@
--
-- auto-rejoin.lua: join back automatically when someone kicks you
--
-- Copyright (c) 2016, 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.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
local timeout
xC.setup_config {
timeout = {
type = "integer",
comment = "auto rejoin timeout",
default = "0",
on_change = function (v)
timeout = v
end,
validate = function (v)
if v < 0 then error ("timeout must not be negative", 0) end
end,
},
}
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 ("^[^!]*")
local channel, whom = table.unpack (msg.params)
if who ~= whom and whom == server.user.nickname then
async.go (function ()
await (async.timer_ms (timeout * 1000))
server:send ("JOIN " .. channel)
end)
end
return line
end)

90
plugins/xC/censor.lua Normal file
View File

@@ -0,0 +1,90 @@
--
-- censor.lua: black out certain users' messages
--
-- 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.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
local to_pattern = function (mask)
if not mask:match ("!") then mask = mask .. "!*" end
if not mask:match ("@") then mask = mask .. "@*" end
-- That is, * acts like a wildcard, otherwise everything is escaped
return "^" .. mask:gsub ("[%^%$%(%)%%%.%[%]%+%-%?]", "%%%0")
:gsub ("%*", ".*") .. "$"
end
local patterns = {}
local read_masks = function (v)
patterns = {}
local add = function (who, where)
local channels = patterns[who] or {}
table.insert (channels, where)
patterns[who] = channels
end
for item in v:lower ():gmatch ("[^,]+") do
local who, where = item:match ("^([^/]+)/*(.*)")
if who then add (to_pattern (who), where == "" or where) end
end
end
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 .. quote .. decolor (rest) or quote .. decolor (text)
return start .. text
end
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 ()
for who, where in pairs (patterns) do
if msg.prefix:lower ():match (who) then
for _, x in pairs (where) do
if x == true or x == channel then
return censor (line)
end
end
end
end
return line
end)

113
plugins/xC/fancy-prompt.lua Normal file
View File

@@ -0,0 +1,113 @@
--
-- fancy-prompt.lua: the fancy multiline prompt you probably want
--
-- Copyright (c) 2016 - 2022, 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.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
-- Beware that it is a hack and only goes about 90% of the way, which is why
-- this functionality is only available as a plugin in the first place
-- (well, and also for customizability).
--
-- The biggest problem is that the way we work with Readline is incompatible
-- with multiline prompts, and normal newlines just don't work. This is being
-- circumvented by using an overflowing single-line prompt with a specially
-- crafted character in the rightmost column that prevents the bar's background
-- from spilling all over the last line.
--
-- There is also a problem with C-r search rendering not clearing out the
-- 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.
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 (xC.buffers) do
if buffer == current then
current_n = i
elseif buffer.new_messages_count ~= buffer.new_unimportant_count then
active = active .. ","
if buffer.highlighted then
active = active .. "!"
bg_color = "224"
end
active = active .. i
end
end
local x = current_n .. ":" .. current.name
if chan and chan.users_len ~= 0 then
local params = ""
for mode, param in pairs (chan.param_modes) do
params = params .. " +" .. mode .. " " .. param
end
local modes = chan.no_param_modes .. params:sub (3)
if modes ~= "" then
x = x .. "(+" .. modes .. ")"
end
x = x .. "{" .. chan.users_len .. "}"
end
if current.hide_unimportant then
x = x .. "<H>"
end
if active ~= "" then
x = x .. " (" .. active:sub (2) .. ")"
end
-- Readline 7.0.003 seems to be broken and completely corrupts the prompt.
-- However 8.0.004 seems to be fine with these, as is libedit 20191231-3.1.
--x = x:gsub("[\128-\255]", "?")
-- Align to the terminal's width and apply formatting, including the hack.
local lines, cols = xC.get_screen_size ()
local trailing, width = " ", xC.measure (x)
while cols > 0 and width >= cols do
x = x:sub (1, utf8.offset (x, -1) - 1)
trailing, width = ">", xC.measure (x)
end
x = "\x01\x1b[0;4;1;38;5;16m\x1b[48;5;" .. bg_color .. "m\x02" ..
x .. string.rep (" ", cols - width - 1) ..
"\x01\x1b[0;4;1;7;38;5;" .. bg_color .. "m\x02" ..
trailing .. "\x01\x1b[0;1m\x02"
local user_prefix = function (chan, user)
for i, chan_user in ipairs (chan.users) do
if chan_user.user == user then return chan_user.prefixes end
end
return ""
end
if s then
x = x .. "["
local state = s.state
if state == "disconnected" or state == "connecting" then
x = x .. "(" .. state .. ")"
elseif state ~= "registered" then
x = x .. "(unregistered)"
else
local user, modes = s.user, s.user_mode
if chan then x = x .. user_prefix (chan, user) end
x = x .. user.nickname
if modes ~= "" then x = x .. "(" .. modes .. ")" end
end
x = x .. "] "
else
-- There needs to be at least one character so that the cursor
-- doesn't get damaged by our hack in that last column
x = x .. "> "
end
return x
end)

View File

@@ -5,11 +5,10 @@
--
-- I call this style closure-oriented programming
--
-- Copyright (c) 2016, Přemysl Janouch <p.janouch@gmail.com>
-- Copyright (c) 2016, 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, provided that the above
-- copyright notice and this permission notice appear in all copies.
-- purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
@@ -24,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",
@@ -118,24 +117,23 @@ end
local running
-- Initiate a connection to last.fm servers
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")
return
end
if running then running.abort () end
running = degesch.connect ("ws.audioscrobbler.com", 80, {
on_success = function (c, host)
on_connected (buffer, c, host, action)
running = nil
end,
on_error = function (e)
if running then running:cancel () end
running = async.go (function ()
local c, host, e = await (async.dial ("ws.audioscrobbler.com", 80))
if e then
report_error (buffer, e)
running = nil
else
on_connected (buffer, c, host, action)
end
})
running = nil
end)
end
-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -161,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

@@ -1,11 +1,10 @@
--
-- ping-timeout.lua: ping timeout readability enhancement plugin
--
-- Copyright (c) 2015, Přemysl Janouch <p.janouch@gmail.com>
-- Copyright (c) 2015 - 2016, 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, provided that the above
-- copyright notice and this permission notice appear in all copies.
-- purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
@@ -16,10 +15,10 @@
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
degesch.hook_irc (function (hook, server, line)
local start, timeout =
line:match ("^(:[^ ]* QUIT :Ping timeout:) (%d+) seconds$")
if not start then
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
end

68
plugins/xC/prime.lua Normal file
View File

@@ -0,0 +1,68 @@
--
-- prime.lua: highlight prime numbers in messages
--
-- Copyright (c) 2020, 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.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
local smallest, highlight = 0, "\x1f"
xC.setup_config {
smallest = {
type = "integer",
default = "0",
comment = "smallest number to scan for primality",
on_change = function (v) smallest = math.max (v, 2) end
},
highlight = {
type = "string",
default = "\"\\x1f\"",
comment = "the attribute to use for highlights",
on_change = function (v) highlight = v end
},
}
-- The prime test is actually very fast, so there is no DoS concern
local do_intercolour = function (text)
return tostring (text:gsub ("%f[%w_]%d+", function (n)
if tonumber (n) < smallest then return nil end
for i = 2, n ^ (1 / 2) do if (n % i) == 0 then return nil end end
return highlight .. n .. highlight
end))
end
local do_interlink = function (text)
local rebuilt, last = {""}, 1
for start in text:gmatch ('()\x03') do
table.insert (rebuilt, do_intercolour (text:sub (last, start - 1)))
local sub = text:sub (start + 1)
last = start + (sub:match ('^%d%d?,%d%d?()') or sub:match ('^%d?%d?()'))
table.insert (rebuilt, text:sub (start, last - 1))
end
return table.concat (rebuilt) .. do_intercolour (text:sub (last))
end
local do_message = function (text)
local rebuilt, last = {""}, 1
for run, link, endpos in text:gmatch ('(.-)(%f[%g]https?://%g+)()') do
last = endpos
table.insert (rebuilt, do_interlink (run) .. link)
end
return table.concat (rebuilt) .. do_interlink (text:sub (last))
end
-- XXX: sadly it won't typically highlight primes in our own messages,
-- unless IRCv3 echo-message is on
xC.hook_irc (function (hook, server, line)
local start, message = line:match ("^(.- PRIVMSG .- :)(.*)$")
return message and start .. do_message (message) or line
end)

147
plugins/xC/slack.lua Normal file
View File

@@ -0,0 +1,147 @@
--
-- slack.lua: try to fix up UX when using the Slack IRC gateway
--
-- Copyright (c) 2017, 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.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
local servers = {}
local read_servers = function (v)
servers = {}
for name in v:lower ():gmatch "[^,]+" do
servers[name] = true
end
end
-- This is a reverse list of Slack's automatic emoji, noseless forms
local unemojify, emoji, emoji_default = false, {}, {
heart = "<3",
broken_heart = "</3",
sunglasses = "8)",
anguished = "D:",
cry = ":'(",
monkey_face = ":o)",
kiss = ":*",
smiley = "=)",
smile = ":D",
wink = ";)",
laughing = ":>",
neutral_face = ":|",
open_mouth = ":o",
angry = ">:(",
slightly_smiling_face = ":)",
disappointed = ":(",
confused = ":/",
stuck_out_tongue = ":p",
stuck_out_tongue_winking_eye = ";p",
}
local load_emoji = function (extra)
emoji = {}
for k, v in pairs (emoji_default) do emoji[k] = v end
for k, v in extra:gmatch "([^,]+) ([^,]+)" do emoji[k] = v end
end
xC.setup_config {
servers = {
type = "string_array",
default = "\"\"",
comment = "list of server names that are Slack IRC gateways",
on_change = read_servers
},
unemojify = {
type = "boolean",
default = "true",
comment = "convert emoji to normal ASCII emoticons",
on_change = function (v) unemojify = v end
},
extra_emoji = {
type = "string_array",
default = "\"grinning :)),joy :'),innocent o:),persevere >_<\"",
comment = "overrides or extra emoji for unemojify",
on_change = function (v) load_emoji (v) end
}
}
-- 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
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
-- Taking a shortcut to avoid lengthy message reassembly
local quoted_nick = us.nickname:gsub ("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0")
local text = line:match ("^.- PRIVMSG .- :%[" .. quoted_nick .. "%] (.*)$")
if not text then return line end
return ":" .. us.nickname .. "!" .. server.irc_user_host .. " PRIVMSG "
.. msg.prefix:match "^[^!@]*" .. " :" .. text
end)
-- Unfuck emoji and :nick!nick@irc.tinyspeck.com MODE #channel +v nick : active
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 .- :)(.*)$")
if start then return start .. text:gsub (":([a-z_]+):", function (name)
if emoji[name] then return emoji[name] end
return ":" .. name .. ":"
end) end
end
return line:gsub ("^(:%S+ MODE .+) : .*", "%1")
end)
-- The gateway simply ignores the NAMES command altogether
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
local users = buffer.channel.users
table.sort (users, function (a, b)
if a.prefixes > b.prefixes then return true end
if a.prefixes < b.prefixes then return false end
return a.user.nickname < b.user.nickname
end)
local names = "Users on " .. buffer.channel.name .. ":"
for i, chan_user in ipairs (users) do
names = names .. " " .. chan_user.prefixes .. chan_user.user.nickname
end
buffer:log (names)
end)
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
if data.location == 1 and data.words[1]:match "^/" then return end
-- Handle both when the at sign is already there and when it is not
local needle = word:gsub ("^@", ""):lower ()
local t = {}
local try = function (name)
if data.location == 0 then name = name .. ":" end
if name:sub (1, #needle):lower () == needle then
table.insert (t, "@" .. name)
end
end
for _, chan_user in ipairs (chan.users) do
try (chan_user.user.nickname)
end
for _, special in ipairs { "channel", "here" } do
try (special)
end
return t
end)

View File

@@ -0,0 +1,27 @@
--
-- thin-cursor.lua: set a thin cursor
--
-- Copyright (c) 2016, 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.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--
-- If tmux doesn't work, add the following to its configuration:
-- set -as terminal-overrides ',*:Ss=\E[%p1%d q:Se=\E[2 q'
-- Change the "2" as per http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
local out = io.output ()
out:write ("\x1b[6 q"):flush ()
-- By registering a global variable, we get notified about plugin unload
x = setmetatable ({}, { __gc = function ()
out:write ("\x1b[2 q"):flush ()
end })

View File

@@ -1,11 +1,10 @@
--
-- utm-filter.lua: filter out Google Analytics bullshit from URLs
-- utm-filter.lua: filter out Google Analytics bullshit etc. from URLs
--
-- Copyright (c) 2015, Přemysl Janouch <p.janouch@gmail.com>
-- 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, provided that the above
-- copyright notice and this permission notice appear in all copies.
-- purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
@@ -20,6 +19,10 @@
local banned = {
gclid = 1,
-- Alas, Facebook no longer uses this parameter, see:
-- https://news.ycombinator.com/item?id=32117489
fbclid = 1,
utm_source = 1,
utm_medium = 1,
utm_term = 1,
@@ -53,11 +56,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)

50
test
View File

@@ -1,50 +0,0 @@
#!/usr/bin/expect -f
# Very basic end-to-end testing for Travis CI
# Run the daemon to test against
system ./kike --write-default-cfg
spawn ./kike -d
# 10 seconds is a bit too much
set timeout 5
spawn ./degesch
# Fuck this Tcl shit, I want the exit code
expect_after {
eof {
puts ""
puts "Child exited prematurely"
exit 1
}
}
# Connect to the daemon
send "/server add localhost\n"
expect "]"
send "/set servers.localhost.addresses = \"localhost\"\n"
expect "Option changed"
send "/disconnect\n"
expect "]"
send "/connect\n"
expect "Connection established"
# Try some chatting
send "/join #test\n"
expect "has joined"
send "Hello\n"
expect "Hello"
# Attributes
send "\x1bmbBold text! \x1bmc0,5And colors.\n"
expect "]"
# Try basic commands
send "/set\n"
expect "]"
send "/help\n"
expect "]"
# Quit
send "/quit\n"
expect "Shutting down"

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' &

22
test-static Executable file
View File

@@ -0,0 +1,22 @@
#!/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.
#
# In clang-query terms, the string we're interested in can be found through:
# set traversal IgnoreUnlessSpelledInSource
# set output dump
# match callExpr(callee(functionDecl(
# hasName("log_full"))),
# hasArgument(5, stringLiteral().bind("format")))
# However, the tool is too restricted to be useful in a shell script.
perl -n0777 - "$(dirname "$0")"/xC.c <<-'END'
while (/\blog_[^ ]+\s*\([^"()]*"[^"]*%\w[^"]*"/gm) {
my ($p, $m) = ($`, $&);
printf "$ARGV:%d: suspicious log format string: %s...\n",
(1 + $p =~ tr/\n//), ($m =~ s/\s+/ /rg);
$status = 1;
}
END {
exit $status;
}
END

72
test.lua Normal file
View File

@@ -0,0 +1,72 @@
#!/usr/bin/env wdye
-- Very basic end-to-end testing for CI
function exec (...)
local p = wdye.spawn(...)
local out = wdye.expect(p:eof {function (p) return p[0] end})
if not out then
error "exec() timeout"
end
local status = p:wait()
if status ~= 0 then
io.write(out, "\n")
error("exit status " .. status)
end
return out:gsub("%s+$", "")
end
local temp = exec {"mktemp", "-d"}
local atexit = {}
setmetatable(atexit, {__gc = function () exec {"rm", "-rf", "--", temp} end})
local env = {XDG_CONFIG_HOME=temp, TERM="xterm"}
exec {"./xD", "--write-default-cfg", environ=env}
-- Run the daemon to test against (assuming the default port 6667)
local xD = wdye.spawn {"./xD", "-d", environ=env}
local xC = wdye.spawn {"./xC", environ=env}
function send (...) xC:send(...) end
function expect (string)
wdye.expect(xC:exact {string},
wdye.timeout {5, function (p) error "xC timeout" end},
xC:eof {function (p) error "xC exited prematurely" end})
end
-- Connect to the daemon
send "/server add localhost\n"
expect "]"
send "/set servers.localhost.addresses = \"localhost\"\n"
expect "Option changed"
send "/disconnect\n"
expect "]"
send "/connect\n"
expect "Welcome to"
-- Try some chatting
send "/join #test\n"
expect "has joined"
send "Hello\n"
expect "Hello"
-- Attributes
send "\x1bmbBold text! \x1bmc0,5And colors.\n"
expect "]"
-- Try basic commands
send "/set\n"
expect "]"
send "/help\n"
expect "]"
-- Quit
send "/quit\n"
expect "Shutting down"
local s1 = xC:wait()
assert(s1 == 0, "xC exited abnormally: " .. s1)
-- Send SIGINT (^C)
xD:send "\003"
local s2 = xD:wait()
assert(s2 == 0, "xD exited abnormally: " .. s2)

5
xA/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
/xA
/proto.go
/FyneApp.toml
/*.png
/beep.raw

36
xA/Makefile Normal file
View File

@@ -0,0 +1,36 @@
.POSIX:
.SUFFIXES:
.SUFFIXES: .png .svg
AWK = env LC_ALL=C awk
tools = ../liberty/tools
generated = FyneApp.toml xA.png xA-highlighted.png beep.raw proto.go
outputs = xA $(generated)
all: $(outputs)
generate: $(generated)
FyneApp.toml: ../xK-version
printf "\
[Details]\n\
Icon = 'xA.png'\n\
Name = 'xA'\n\
ID = 'name.janouch.xA'\n\
Version = '$$(cat ../xK-version)'\n\
Build = 1\n\
\n\
[LinuxAndBSD]\n\
GenericName = 'IRC Client'\n\
Categories = ['Network', 'Chat', 'IRCClient']\n" > $@
.svg.png:
rsvg-convert --output=$@ -- $<
beep.raw:
sox -Dr 44100 -c 1 -e signed-integer -b 16 -L -n $@ \
synth 0.1 0 25 triangle 800 vol 0.5 fade t 0 -0 0.005 pad 0 0.05
proto.go: $(tools)/lxdrgen.awk $(tools)/lxdrgen-go.awk ../xC.lxdr
$(AWK) -f $(tools)/lxdrgen.awk -f $(tools)/lxdrgen-go.awk \
-v PrefixCamel=Relay ../xC.lxdr > $@
xA: xA.go ../xK-version $(generated)
go build -ldflags "-X 'main.projectVersion=$$(cat ../xK-version)'" -o $@ \
-gcflags=all="-N -l"
clean:
rm -f $(outputs)

46
xA/go.mod Normal file
View File

@@ -0,0 +1,46 @@
module janouch.name/xK/xA
go 1.23.0
toolchain go1.24.0
require (
fyne.io/fyne/v2 v2.6.0
github.com/ebitengine/oto/v3 v3.3.3
)
require (
fyne.io/systray v1.11.0 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ebitengine/purego v0.8.2 // indirect
github.com/fredbi/uri v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fyne-io/gl-js v0.1.0 // indirect
github.com/fyne-io/glfw-js v0.2.0 // indirect
github.com/fyne-io/image v0.1.1 // indirect
github.com/fyne-io/oksvg v0.1.0 // indirect
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 // indirect
github.com/go-text/render v0.2.0 // indirect
github.com/go-text/typesetting v0.3.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
github.com/hack-pad/safejs v0.1.1 // indirect
github.com/jeandeaual/go-locale v0.0.0-20250421151639-a9d6ed1b3d45 // indirect
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/nicksnyder/go-i18n/v2 v2.6.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rymdport/portal v0.4.1 // indirect
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/yuin/goldmark v1.7.10 // indirect
golang.org/x/image v0.26.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

84
xA/go.sum Normal file
View File

@@ -0,0 +1,84 @@
fyne.io/fyne/v2 v2.6.0 h1:Rywo9yKYN4qvNuvkRuLF+zxhJYWbIFM+m4N4KV4p1pQ=
fyne.io/fyne/v2 v2.6.0/go.mod h1:YZt7SksjvrSNJCwbWFV32WON3mE1Sr7L41D29qMZ/lU=
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/oto/v3 v3.3.3 h1:m6RV69OqoXYSWCDsHXN9rc07aDuDstGHtait7HXSM7g=
github.com/ebitengine/oto/v3 v3.3.3/go.mod h1:MZeb/lwoC4DCOdiTIxYezrURTw7EvK/yF863+tmBI+U=
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8=
github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fyne-io/gl-js v0.1.0 h1:8luJzNs0ntEAJo+8x8kfUOXujUlP8gB3QMOxO2mUdpM=
github.com/fyne-io/gl-js v0.1.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI=
github.com/fyne-io/glfw-js v0.2.0 h1:8GUZtN2aCoTPNqgRDxK5+kn9OURINhBEBc7M4O1KrmM=
github.com/fyne-io/glfw-js v0.2.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk=
github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA=
github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM=
github.com/fyne-io/oksvg v0.1.0 h1:7EUKk3HV3Y2E+qypp3nWqMXD7mum0hCw2KEGhI1fnBw=
github.com/fyne-io/oksvg v0.1.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI=
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 h1:RkGhqHxEVAvPM0/R+8g7XRwQnHatO0KAuVcwHo8q9W8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728/go.mod h1:SyRD8YfuKk+ZXlDqYiqe1qMSqjNgtHzBTG810KUagMc=
github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc=
github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU=
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A=
github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0=
github.com/hack-pad/safejs v0.1.1 h1:d5qPO0iQ7h2oVtpzGnLExE+Wn9AtytxIfltcS2b9KD8=
github.com/hack-pad/safejs v0.1.1/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
github.com/jeandeaual/go-locale v0.0.0-20250421151639-a9d6ed1b3d45 h1:vFdvrlsVU+p/KFBWTq0lTG4fvWvG88sawGlCzM+RUEU=
github.com/jeandeaual/go-locale v0.0.0-20250421151639-a9d6ed1b3d45/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA=
github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.7.10 h1:S+LrtBjRmqMac2UdtB6yyCEJm+UILZ2fefI4p7o0QpI=
github.com/yuin/goldmark v1.7.10/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

23
xA/xA-highlighted.svg Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1.1" width="512" height="512" viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="background" x1="0" y1="0" x2="0" y2="1">
<stop stop-color="#ffaa00" offset="0" />
<stop stop-color="#ffffff" offset="1" />
</linearGradient>
<clipPath id="clip">
<rect x="0" y="0" width="1" height="1" />
</clipPath>
</defs>
<rect x="0" y="0" width="512" height="512" fill="url(#background)" />
<g transform="translate(64, 64) scale(384)" stroke-linecap="square">
<g clip-path="url(#clip)">
<path stroke="#ff0000" stroke-width="0.125"
d="M 0.25,1.1 0.65,-0.1 M 0.75,1.1 0.35,-0.1 M 0.225,0.75 0.775,0.75" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 781 B

1651
xA/xA.go Normal file

File diff suppressed because it is too large Load Diff

23
xA/xA.svg Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1.1" width="512" height="512" viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="background" x1="0" y1="0" x2="0" y2="1">
<stop stop-color="#ffaa00" offset="0" />
<stop stop-color="#ff0000" offset="1" />
</linearGradient>
<clipPath id="clip">
<rect x="0" y="0" width="1" height="1" />
</clipPath>
</defs>
<rect x="0" y="0" width="512" height="512" fill="url(#background)" />
<g transform="translate(64, 64) scale(384)" stroke-linecap="square">
<g clip-path="url(#clip)">
<path stroke="#ffffff" stroke-width="0.125"
d="M 0.25,1.1 0.65,-0.1 M 0.75,1.1 0.35,-0.1 M 0.225,0.75 0.775,0.75" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 781 B

104
xB.adoc Normal file
View File

@@ -0,0 +1,104 @@
xB(1)
=====
:doctype: manpage
:manmanual: xK Manual
:mansource: xK {release-version}
Name
----
xB - modular IRC bot
Synopsis
--------
*xB* [_OPTION_]...
Description
-----------
*xB* is a modular IRC bot with a programming language-agnostic plugin
architecture based on co-processes.
Options
-------
*-d*, *--debug*::
Print more information to help debug various issues.
*-h*, *--help*::
Display a help message and exit.
*-V*, *--version*::
Output version information and exit.
*--write-default-cfg*[**=**__PATH__]::
Write a configuration file with defaults, show its path and exit.
+
The file will be appropriately commented.
Commands
--------
The bot accepts the following commands when they either appear quoted by the
*prefix* string on a channel or unquoted as a private message sent directly
to the bot, on the condition that the sending user matches the *admin*
regular expression or that it is left unset:
*quote* [_message_]::
Forwards the message to the IRC server as-is.
*quit* [_reason_]::
Quits the IRC server, with an optional reason string.
*status*::
Sends back a report about its state and all loaded plugins.
*load* _plugin_[, _plugin_]...::
Tries to load the given plugins.
*unload* _plugin_[, _plugin_]...::
Tries to unload the given plugins.
*reload* _plugin_[, _plugin_]...::
The same as *unload* immediately followed by *load*.
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 *XB* command is introduced
for RPC, with the following subcommands:
*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:
+
....
XB :value
....
+
This is particularly useful for retrieving the *prefix* string.
*XB print* _message_::
Make the bot print the _message_ on its standard output.
*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.
All other commands will be forwarded directly to the IRC server.
Files
-----
*xB* follows the XDG Base Directory Specification.
_~/.config/xB/xB.conf_::
The bot's configuration file. Use the *--write-default-cfg* option
to create a new one for editing.
_~/.local/share/xB/_::
The initial working directory for plugins, in which they may create private
databases or other files as needed.
_~/.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.
Reporting bugs
--------------
Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests.

View File

@@ -1,11 +1,10 @@
/*
* zyklonb.c: the experimental IRC bot
* xB.c: a modular IRC bot
*
* Copyright (c) 2014 - 2016, Přemysl Janouch <p.janouch@gmail.com>
* Copyright (c) 2014 - 2020, 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, provided that the above
* copyright notice and this permission notice appear in all copies.
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
@@ -18,8 +17,7 @@
*/
#include "config.h"
#define PROGRAM_NAME "ZyklonB"
#define PLUGIN_DIR ZYKLONB_PLUGIN_DIR
#define PROGRAM_NAME "xB"
#include "common.c"
@@ -27,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" },
@@ -50,7 +48,7 @@ static struct simple_config_item g_config_table[] =
{ "prefix", ":", "The prefix for bot commands" },
{ "admin", NULL, "Host mask for administrators" },
{ "plugins", NULL, "The plugins to load on startup" },
{ "plugin_dir", PLUGIN_DIR, "Where to search for plugins" },
{ "plugin_dir", NULL, "Plugin search path override" },
{ "recover", "on", "Whether to re-launch on crash" },
{ NULL, NULL, NULL }
@@ -83,22 +81,22 @@ struct plugin
struct str write_buffer; ///< Output yet to be sent out
};
static void
plugin_init (struct plugin *self)
static struct plugin *
plugin_new (void)
{
memset (self, 0, sizeof *self);
struct plugin *self = xcalloc (1, sizeof *self);
self->pid = -1;
str_init (&self->queued_output);
self->queued_output = str_make ();
self->read_fd = -1;
str_init (&self->read_buffer);
self->read_buffer = str_make ();
self->write_fd = -1;
str_init (&self->write_buffer);
self->write_buffer = str_make ();
return self;
}
static void
plugin_free (struct plugin *self)
plugin_destroy (struct plugin *self)
{
soft_assert (self->pid == -1);
free (self->name);
@@ -113,6 +111,8 @@ plugin_free (struct plugin *self)
if (!self->initialized)
str_free (&self->queued_output);
free (self);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -152,34 +152,33 @@ static void on_irc_reconnect_timeout (void *user_data);
static void
bot_context_init (struct bot_context *self)
{
str_map_init (&self->config);
self->config.free = free;
self->config = str_map_make (free);
simple_config_load_defaults (&self->config, g_config_table);
self->admin_re = NULL;
self->irc_fd = -1;
str_init (&self->read_buffer);
self->read_buffer = str_make ();
self->irc_registered = false;
self->ssl = NULL;
self->ssl_ctx = NULL;
self->plugins = NULL;
str_map_init (&self->plugins_by_name);
self->plugins_by_name = str_map_make (NULL);
poller_init (&self->poller);
self->quitting = false;
self->polling = false;
poller_timer_init (&self->timeout_tmr, &self->poller);
self->timeout_tmr = poller_timer_make (&self->poller);
self->timeout_tmr.dispatcher = on_irc_timeout;
self->timeout_tmr.user_data = self;
poller_timer_init (&self->ping_tmr, &self->poller);
self->ping_tmr = poller_timer_make (&self->poller);
self->ping_tmr.dispatcher = on_irc_ping_timeout;
self->ping_tmr.user_data = self;
poller_timer_init (&self->reconnect_tmr, &self->poller);
self->reconnect_tmr = poller_timer_make (&self->poller);
self->reconnect_tmr.dispatcher = on_irc_reconnect_timeout;
self->reconnect_tmr.user_data = self;
}
@@ -193,18 +192,13 @@ bot_context_free (struct bot_context *self)
str_free (&self->read_buffer);
// TODO: terminate the plugins properly before this is called
struct plugin *link, *tmp;
for (link = self->plugins; link; link = tmp)
{
tmp = link->next;
plugin_free (link);
free (link);
}
LIST_FOR_EACH (struct plugin, link, self->plugins)
plugin_destroy (link);
if (self->irc_fd != -1)
{
xclose (self->irc_fd);
poller_fd_reset (&self->irc_event);
xclose (self->irc_fd);
}
if (self->ssl)
SSL_free (self->ssl);
@@ -272,8 +266,7 @@ irc_send (struct bot_context *ctx, const char *format, ...)
return false;
va_start (ap, format);
struct str str;
str_init (&str);
struct str str = str_make ();
str_append_vprintf (&str, format, ap);
str_append (&str, "\r\n");
va_end (ap);
@@ -286,7 +279,7 @@ irc_send (struct bot_context *ctx, const char *format, ...)
if (SSL_write (ctx->ssl, str.str, str.len) != (int) str.len)
{
print_debug ("%s: %s: %s", __func__, "SSL_write",
ERR_error_string (ERR_get_error (), NULL));
xerr_describe_error ());
result = false;
}
}
@@ -310,7 +303,7 @@ irc_get_boolean_from_config
if (set_boolean_if_valid (value, str))
return true;
FAIL ("invalid configuration value for `%s'", name);
return error_set (e, "invalid configuration value for `%s'", name);
}
static bool
@@ -324,13 +317,15 @@ irc_initialize_ca_set (SSL_CTX *ssl_ctx, const char *file, const char *path,
if (SSL_CTX_load_verify_locations (ssl_ctx, file, path))
return true;
FAIL ("%s: %s", "failed to set locations for the CA certificate bundle",
ERR_reason_error_string (ERR_get_error ()));
return error_set (e, "%s: %s",
"failed to set locations for the CA certificate bundle",
xerr_describe_error ());
}
if (!SSL_CTX_set_default_verify_paths (ssl_ctx))
FAIL ("%s: %s", "couldn't load the default CA certificate bundle",
ERR_reason_error_string (ERR_get_error ()));
return error_set (e, "%s: %s",
"couldn't load the default CA certificate bundle",
xerr_describe_error ());
return true;
}
@@ -411,7 +406,7 @@ irc_initialize_tls (struct bot_context *ctx, struct error **e)
else if (!SSL_use_certificate_file (ctx->ssl, path, SSL_FILETYPE_PEM)
|| !SSL_use_PrivateKey_file (ctx->ssl, path, SSL_FILETYPE_PEM))
print_error ("%s: %s", "setting the TLS client certificate failed",
ERR_error_string (ERR_get_error (), NULL));
xerr_describe_error ());
free (path);
}
@@ -438,11 +433,9 @@ error_ssl_2:
SSL_CTX_free (ctx->ssl_ctx);
ctx->ssl_ctx = NULL;
error_ssl_1:
// XXX: these error strings are really nasty; also there could be
// multiple errors on the OpenSSL stack.
if (!error_info)
error_info = ERR_error_string (ERR_get_error (), NULL);
FAIL ("%s: %s", "could not initialize TLS", error_info);
error_info = xerr_describe_error ();
return error_set (e, "%s: %s", "could not initialize TLS", error_info);
}
static bool
@@ -455,7 +448,7 @@ irc_establish_connection (struct bot_context *ctx,
int err = getaddrinfo (host, port, &gai_hints, &gai_result);
if (err)
FAIL ("%s: %s: %s", "connection failed",
return error_set (e, "%s: %s: %s", "connection failed",
"getaddrinfo", gai_strerror (err));
int sockfd;
@@ -497,7 +490,7 @@ irc_establish_connection (struct bot_context *ctx,
freeaddrinfo (gai_result);
if (!gai_iter)
FAIL ("connection failed");
return error_set (e, "connection failed");
ctx->irc_fd = sockfd;
return true;
@@ -507,7 +500,7 @@ irc_establish_connection (struct bot_context *ctx,
static int g_signal_pipe[2]; ///< A pipe used to signal... signals
static struct str_vector
static struct strv
g_original_argv, ///< Original program arguments
g_recovery_env; ///< Environment for re-exec recovery
@@ -682,8 +675,8 @@ recovery_handler (int signum, siginfo_t *info, void *context)
static void
prepare_recovery_environment (void)
{
str_vector_init (&g_recovery_env);
str_vector_add_vector (&g_recovery_env, environ);
g_recovery_env = strv_make ();
strv_append_vector (&g_recovery_env, environ);
// Prepare a location within the environment where we will put the startup
// (or maybe rather restart) reason in case of an irrecoverable error.
@@ -700,7 +693,7 @@ prepare_recovery_environment (void)
else
{
g_startup_reason_location = g_recovery_env.vector + g_recovery_env.len;
str_vector_add (&g_recovery_env, "");
strv_append (&g_recovery_env, "");
}
}
@@ -742,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)
@@ -962,7 +955,7 @@ on_plugin_readable (const struct pollfd *fd, struct plugin *plugin)
struct str *buf = &plugin->read_buffer;
while (true)
{
str_ensure_space (buf, 512 + 1);
str_reserve (buf, 512 + 1);
ssize_t n_read = read (fd->fd, buf->str + buf->len,
buf->alloc - buf->len - 1);
@@ -1016,21 +1009,47 @@ is_valid_plugin_name (const char *name)
if (!*name)
return false;
for (const char *p = name; *p; p++)
if (!isgraph (*p) || *p == '/')
if (!isgraph ((uint8_t) *p) || *p == '/')
return false;
return true;
}
static char *
plugin_resolve_relative_filename (const char *filename)
{
struct strv paths = strv_make ();
get_xdg_data_dirs (&paths);
strv_append (&paths, PROJECT_DATADIR);
char *result = resolve_relative_filename_generic
(&paths, PROGRAM_NAME "/plugins/", filename);
strv_free (&paths);
return result;
}
static struct plugin *
plugin_launch (struct bot_context *ctx, const char *name, struct error **e)
{
char *path = NULL;
const char *plugin_dir = str_map_find (&ctx->config, "plugin_dir");
if (!plugin_dir)
FAIL ("plugin directory not set");
if (plugin_dir)
{
// resolve_relative_filename_generic() won't accept relative paths,
// so just keep the old behaviour and expect the file to exist.
// We could use resolve_filename() on "plugin_dir" with paths=getcwd().
path = xstrdup_printf ("%s/%s", plugin_dir, name);
}
else if (!(path = plugin_resolve_relative_filename (name)))
{
error_set (e, "plugin not found");
goto fail_0;
}
int stdin_pipe[2];
if (pipe (stdin_pipe) == -1)
FAIL ("%s: %s", "pipe", strerror (errno));
{
error_set (e, "%s: %s", "pipe", strerror (errno));
goto fail_0;
}
int stdout_pipe[2];
if (pipe (stdout_pipe) == -1)
@@ -1039,8 +1058,7 @@ plugin_launch (struct bot_context *ctx, const char *name, struct error **e)
goto fail_1;
}
struct str work_dir;
str_init (&work_dir);
struct str work_dir = str_make ();
get_xdg_home_dir (&work_dir, "XDG_DATA_HOME", ".local/share");
str_append_printf (&work_dir, "/%s", PROGRAM_NAME);
@@ -1080,7 +1098,7 @@ plugin_launch (struct bot_context *ctx, const char *name, struct error **e)
// Restore some of the signal handling
signal (SIGPIPE, SIG_DFL);
char *argv[] = { xstrdup_printf ("%s/%s", plugin_dir, name), NULL };
char *argv[] = { path, NULL };
execve (argv[0], argv, environ);
// We will collect the failure later via SIGCHLD
@@ -1090,12 +1108,12 @@ plugin_launch (struct bot_context *ctx, const char *name, struct error **e)
}
str_free (&work_dir);
free (path);
xclose (stdin_pipe[0]);
xclose (stdout_pipe[1]);
struct plugin *plugin = xmalloc (sizeof *plugin);
plugin_init (plugin);
struct plugin *plugin = plugin_new ();
plugin->ctx = ctx;
plugin->pid = pid;
plugin->name = xstrdup (name);
@@ -1110,6 +1128,8 @@ fail_2:
fail_1:
xclose (stdin_pipe[0]);
xclose (stdin_pipe[1]);
fail_0:
free (path);
return NULL;
}
@@ -1117,9 +1137,9 @@ static bool
plugin_load (struct bot_context *ctx, const char *name, struct error **e)
{
if (!is_valid_plugin_name (name))
FAIL ("invalid plugin name");
return error_set (e, "invalid plugin name");
if (str_map_find (&ctx->plugins_by_name, name))
FAIL ("the plugin has already been loaded");
return error_set (e, "the plugin has already been loaded");
struct plugin *plugin;
if (!(plugin = plugin_launch (ctx, name, e)))
@@ -1128,11 +1148,11 @@ plugin_load (struct bot_context *ctx, const char *name, struct error **e)
set_blocking (plugin->read_fd, false);
set_blocking (plugin->write_fd, false);
poller_fd_init (&plugin->read_event, &ctx->poller, plugin->read_fd);
plugin->read_event = poller_fd_make (&ctx->poller, plugin->read_fd);
plugin->read_event.dispatcher = (poller_fd_fn) on_plugin_readable;
plugin->read_event.user_data = plugin;
poller_fd_init (&plugin->write_event, &ctx->poller, plugin->write_fd);
plugin->write_event = poller_fd_make (&ctx->poller, plugin->write_fd);
plugin->write_event.dispatcher = (poller_fd_fn) on_plugin_writable;
plugin->write_event.user_data = plugin;
@@ -1149,7 +1169,7 @@ plugin_unload (struct bot_context *ctx, const char *name, struct error **e)
struct plugin *plugin = str_map_find (&ctx->plugins_by_name, name);
if (!plugin)
FAIL ("no such plugin is loaded");
return error_set (e, "no such plugin is loaded");
plugin_zombify (plugin);
@@ -1165,10 +1185,8 @@ plugin_load_all_from_config (struct bot_context *ctx)
if (!plugin_list)
return;
struct str_vector plugins;
str_vector_init (&plugins);
cstr_split_ignore_empty (plugin_list, ',', &plugins);
struct strv plugins = strv_make ();
cstr_split (plugin_list, ",", true, &plugins);
for (size_t i = 0; i < plugins.len; i++)
{
char *name = cstr_strip_in_place (plugins.vector[i], " ");
@@ -1181,7 +1199,7 @@ plugin_load_all_from_config (struct bot_context *ctx)
}
}
str_vector_free (&plugins);
strv_free (&plugins);
}
// --- Main program ------------------------------------------------------------
@@ -1195,7 +1213,7 @@ parse_bot_command (const char *s, const char *command, const char **following)
s += command_len;
// Expect a word boundary, so that we don't respond to invalid things
if (isalnum (*s))
if (isalnum ((uint8_t) *s))
return false;
// Ignore any initial spaces; the rest is the command's argument
@@ -1206,13 +1224,13 @@ parse_bot_command (const char *s, const char *command, const char **following)
}
static void
split_bot_command_argument_list (const char *arguments, struct str_vector *out)
split_bot_command_argument_list (const char *arguments, struct strv *out)
{
cstr_split_ignore_empty (arguments, ',', out);
cstr_split (arguments, ",", true, out);
for (size_t i = 0; i < out->len; )
{
if (!*cstr_strip_in_place (out->vector[i], " \t"))
str_vector_remove (out, i);
strv_remove (out, i);
else
i++;
}
@@ -1248,10 +1266,8 @@ respond_to_user (struct bot_context *ctx, const struct irc_message *msg,
strncpy (nick, msg->prefix, sizeof nick - 1);
nick[sizeof nick - 1] = '\0';
struct str text;
va_list ap;
str_init (&text);
struct str text = str_make ();
va_start (ap, format);
str_append_vprintf (&text, format, ap);
va_end (ap);
@@ -1312,9 +1328,7 @@ process_plugin_reload (struct bot_context *ctx,
static char *
make_status_report (struct bot_context *ctx)
{
struct str report;
str_init (&report);
struct str report = str_make ();
const char *reason = getenv (g_startup_reason_str);
if (!reason)
reason = "launched normally";
@@ -1359,8 +1373,7 @@ process_privmsg (struct bot_context *ctx, const struct irc_message *msg)
return;
const char *following;
struct str_vector list;
str_vector_init (&list);
struct strv list = strv_make ();
if (parse_bot_command (text, "quote", &following))
// This seems to replace tons of random stupid commands
@@ -1400,7 +1413,7 @@ process_privmsg (struct bot_context *ctx, const struct irc_message *msg)
process_plugin_unload (ctx, msg, list.vector[i]);
}
str_vector_free (&list);
strv_free (&list);
}
static void
@@ -1577,13 +1590,11 @@ on_irc_disconnected (struct bot_context *ctx)
ctx->ssl_ctx = NULL;
}
poller_fd_reset (&ctx->irc_event);
xclose (ctx->irc_fd);
ctx->irc_fd = -1;
ctx->irc_registered = false;
ctx->irc_event.closed = true;
poller_fd_reset (&ctx->irc_event);
// TODO: inform plugins about the disconnect event
// All of our timers have lost their meaning now
@@ -1638,7 +1649,7 @@ on_irc_readable (const struct pollfd *fd, struct bot_context *ctx)
bool disconnected = false;
while (true)
{
str_ensure_space (buf, 512);
str_reserve (buf, 512);
switch (fill_buffer (ctx, buf))
{
case IRC_READ_AGAIN:
@@ -1778,7 +1789,7 @@ irc_connect (struct bot_context *ctx, struct error **e)
// TODO: again, get rid of `struct error' in here. The question is: how
// do we tell our caller that he should not try to reconnect?
if (!irc_host)
FAIL ("no hostname specified in configuration");
return error_set (e, "no hostname specified in configuration");
bool use_tls;
if (!irc_get_boolean_from_config (ctx, "tls", &use_tls, e))
@@ -1799,7 +1810,7 @@ irc_connect (struct bot_context *ctx, struct error **e)
}
print_status ("connection established");
poller_fd_init (&ctx->irc_event, &ctx->poller, ctx->irc_fd);
ctx->irc_event = poller_fd_make (&ctx->poller, ctx->irc_fd);
ctx->irc_event.dispatcher = (poller_fd_fn) on_irc_readable;
ctx->irc_event.user_data = ctx;
@@ -1823,7 +1834,10 @@ parse_config (struct bot_context *ctx, struct error **e)
const char *delay_str = str_map_find (&ctx->config, "reconnect_delay");
hard_assert (delay_str != NULL); // We have a default value for this
if (!xstrtoul (&ctx->reconnect_delay, delay_str, 10))
FAIL ("invalid configuration value for `%s'", "reconnect_delay");
{
return error_set (e,
"invalid configuration value for `%s'", "reconnect_delay");
}
hard_assert (!ctx->admin_re);
const char *admin = str_map_find (&ctx->config, "admin");
@@ -1847,7 +1861,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 = "";
@@ -1877,8 +1891,7 @@ on_plugin_death (struct plugin *plugin, int status)
plugin->read_fd = -1;
LIST_UNLINK (ctx->plugins, plugin);
plugin_free (plugin);
free (plugin);
plugin_destroy (plugin);
// Living child processes block us from quitting
try_finish_quit (ctx);
@@ -1956,8 +1969,8 @@ on_signal_pipe_readable (const struct pollfd *fd, struct bot_context *ctx)
int
main (int argc, char *argv[])
{
str_vector_init (&g_original_argv);
str_vector_add_vector (&g_original_argv, argv);
g_original_argv = strv_make ();
strv_append_vector (&g_original_argv, argv);
static const struct opt opts[] =
{
@@ -1970,8 +1983,8 @@ main (int argc, char *argv[])
{ 0, NULL, NULL, 0, NULL }
};
struct opt_handler oh;
opt_handler_init (&oh, argc, argv, opts, NULL, "Experimental IRC bot.");
struct opt_handler oh =
opt_handler_make (argc, argv, opts, NULL, "Modular IRC bot.");
int c;
while ((c = opt_handler_get (&oh)) != -1)
@@ -1999,12 +2012,7 @@ main (int argc, char *argv[])
print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting");
setup_signal_handlers ();
SSL_library_init ();
atexit (EVP_cleanup);
SSL_load_error_strings ();
// XXX: ERR_load_BIO_strings()? Anything else?
atexit (ERR_free_strings);
init_openssl ();
struct bot_context ctx;
bot_context_init (&ctx);
@@ -2018,11 +2026,17 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE);
}
poller_fd_init (&ctx.signal_event, &ctx.poller, g_signal_pipe[0]);
ctx.signal_event = poller_fd_make (&ctx.poller, g_signal_pipe[0]);
ctx.signal_event.dispatcher = (poller_fd_fn) on_signal_pipe_readable;
ctx.signal_event.user_data = &ctx;
poller_fd_set (&ctx.signal_event, POLLIN);
#if OpenBSD >= 201605
// cpath is for creating the plugin home directory
if (pledge ("stdio rpath cpath inet proc exec", NULL))
exit_fatal ("%s: %s", "pledge", strerror (errno));
#endif
plugin_load_all_from_config (&ctx);
if (!parse_config (&ctx, &e)
|| !irc_connect (&ctx, &e))
@@ -2044,7 +2058,7 @@ main (int argc, char *argv[])
poller_run (&ctx.poller);
bot_context_free (&ctx);
str_vector_free (&g_original_argv);
strv_free (&g_original_argv);
return EXIT_SUCCESS;
}

127
xC.adoc Normal file
View File

@@ -0,0 +1,127 @@
xC(1)
=====
:doctype: manpage
:manmanual: xK Manual
:mansource: xK {release-version}
Name
----
xC - terminal-based IRC client
Synopsis
--------
*xC* [_OPTION_]...
Description
-----------
*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
-------
*-f*, *--format*::
Format IRC text from the standard input, converting colour sequences and
other formatting marks to ANSI codes retrieved from the *terminfo*(5)
database:
+
....
printf '\x02bold\x02\n' | xC -f
....
+
This feature may be used to preview server MOTD files.
*-h*, *--help*::
Display a help message and exit.
*-V*, *--version*::
Output version information and exit.
Key bindings
------------
Most key bindings are inherited from the frontend in use, which is either GNU
Readline or BSD editline. A few of them, however, are special to the IRC client
or assume a different function. This is a list of all local overrides and
their respective function names:
*M-p*::
Go up in history for this buffer (normally mapped to *C-p*).
*M-n*::
Go down in history for this buffer (normally mapped to *C-n*).
*C-p*, *F5*: *previous-buffer*::
Switch to the previous buffer in order.
*C-n*, *F6*: *next-buffer*::
Switch to the next buffer in order.
*M-TAB*: *switch-buffer*::
Switch to the last buffer, i.e., the one you were in before.
*M-0*, *M-1*, ..., *M-9*: *goto-buffer*::
Go to the N-th buffer (normally sets a repeat counter).
Since there is no buffer number zero, *M-0* goes to the tenth one.
*M-!*: *goto-highlight*::
Go to the first following buffer with an unseen highlight.
*M-a*: *goto-activity*::
Go to the first following buffer with unseen activity.
*PageUp*: *display-backlog*::
Show the in-memory backlog for this buffer using *general.pager*,
which is almost certainly the *less*(1) program.
*M-h*: *display-full-log*::
Show the log file for this buffer using *general.pager*.
*M-H*: *toggle-unimportant*::
Hide all join, part and quit messages, as well as all channel mode changes
that only relate to user channel modes. Intended to reduce noise in
channels with lots of people.
*M-e*: *edit-input*::
Run an editor on the command line, making it easy to edit multiline
messages. Remember to save the file before exit.
*M-m*: *insert-attribute*::
The next key will be interpreted as a formatting mark to insert:
*c* for colours (optionally followed by numbers for the foreground
and background), *i* for italics, *b* for bold text, *u* for underlined,
*s* for struck-through, *m* for monospace, *v* for inverse text,
and *o* resets all formatting.
*C-l*: *redraw-screen*::
Should there be any issues with the display, this will clear the terminal
screen and redraw all information.
Additionally, *C-w* and *C-u* in editline behave the same as they would in
Readline or the "vi" command mode, even though the "emacs" mode is enabled
by default.
Bindings can be customized in your _.inputrc_ or _.editrc_ file. Both libraries
support conditional execution based on the program name. Beware that it is easy
to make breaking changes.
Environment
-----------
*VISUAL*, *EDITOR*::
The editor program to be launched by the *edit-input* function.
If neither variable is set, it defaults to *vi*(1).
Files
-----
*xC* follows the XDG Base Directory Specification.
_~/.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/xC/logs/_::
When enabled by *general.logging*, log files are stored here.
_~/.local/share/xC/plugins/_::
_/usr/local/share/xC/plugins/_::
_/usr/share/xC/plugins/_::
Plugins are searched for in these directories, in order.
Bugs
----
The editline (libedit) frontend may exhibit some unexpected behaviour.
Reporting bugs
--------------
Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests.
See also
--------
*less*(1), *readline*(3) or *editline*(7)

File diff suppressed because it is too large Load Diff

209
xC.lxdr Normal file
View File

@@ -0,0 +1,209 @@
// Backwards-compatible protocol version.
const VERSION = 2;
// From the frontend to the relay.
// All commands receive either an Event.RESPONSE, or an Event.ERROR.
struct CommandMessage {
// The command sequence number will be repeated in responses
// in the respective fields.
u32 command_seq;
union CommandData switch (enum Command {
HELLO,
ACTIVE,
BUFFER_ACTIVATE,
BUFFER_INPUT,
BUFFER_TOGGLE_UNIMPORTANT,
PING_RESPONSE,
PING,
BUFFER_COMPLETE,
BUFFER_LOG,
} command) {
// If the version check succeeds, the client will receive
// an initial stream of SERVER_UPDATE, BUFFER_UPDATE, BUFFER_STATS,
// BUFFER_LINE, and finally a BUFFER_ACTIVATE message.
case HELLO:
u32 version;
case ACTIVE:
void;
case BUFFER_ACTIVATE:
string buffer_name;
case BUFFER_INPUT:
string buffer_name;
string text;
// XXX: Perhaps this should rather be handled through a /buffer command.
case BUFFER_TOGGLE_UNIMPORTANT:
string buffer_name;
case PING:
void;
case PING_RESPONSE:
u32 event_seq;
case BUFFER_COMPLETE:
string buffer_name;
string text;
u32 position;
case BUFFER_LOG:
string buffer_name;
} data;
};
// From the relay to the frontend.
struct EventMessage {
u32 event_seq;
union EventData switch (enum Event {
ERROR,
RESPONSE,
PING,
BUFFER_LINE,
BUFFER_UPDATE,
BUFFER_STATS,
BUFFER_RENAME,
BUFFER_REMOVE,
BUFFER_ACTIVATE,
BUFFER_INPUT,
BUFFER_CLEAR,
SERVER_UPDATE,
SERVER_RENAME,
SERVER_REMOVE,
} event) {
// Restriction: command_seq strictly follows the sequence received
// by the relay, across both of these replies.
case ERROR:
u32 command_seq;
string error;
case RESPONSE:
u32 command_seq;
union ResponseData switch (Command command) {
case BUFFER_COMPLETE:
u32 start;
string completions<>;
case BUFFER_LOG:
// UTF-8, but not guaranteed.
u8 log<>;
default:
// Reception acknowledged.
void;
} data;
case PING:
void;
case BUFFER_LINE:
string buffer_name;
// Whether the line should also be displayed in the active buffer.
bool leak_to_active;
bool is_unimportant;
bool is_highlight;
enum Rendition {
BARE,
INDENT,
STATUS,
ERROR,
JOIN,
PART,
ACTION,
} rendition;
// Unix timestamp in milliseconds.
u64 when;
// Broken-up text, with in-band formatting.
union ItemData switch (enum Item {
TEXT,
RESET,
FG_COLOR,
BG_COLOR,
FLIP_BOLD,
FLIP_ITALIC,
FLIP_UNDERLINE,
FLIP_INVERSE,
FLIP_CROSSED_OUT,
FLIP_MONOSPACE,
} kind) {
case TEXT:
string text;
case RESET:
void;
case FG_COLOR:
i16 color;
case BG_COLOR:
i16 color;
case FLIP_BOLD:
case FLIP_ITALIC:
case FLIP_UNDERLINE:
case FLIP_INVERSE:
case FLIP_CROSSED_OUT:
case FLIP_MONOSPACE:
void;
} items<>;
case BUFFER_UPDATE:
string buffer_name;
bool hide_unimportant;
union BufferContext switch (enum BufferKind {
GLOBAL,
SERVER,
CHANNEL,
PRIVATE_MESSAGE,
} kind) {
case GLOBAL:
void;
case SERVER:
string server_name;
case CHANNEL:
string server_name;
ItemData topic<>;
// This includes parameters, separated by spaces.
string modes;
case PRIVATE_MESSAGE:
string server_name;
} context;
case BUFFER_STATS:
string buffer_name;
// These are cumulative, even for lines flushed out from buffers.
// Updates to these values aren't broadcasted, thus handle:
// - BUFFER_LINE by bumping/setting them as appropriate,
// - BUFFER_ACTIVATE by clearing them for the previous buffer
// (this way, they can be used to mark unread messages).
u32 new_messages;
u32 new_unimportant_messages;
bool highlighted;
case BUFFER_RENAME:
string buffer_name;
string new;
case BUFFER_REMOVE:
string buffer_name;
case BUFFER_ACTIVATE:
string buffer_name;
case BUFFER_INPUT:
string buffer_name;
string text;
case BUFFER_CLEAR:
string buffer_name;
case SERVER_UPDATE:
string server_name;
union ServerData switch (enum ServerState {
DISCONNECTED,
CONNECTING,
CONNECTED,
REGISTERED,
DISCONNECTING,
} state) {
case DISCONNECTED:
case CONNECTING:
case CONNECTED:
void;
case REGISTERED:
string user;
string user_modes;
// Theoretically, we could also send user information in this state,
// but we'd have to duplicate both fields.
case DISCONNECTING:
void;
} data;
case SERVER_RENAME:
// Buffers aren't sent updates for in this circumstance,
// as that wouldn't be sufficiently atomic anyway.
string server_name;
string new;
case SERVER_REMOVE:
string server_name;
} data;
};

BIN
xC.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

29
xD-gen-replies.awk Executable file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/awk -f
BEGIN {
# The message catalog is a by-product
msg = "xD.msg"
print "$quote \"" > msg
print "$set 1" > msg
}
/^[0-9]+ *IRC_(ERR|RPL)_[A-Z]+ *".*"$/ {
match($0, /".*"/)
ids[$1] = $2
texts[$2] = substr($0, RSTART, RLENGTH)
print $1 " " texts[$2] > msg
}
END {
printf("enum\n{")
for (i in ids) {
if (seen_first)
printf(",")
seen_first = 1
printf("\n\t%s = %s", ids[i], i)
}
print "\n};\n"
print "static const char *g_default_replies[] =\n{"
for (i in ids)
print "\t[" ids[i] "] = " texts[ids[i]] ","
print "};"
}

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"

53
xD.adoc Normal file
View File

@@ -0,0 +1,53 @@
xD(1)
=====
:doctype: manpage
:manmanual: xK Manual
:mansource: xK {release-version}
Name
----
xD - IRC daemon
Synopsis
--------
*xD* [_OPTION_]...
Description
-----------
*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.
Options
-------
*-d*, *--debug*::
Do not daemonize, print more information on the standard error stream
to help debug various issues.
*-h*, *--help*::
Display a help message and exit.
*-V*, *--version*::
Output version information and exit.
*--write-default-cfg*[**=**__PATH__]::
Write a configuration file with defaults, show its path and exit.
+
The file will be appropriately commented.
+
When no _PATH_ is specified, it will be created in the user's home directory,
contrary to what you might expect from a server.
Files
-----
*xD* follows the XDG Base Directory Specification.
_~/.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.
Reporting bugs
--------------
Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests.

File diff suppressed because it is too large Load Diff

172
xF.c Normal file
View File

@@ -0,0 +1,172 @@
/*
* xF.c: a toothless IRC client frontend
*
* Copyright (c) 2022, 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.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "config.h"
#define PROGRAM_NAME "xF"
#include "common.c"
#include "xC-proto.c"
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/XKBlib.h>
#include <X11/Xft/Xft.h>
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static struct
{
bool polling;
struct connector connector;
int socket;
}
g;
static void
on_connector_connecting (void *user_data, const char *address)
{
(void) user_data;
print_status ("connecting to %s...", address);
}
static void
on_connector_error (void *user_data, const char *error)
{
(void) user_data;
print_status ("connection failed: %s", error);
}
static void
on_connector_failure (void *user_data)
{
(void) user_data;
exit_fatal ("giving up");
}
static void
on_connector_connected (void *user_data, int socket, const char *hostname)
{
(void) user_data;
(void) hostname;
g.polling = false;
g.socket = socket;
}
static void
protocol_test (const char *host, const char *port)
{
struct poller poller = {};
poller_init (&poller);
connector_init (&g.connector, &poller);
g.connector.on_connecting = on_connector_connecting;
g.connector.on_error = on_connector_error;
g.connector.on_connected = on_connector_connected;
g.connector.on_failure = on_connector_failure;
connector_add_target (&g.connector, host, port);
g.polling = true;
while (g.polling)
poller_run (&poller);
connector_free (&g.connector);
struct str s = str_make ();
str_pack_u32 (&s, 0);
struct relay_command_message m = {};
m.data.hello.command = RELAY_COMMAND_HELLO;
m.data.hello.version = RELAY_VERSION;
if (!relay_command_message_serialize (&m, &s))
exit_fatal ("serialization failed");
uint32_t len = htonl (s.len - sizeof len);
memcpy (s.str, &len, sizeof len);
if (errno = 0, write (g.socket, s.str, s.len) != (ssize_t) s.len)
exit_fatal ("short send or error: %s", strerror (errno));
char buf[1 << 20] = "";
while (errno = 0, read (g.socket, &len, sizeof len) == sizeof len)
{
len = ntohl (len);
if (errno = 0, read (g.socket, buf, MIN (len, sizeof buf)) != len)
exit_fatal ("short read or error: %s", strerror (errno));
struct msg_unpacker r = msg_unpacker_make (buf, len);
struct relay_event_message m = {};
if (!relay_event_message_deserialize (&m, &r))
exit_fatal ("deserialization failed");
if (msg_unpacker_get_available (&r))
exit_fatal ("trailing data");
printf ("event: %d\n", m.data.event);
relay_event_message_free (&m);
}
exit_fatal ("short read or error: %s", strerror (errno));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int
main (int argc, char *argv[])
{
static const struct opt opts[] =
{
{ 'h', "help", NULL, 0, "display this help and exit" },
{ 'V', "version", NULL, 0, "output version information and exit" },
{ 0, NULL, NULL, 0, NULL }
};
struct opt_handler oh = opt_handler_make (argc, argv, opts,
"HOST:PORT", "X11 frontend for xC.");
int c;
while ((c = opt_handler_get (&oh)) != -1)
switch (c)
{
case 'h':
opt_handler_usage (&oh, stdout);
exit (EXIT_SUCCESS);
case 'V':
printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
exit (EXIT_SUCCESS);
default:
print_error ("wrong options");
opt_handler_usage (&oh, stderr);
exit (EXIT_FAILURE);
}
argc -= optind;
argv += optind;
if (argc != 1)
{
opt_handler_usage (&oh, stderr);
exit (EXIT_FAILURE);
}
opt_handler_free (&oh);
char *address = xstrdup (argv[0]);
const char *port = NULL, *host = tokenize_host_port (address, &port);
if (!port)
exit_fatal ("missing port number/service name");
// TODO: Actually implement an X11-based user interface.
protocol_test (host, port);
return 0;
}

34
xF.svg Normal file
View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1.1" width="48" height="48" viewBox="0 0 48 48"
xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="background" x1="0" y1="0" x2="1" y2="1">
<stop stop-color="#808080" offset="0" />
<stop stop-color="#000000" offset="1" />
</linearGradient>
<!-- librsvg screws up the filter's orientation in a weird way
otherwise a larger blur value would look better -->
<filter id="shadow" color-interpolation-filters="sRGB">
<feOffset dy="0.5" />
<feGaussianBlur stdDeviation="0.5" />
<feComposite in2="SourceGraphic" operator="in" />
</filter>
<clipPath id="clip">
<rect x="-7" y="-10" width="14" height="20" />
</clipPath>
</defs>
<circle cx="24" cy="24" r="20"
fill="url(#background)" stroke="#404040" stroke-width="2" />
<g transform="rotate(-45 24 24)" filter="url(#shadow)">
<path d="m 12,25 h 24 v 11 h -5 v -8 h -4.5 v 6 h -5 v -6 h -9.5 z"
fill="#ffffff" />
<g stroke-width="4" transform="translate(24, 16)" clip-path="url(#clip)"
stroke="#ffffff">
<line x1="-8" x2="8" y1="-5" y2="5" />
<line x1="-8" x2="8" y1="5" y2="-5" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1
xK-version Normal file
View File

@@ -0,0 +1 @@
2.1.0

45
xM/CMakeLists.txt Normal file
View File

@@ -0,0 +1,45 @@
# Swift language support
cmake_minimum_required (VERSION 3.15)
file (READ ../xK-version project_version)
configure_file (../xK-version xK-version.tag COPYONLY)
string (STRIP "${project_version}" project_version)
# There were two issues when building this from the main CMakeLists.txt:
# a) renaming main.swift to xM.swift requires removing top-level statements,
# b) there is a "redefinition of module 'FFI'" error.
project (xM VERSION "${project_version}"
DESCRIPTION "Cocoa frontend for xC" LANGUAGES Swift)
set (root "${PROJECT_SOURCE_DIR}/..")
add_custom_command (OUTPUT xC-proto.swift
COMMAND env LC_ALL=C awk
-f ${root}/liberty/tools/lxdrgen.awk
-f ${root}/liberty/tools/lxdrgen-swift.awk
-v PrefixCamel=Relay
${root}/xC.lxdr > xC-proto.swift
DEPENDS
${root}/liberty/tools/lxdrgen.awk
${root}/liberty/tools/lxdrgen-swift.awk
${root}/xC.lxdr
COMMENT "Generating xC relay protocol code" VERBATIM)
set (MACOSX_BUNDLE_GUI_IDENTIFIER name.janouch.${PROJECT_NAME})
set (MACOSX_BUNDLE_ICON_FILE xM.icns)
# Avoid including binary files in the repository by generating icons in code.
# sips(1) + Javascript + iconutil(1) could probably also be used.
find_program (SWIFT_EXECUTABLE swift REQUIRED)
set (icon "${PROJECT_BINARY_DIR}/${MACOSX_BUNDLE_ICON_FILE}")
add_custom_command (OUTPUT "${icon}"
COMMAND ${SWIFT_EXECUTABLE} "${PROJECT_SOURCE_DIR}/gen-icon.swift" "${icon}"
DEPENDS gen-icon.swift
COMMENT "Generating xM application icon" VERBATIM)
set_source_files_properties ("${icon}" PROPERTIES
MACOSX_PACKAGE_LOCATION Resources)
# Other requirements: macOS 10.14 for Network, and macOS 11 for Logger.
set (CMAKE_Swift_LANGUAGE_VERSION 5)
add_executable (xM MACOSX_BUNDLE
main.swift "${icon}" "${PROJECT_BINARY_DIR}/xC-proto.swift")

99
xM/gen-icon.swift Normal file
View File

@@ -0,0 +1,99 @@
// gen-icon.swift: generate a program icon for xM in the Apple icon format
//
// Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name>
// SPDX-License-Identifier: 0BSD
//
// As an odd regression, AppKit may be necessary for JIT linking.
import AppKit
// NSGraphicsContext mostly just weirdly wraps over Quartz,
// so we do it all in Quartz directly.
import CoreGraphics
import Foundation
import ImageIO
import UniformTypeIdentifiers
// Apple uses something that's close to a "quintic superellipse" in their icons,
// but doesn't quite match. Either way, it looks better than rounded rectangles.
func addSquircle(context: CGContext, bounds: CGRect) {
context.move(to: CGPoint(x: bounds.maxX, y: bounds.midY))
for theta in stride(from: 0.0, to: .pi * 2, by: .pi / 1e4) {
let x = pow(abs(cos(theta)), 2 / 5.0) * bounds.width / 2
* CGFloat(signOf: cos(theta), magnitudeOf: 1) + bounds.midX
let y = pow(abs(sin(theta)), 2 / 5.0) * bounds.height / 2
* CGFloat(signOf: sin(theta), magnitudeOf: 1) + bounds.midY
context.addLine(to: CGPoint(x: x, y: y))
}
context.closePath()
}
func drawIcon(scale: CGFloat) -> CGImage? {
let size = CGSizeMake(1024, 1024)
let colorspace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: nil,
width: Int(size.width * scale), height: Int(size.height * scale),
bitsPerComponent: 8, bytesPerRow: 0, space: colorspace,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
context.scaleBy(x: scale, y: scale)
let bounds = CGRectMake(100, 100, size.width - 200, size.height - 200)
addSquircle(context: context, bounds: bounds)
let squircle = context.path!
// Gradients don't draw shadows, so draw it separately.
context.saveGState()
context.setShadow(offset: CGSizeMake(0, -12).applying(context.ctm),
blur: 28 * scale, color: CGColor(gray: 0, alpha: 0.5))
context.setFillColor(CGColor(red: 1, green: 0x55p-8, blue: 0, alpha: 1))
context.fillPath()
context.restoreGState()
context.saveGState()
context.addPath(squircle)
context.clip()
context.drawLinearGradient(
CGGradient(colorsSpace: colorspace, colors: [
CGColor(red: 1, green: 0x00p-8, blue: 0, alpha: 1),
CGColor(red: 1, green: 0xaap-8, blue: 0, alpha: 1)
] as CFArray, locations: [0, 1])!,
start: CGPointMake(0, 100), end: CGPointMake(0, size.height - 100),
options: CGGradientDrawingOptions(rawValue: 0))
context.restoreGState()
context.move(to: CGPoint(x: size.width * 0.30, y: size.height * 0.30))
context.addLine(to: CGPoint(x: size.width * 0.30, y: size.height * 0.70))
context.addLine(to: CGPoint(x: size.width * 0.575, y: size.height * 0.425))
context.move(to: CGPoint(x: size.width * 0.70, y: size.height * 0.30))
context.addLine(to: CGPoint(x: size.width * 0.70, y: size.height * 0.70))
context.addLine(to: CGPoint(x: size.width * 0.425, y: size.height * 0.425))
context.setLineWidth(80)
context.setLineCap(.round)
context.setLineJoin(.round)
context.setStrokeColor(CGColor.white)
context.strokePath()
return context.makeImage()
}
if CommandLine.arguments.count != 2 {
print("Usage: \(CommandLine.arguments.first!) OUTPUT.icns")
exit(EXIT_FAILURE)
}
let filename = CommandLine.arguments[1]
let macOSSizes: Array<CGFloat> = [16, 32, 128, 256, 512]
let icns = CGImageDestinationCreateWithURL(
URL(fileURLWithPath: filename) as CFURL,
UTType.icns.identifier as CFString, macOSSizes.count * 2, nil)!
for size in macOSSizes {
CGImageDestinationAddImage(icns, drawIcon(scale: size / 1024.0)!, nil)
CGImageDestinationAddImage(icns, drawIcon(scale: size / 1024.0 * 2)!, [
kCGImagePropertyDPIWidth: 144,
kCGImagePropertyDPIHeight: 144,
] as CFDictionary)
}
if !CGImageDestinationFinalize(icns) {
print("ICNS finalization failed.")
exit(EXIT_FAILURE)
}

1376
xM/main.swift Normal file

File diff suppressed because it is too large Load Diff

3
xN/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/xN
/xN.1
/irc.go

21
xN/Makefile Normal file
View File

@@ -0,0 +1,21 @@
.POSIX:
.SUFFIXES:
AWK = env LC_ALL=C awk
outputs = irc.go xN xN.1
all: $(outputs)
# If we want to keep module dependencies separate, we don't have many options.
# Symlinking seems to work good enough.
irc.go: ../xS/irc.go
ln -sf ../xS/irc.go $@
xN: xN.go ../xK-version irc.go
go build -ldflags "-X 'main.projectVersion=$$(cat ../xK-version)'" -o $@
xN.1: ../xK-version ../liberty/tools/asciiman.awk xN.adoc
env "asciidoc-release-version=$$(cat ../xK-version)" \
$(AWK) -f ../liberty/tools/asciiman.awk xN.adoc > $@
test: all
go test
clean:
rm -f $(outputs)

3
xN/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module janouch.name/xK/xN
go 1.19

86
xN/xN.adoc Normal file
View File

@@ -0,0 +1,86 @@
xN(1)
=====
:doctype: manpage
:manmanual: xK Manual
:mansource: xK {release-version}
Name
----
xN - IRC notifier
Synopsis
--------
*xN* [_OPTION_]... IRC-URL...
Description
-----------
*xN* is a simple IRC notifier, sending the text it receives on its standard
input to all IRC targets specified by its command line arguments.
The input text is forced to validate as UTF-8, and it is _not_ split
automatically to comply with the maximum IRC message length.
Thus, make sure to make the lines short, or they will be trimmed by the servers.
*xN* does not attempt to appease flood detectors.
Options
-------
*-debug*::
Print incoming IRC traffic, which may help in debugging any issues.
*-version*::
Output version information and exit.
URL format
----------
*xN* accepts URLs describing IRC users and channels, roughly as specified by
the Internet Draft _draft-butcher-irc-url-04.txt_. Note, however, that close
to no validation is done on these, and you should not pass URLs from untrusted
sources, so as to avoid command or parameter injection.
Any provided username will be propagated to the nickname, username,
and realname. The default value for these is the name of your system user.
As an extension, you may use the following options:
*skipjoin*::
Do not JOIN channels before sending messages to them.
This requires channels to allow external messages
(which are disabled by channel mode *+n*).
*usenotice*::
Send a NOTICE rather than a PRIVMSG, which distinguishes automated messages,
and is more appropriate for bots.
Examples
--------
$ uptime | xN 'irc://uptime@localhost/%23watch?skipjoin&usenotice'
Send *uptime*(1) information as an external notice to channel *#watch*
on the local server, using the standard port *6667*.
$ fortune -s | xN ircs://ohayou@irc.libera.chat/john,isuser
Greet user *john* with a fortune for this day. In compliance with _RFC 7194_,
the default TLS port is assumed to be *6697*.
$ xN 'ircs://agent:Password123@irc.cia.gov:1337/#hq?key=123456' <<EOF
The red fox trots quietly at midnight.
EOF
Connect over TLS to *irc.cia.gov* on port *1337*, use *Password123*
as the server password, register as user *agent*,
join channel *#hq* using the channel key *123456*,
and send a very secret message.
Reporting bugs
--------------
Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests.
See also
--------
_Uniform Resource Locator Schemes for Internet Relay Chat Entities_,
https://datatracker.ietf.org/doc/html/draft-butcher-irc-url-04[].
_Default Port for Internet Relay Chat (IRC) via TLS/SSL_,
https://datatracker.ietf.org/doc/html/rfc7194[].

279
xN/xN.go Normal file
View File

@@ -0,0 +1,279 @@
//
// Copyright (c) 2024, 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.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// xN is a simple IRC notifier.
package main
import (
"bufio"
"bytes"
"crypto/tls"
"errors"
"flag"
"fmt"
"io"
"log"
"net"
"net/url"
"os"
"os/user"
"strconv"
"strings"
)
const projectName = "xN"
var projectVersion = "?"
var debugMode = false
type parameters struct {
conn net.Conn // underlying network connection
username string // nickname + username + realname
password string // server password
target string // where to send text
isuser bool // the target is a user rather than a channel
chankey string // channel key
skipjoin bool // whether to send external messages to channels
usenotice bool // whether to use NOTICE rather than PRIVMSG
message []string // lines of the message to send
}
func (p *parameters) send(command string, args ...string) error {
var buf bytes.Buffer
buf.WriteString(command)
for i, arg := range args {
buf.WriteRune(' ')
if i+1 == len(args) {
buf.WriteRune(':')
}
buf.WriteString(arg)
}
buf.WriteString("\r\n")
_, err := p.conn.Write(buf.Bytes())
return err
}
const (
rplWELCOME = "001"
errNICKNAMEINUSE = "433"
)
func notify(p *parameters) error {
// The intro should comfortably fit in the TCP send buffer whole.
if p.password != "" {
p.send("PASS", p.password)
}
p.send("USER", p.username, "0", "*", p.username)
p.send("NICK", p.username)
scanner, nickCounter, issue := bufio.NewScanner(p.conn), 1, ""
for scanner.Scan() {
if debugMode {
log.Println(scanner.Text())
}
m := ircParseMessage(scanner.Text())
switch m.command {
case "PING":
p.send("PONG", m.params...)
case rplWELCOME:
if !p.isuser && !p.skipjoin {
if p.chankey != "" {
p.send("JOIN", p.target, p.chankey)
} else {
p.send("JOIN", p.target)
}
}
for _, line := range p.message {
if p.usenotice {
p.send("NOTICE", p.target, line)
} else {
p.send("PRIVMSG", p.target, line)
}
}
p.send("QUIT")
case errNICKNAMEINUSE:
p.send("NICK", fmt.Sprintf("%s%d", p.username, nickCounter))
nickCounter++
default:
// Prevent hanging on unsuccessful registrations.
numeric, _ := strconv.Atoi(m.command)
if numeric >= 400 && numeric <= 599 {
if len(m.params) > 1 {
issue = strings.Join(m.params[1:], " ")
} else {
issue = strings.Join(m.params, " ")
}
p.send("QUIT")
}
}
}
if err := scanner.Err(); err != nil {
return err
}
if issue != "" {
return errors.New(issue)
}
return nil
}
func parse(rawURL string, text []byte) (
p parameters, connect func() (net.Conn, error), err error) {
u, err := url.Parse(rawURL)
if err != nil {
return p, nil, err
} else if !u.IsAbs() || u.Opaque != "" {
return p, nil, errors.New("need an absolute URL")
} else if u.Path == "/" && u.Fragment != "" {
// Try to handle the common but degenerate case.
fragment := "%23" + u.Fragment
u.Fragment, u.RawFragment = "", ""
if u, err = url.Parse(u.String() + fragment); err != nil {
return p, nil, err
}
}
// Figure out registration details.
p.username = projectName
if u, _ := user.Current(); u != nil {
p.username = u.Username
}
if u.User.Username() != "" {
p.username = u.User.Username()
}
p.password, _ = u.User.Password()
// Figure out the target, which for our intents must accept messages.
path, _ := strings.CutPrefix(u.Path, "/")
elements := strings.Split(path, ",")
if path == "" || elements[0] == "" {
return p, nil, errors.New("unspecified entity")
}
// The last entity type wins.
p.target, p.isuser = elements[0], false
for _, typ := range elements[1:] {
switch typ {
case "isuser":
p.isuser = true
case "ischannel":
p.isuser = false
case "isserver":
// We do not support network names, and this is the default.
default:
return p, nil, errors.New("unsupported type: " + typ)
}
}
if p.isuser {
if i := strings.IndexAny(p.target, "!@"); i != -1 {
p.target = p.target[:i]
}
} else if !strings.HasPrefix(p.target, "#") {
// TODO(p): We should consult RPL_ISUPPORT rather than guess,
// though other prefixes are rare.
p.target = "#" + p.target
}
// Note that the draft RFC wants these to be case-insensitive.
p.chankey = u.Query().Get("key")
// Being able to skip channel join is our own requirement and invention,
// as are notices (names taken from Travis CI configuration).
p.skipjoin = u.Query().Has("skipjoin")
p.usenotice = u.Query().Has("usenotice")
// Ensure valid LF-separated UTF-8, and split it at lines.
sanitized := strings.ReplaceAll(string([]rune(string(text))), "\r", "\n")
for _, line := range strings.Split(sanitized, "\n") {
if line != "" {
p.message = append(p.message, line)
}
}
hostname, port := u.Hostname(), u.Port()
switch u.Scheme {
case "irc":
if port == "" {
port = "6667"
}
connect = func() (net.Conn, error) {
return net.Dial("tcp", net.JoinHostPort(hostname, port))
}
case "ircs":
if port == "" {
port = "6697"
}
connect = func() (net.Conn, error) {
return tls.Dial("tcp", net.JoinHostPort(hostname, port), nil)
}
default:
err = errors.New("unsupported scheme: " + u.Scheme)
}
return
}
// notifyByURL sends the given text to the IRC server specified by a URL.
// See draft-butcher-irc-url-04.txt for the URL scheme specification
// this function loosely follows.
func notifyByURL(rawURL string, text []byte) error {
p, connect, err := parse(rawURL, text)
if p.conn, err = connect(); err != nil {
return err
}
defer p.conn.Close()
return notify(&p)
}
func main() {
flag.BoolVar(&debugMode, "debug", false, "run in verbose debug mode")
version := flag.Bool("version", false, "show version and exit")
flag.Usage = func() {
f := flag.CommandLine.Output()
fmt.Fprintf(f, "Usage: %s [OPTION]... URL...\n", os.Args[0])
flag.PrintDefaults()
}
flag.Parse()
if *version {
fmt.Printf("%s %s\n", projectName, projectVersion)
return
}
if flag.NArg() < 1 {
flag.Usage()
os.Exit(2)
}
text, err := io.ReadAll(os.Stdin)
if err != nil {
log.Fatalln(err)
}
status := 0
for _, rawURL := range flag.Args() {
if err := notifyByURL(rawURL, text); err != nil {
status = 1
var ue *url.Error
if errors.As(err, &ue) {
log.Println(err)
} else {
log.Printf("notify %q: %s\n", rawURL, err)
}
}
}
os.Exit(status)
}

98
xN/xN_test.go Normal file
View File

@@ -0,0 +1,98 @@
//
// Copyright (c) 2024, 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.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
package main
import "testing"
func TestParseURL(t *testing.T) {
for _, rawURL := range []string{
`irc:server/channel`,
`irc://server/channel,isnetwork`,
`ircs://ssl.ircnet.io`,
`ircs://ssl.ircnet.io/`,
`http://server/path`,
} {
if _, _, err := parse(rawURL, nil); err == nil {
t.Errorf("%q should not parse\n", rawURL)
}
}
for _, test := range []struct {
rawURL string
p parameters
}{
{
rawURL: `irc://uptime@localhost/%23watch?skipjoin&usenotice`,
p: parameters{
username: "uptime",
target: "#watch",
skipjoin: true,
usenotice: true,
},
},
{
rawURL: `ircs://ohayou@irc.libera.chat/john,isuser,isserver`,
p: parameters{
username: "ohayou",
target: "john",
isuser: true,
},
},
{
rawURL: `ircs://agent:Password123@irc.cia.gov:1337/#hq?key=123456`,
p: parameters{
username: "agent",
password: "Password123",
target: "#hq",
chankey: "123456",
},
},
} {
p, _, err := parse(test.rawURL, nil)
if err != nil {
t.Errorf("%q should parse, got: %s\n", test.rawURL, err)
continue
}
if p.username != test.p.username {
t.Errorf("%q: username: %v ≠ %v\n",
test.rawURL, p.username, test.p.username)
}
if p.password != test.p.password {
t.Errorf("%q: password: %v ≠ %v\n",
test.rawURL, p.password, test.p.password)
}
if p.target != test.p.target {
t.Errorf("%q: target: %v ≠ %v\n",
test.rawURL, p.target, test.p.target)
}
if p.isuser != test.p.isuser {
t.Errorf("%q: isuser: %v ≠ %v\n",
test.rawURL, p.isuser, test.p.isuser)
}
if p.chankey != test.p.chankey {
t.Errorf("%q: chankey: %v ≠ %v\n",
test.rawURL, p.chankey, test.p.chankey)
}
if p.skipjoin != test.p.skipjoin {
t.Errorf("%q: skipjoin: %v ≠ %v\n",
test.rawURL, p.skipjoin, test.p.skipjoin)
}
if p.usenotice != test.p.usenotice {
t.Errorf("%q: usenotice: %v ≠ %v\n",
test.rawURL, p.usenotice, test.p.usenotice)
}
}
}

BIN
xP.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

4
xP/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/xP
/proto.go
/public/proto.js
/public/mithril.js

21
xP/Makefile Normal file
View File

@@ -0,0 +1,21 @@
.POSIX:
.SUFFIXES:
AWK = env LC_ALL=C awk
tools = ../liberty/tools
outputs = xP proto.go public/proto.js public/mithril.js
all: $(outputs) public/ircfmt.woff2
xP: xP.go proto.go
go build -o $@
proto.go: $(tools)/lxdrgen.awk $(tools)/lxdrgen-go.awk ../xC.lxdr
$(AWK) -f $(tools)/lxdrgen.awk -f $(tools)/lxdrgen-go.awk \
-v PrefixCamel=Relay ../xC.lxdr > $@
public/proto.js: $(tools)/lxdrgen.awk $(tools)/lxdrgen-mjs.awk ../xC.lxdr
$(AWK) -f $(tools)/lxdrgen.awk -f $(tools)/lxdrgen-mjs.awk ../xC.lxdr > $@
public/ircfmt.woff2: gen-ircfmt.awk
$(AWK) -v Output=$@ -f gen-ircfmt.awk
public/mithril.js:
curl -Lo $@ https://unpkg.com/mithril/mithril.js
clean:
rm -f $(outputs)

89
xP/gen-ircfmt.awk Normal file
View File

@@ -0,0 +1,89 @@
# gen-ircfmt.awk: generate a supplementary font for IRC formatting characters
#
# Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
#
# Usage: awk -v Output=static/ircfmt.woff2 -f gen-ircfmt.awk
# Clean up SVG byproducts yourself.
BEGIN {
if (!Output) {
print "Error: you must specify the output filename"
exit 1
}
}
function glyph(name, code, path, filename, actions, cmd) {
filename = Output "." name ".svg"
# Inkscape still does a terrible job at the stroke-to-path conversion.
actions = \
"select-by-id:group;" \
"selection-ungroup;" \
"select-clear;" \
"select-by-id:path;" \
"object-stroke-to-path;" \
"select-by-id:clip;" \
"path-intersection;" \
"select-all;" \
"path-combine;" \
"export-overwrite;" \
"export-filename:" filename ";" \
"export-do"
# These dimensions fit FontForge defaults, and happen to work well.
cmd = "inkscape --pipe --actions='" actions "'"
print "<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n" \
"<svg version='1.1' xmlns='http://www.w3.org/2000/svg'\n" \
" width='1000' height='1000' viewBox='0 0 1000 1000'>\n" \
" <rect x='0' y='0' width='1000' height='1000' />\n" \
" <g id='group' transform='translate(200 200) scale(60, 60)'>\n" \
" <rect id='clip' x='0' y='0' width='10' height='10' />\n" \
" <path id='path' stroke-width='2' fill='none' stroke='#fff'\n" \
" d='" path "' />\n" \
" </g>\n" \
"</svg>\n" | cmd
close(cmd)
print "Select(0u" code ")\n" \
"Import('" filename "')" | FontForge
}
BEGIN {
FontForge = "fontforge -lang=ff -"
print "New()" | FontForge
# Designed a 10x10 raster, going for maximum simplicity.
glyph("B", "02", "m 6,5 c 0,0 2,0 2,2 0,2 -2,2 -2,2 h -3 v -8 h 2.5 c 0,0 2,0 2,2 0,2 -2,2 -2,2 h -2 Z")
glyph("C", "03", "m 7.6,7 A 3,4 0 0 1 4.25,8.875 3,4 0 0 1 2,5 3,4 0 0 1 4.25,1.125 3,4 0 0 1 7.6,3")
glyph("I", "1D", "m 3,9 h 4 m 0,-8 h -4 m 2,-1 v 10")
glyph("M", "11", "m 2,10 v -10 l 3,6 3,-6 v 10")
glyph("O", "0F", "m 1,9 l 8,-8 M 2,5 a 3,3 0 1 0 6,0 3,3 0 1 0 -6,0 z")
#glyph("R", "0F", "m 3,10 v -9 h 2 c 0,0 2.5,0 2.5,2.5 0,2.5 -2.5,2.5 -2.5,2.5 h -2 2.5 l 2.5,4.5")
glyph("S", "1E", "m 7.5,3 c 0,-1 -1,-2 -2.5,-2 -1.5,0 -2.5,1 -2.5,2 0,3 5,1 5,4 0,1 -1,2 -2.5,2 -1.5,0 -2.5,-1 -2.5,-2")
glyph("U", "1F", "m 2.5,0 v 6.5 c 0,1.5 1,2.5 2.5,2.5 1.5,0 2.5,-1 2.5,-2.5 v -6.5")
glyph("V", "16", "m 2,-1 3,11 3,-11")
# In practice, your typical browser font will overshoot its em box,
# so to make the display more cohesive, we need to do the same.
# Sadly, sf->use_typo_metrics can't be unset from FontForge script--
# this is necessary to prevent the caret from jumping upon the first
# inserted non-formatting character in xP's textarea.
# https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align
print "SelectAll()\n" \
"Scale(115, 115, 0, 0)\n" \
"SetOS2Value('WinAscentIsOffset', 1)\n" \
"SetOS2Value('WinDescentIsOffset', 1)\n" \
"SetOS2Value('HHeadAscentIsOffset', 1)\n" \
"SetOS2Value('HHeadDescentIsOffset', 1)\n" \
"CorrectDirection()\n" \
"AutoWidth(100)\n" \
"AutoHint()\n" \
"AddExtrema()\n" \
"RoundToInt()\n" \
"SetFontNames('IRCFormatting-Regular'," \
" 'IRC Formatting', 'IRC Formatting Regular', 'Regular'," \
" 'Copyright (c) 2022, Premysl Eric Janouch')\n" \
"Generate('" Output "')\n" | FontForge
close(FontForge)
}

7
xP/go.mod Normal file
View File

@@ -0,0 +1,7 @@
module janouch.name/xK/xP
go 1.22
toolchain go1.23.2
require nhooyr.io/websocket v1.8.17

2
xP/go.sum Normal file
View File

@@ -0,0 +1,2 @@
nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y=
nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=

BIN
xP/public/ircfmt.woff2 Normal file

Binary file not shown.

260
xP/public/xP.css Normal file
View File

@@ -0,0 +1,260 @@
@font-face {
src: url('ircfmt.woff2') format('woff2');
font-family: 'IRC Formatting';
font-weight: normal;
font-style: normal;
}
body {
margin: 0;
padding: 0;
/* Firefox only renders C0 within the textarea, why? */
font-family: 'IRC Formatting', sans-serif;
font-size: clamp(0.5rem, 2vw, 1rem);
}
.xP {
display: flex;
flex-direction: column;
overflow: hidden;
height: 100vh;
/* https://caniuse.com/viewport-unit-variants */
height: 100dvh;
}
.overlay {
padding: .6em .9em;
background: #eee;
border: 1px outset #eee;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
text-align: center;
z-index: 1;
}
.title, .status {
padding: .05em .3em;
background: #eee;
display: flex;
justify-content: space-between;
align-items: baseline;
column-gap: .3em;
position: relative;
border-top: 3px solid #ccc;
border-bottom: 2px solid #888;
}
.title {
/* To approximate right-aligned space-between. */
flex-direction: row-reverse;
}
.title:before, .status:before {
content: " ";
position: absolute;
left: 0;
right: 0;
height: 2px;
top: -2px;
background: #fff;
}
.title:after, .status:after {
content: " ";
position: absolute;
left: 0;
right: 0;
height: 1px;
bottom: -1px;
background: #ccc;
}
.toolbar {
display: flex;
align-items: baseline;
margin-right: -.3em;
}
.indicator {
margin: 0 .3em;
}
button {
font: inherit;
background: transparent;
border: 1px solid transparent;
padding: 0 .3em;
}
button:focus {
border: 1px dotted #000;
}
button:hover {
border-left: 1px solid #fff;
border-top: 1px solid #fff;
border-right: 1px solid #888;
border-bottom: 1px solid #888;
}
button:hover:active {
border-left: 1px solid #888;
border-top: 1px solid #888;
border-right: 1px solid #fff;
border-bottom: 1px solid #fff;
}
.middle {
flex: auto;
display: flex;
flex-direction: row;
overflow: hidden;
}
.list {
overflow-y: auto;
border-right: 2px solid #ccc;
min-width: 10em;
flex-shrink: 0;
}
.item {
padding: .05em .3em;
cursor: default;
}
.item.highlighted {
color: #ff5f00;
}
.item.activity {
font-weight: bold;
}
.item:hover {
background: #f8f8f8;
}
.item.current {
font-style: italic;
background: #eee;
}
/* Only Firefox currently supports align-content: safe end, thus this. */
.buffer-container {
flex: auto;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
.filler {
flex: auto;
}
.buffer {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
overflow-y: auto;
}
.log {
font-family: monospace;
overflow-y: auto;
}
.log, .content, .completions {
/* Note: https://bugs.chromium.org/p/chromium/issues/detail?id=1261435 */
white-space: break-spaces;
overflow-wrap: break-word;
}
.log, .buffer .content {
padding: .1em .3em;
}
.leaked {
opacity: 50%;
}
.date {
padding: .3em;
grid-column: span 2;
font-weight: bold;
}
.unread {
grid-column: span 2;
border-top: 1px solid #ff5f00;
}
.time {
padding: .1em .3em;
background: #f8f8f8;
color: #bbb;
border-right: 1px solid #ccc;
}
.time.hidden:after {
border-top: .2em dotted #ccc;
display: block;
width: 50%;
margin: 0 auto;
content: "";
}
.mark {
padding-right: .3em;
text-align: center;
display: inline-block;
min-width: 2em;
}
.mark.error {
color: red;
}
.mark.join {
color: green;
}
.mark.part {
color: red;
}
.mark.action {
color: darkred;
}
.content .b {
font-weight: bold;
}
.content .i {
font-style: italic;
}
.content .u {
text-decoration: underline;
}
.content .s {
text-decoration: line-through;
}
.content .m {
font-family: monospace;
}
.completions {
position: absolute;
left: 0;
right: 0;
bottom: 0;
background: #fff;
padding: .05em .3em;
border-top: 1px solid #888;
max-height: 50%;
display: flex;
flex-flow: column wrap;
column-gap: .6em;
overflow-x: auto;
}
.input {
flex-shrink: 0;
border: 2px inset #eee;
overflow: hidden;
resize: vertical;
display: flex;
}
.input:focus-within {
border-color: #ff5f00;
}
.prompt {
padding: .05em .3em;
border-right: 1px solid #ccc;
background: #f8f8f8;
font-weight: bold;
}
textarea {
font: inherit;
padding: .05em .3em;
margin: 0;
border: 0;
flex-grow: 1;
resize: none;
}
textarea:focus {
outline: none;
}

1222
xP/public/xP.js Normal file

File diff suppressed because it is too large Load Diff

359
xP/xP.go Normal file
View File

@@ -0,0 +1,359 @@
// Copyright (c) 2022 - 2025, Přemysl Eric Janouch <p@janouch.name>
// SPDX-License-Identifier: 0BSD
package main
import (
"bufio"
"context"
"crypto/sha1"
"embed"
"encoding/binary"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"html/template"
"io"
"io/fs"
"log"
"net"
"net/http"
"os"
"strings"
"time"
"nhooyr.io/websocket"
)
var (
debug = flag.Bool("debug", false, "enable debug output")
webRoot = flag.String("webroot", "", "override bundled web resources")
//go:embed public/*
webResources embed.FS
webResourcesHash string
addressBind string
addressConnect string
addressWS string
)
// -----------------------------------------------------------------------------
func relayReadFrame(r io.Reader) []byte {
var length uint32
if err := binary.Read(r, binary.BigEndian, &length); err != nil {
log.Println("Event receive failed: " + err.Error())
return nil
}
b := make([]byte, length)
if _, err := io.ReadFull(r, b); err != nil {
log.Println("Event receive failed: " + err.Error())
return nil
}
if *debug {
log.Printf("<? %v\n", b)
var m RelayEventMessage
if after, ok := m.ConsumeFrom(b); !ok {
log.Println("Event deserialization failed")
return nil
} else if len(after) != 0 {
log.Println("Event deserialization failed: trailing data")
return nil
}
j, err := m.MarshalJSON()
if err != nil {
log.Println("Event marshalling failed: " + err.Error())
return nil
}
log.Printf("<- %s\n", j)
}
return b
}
func relayMakeReceiver(ctx context.Context, conn net.Conn) <-chan []byte {
// The usual event message rarely gets above 1 kilobyte,
// thus this is set to buffer up at most 1 megabyte or so.
p := make(chan []byte, 1000)
r := bufio.NewReaderSize(conn, 65536)
go func() {
defer close(p)
for {
b := relayReadFrame(r)
if b == nil {
return
}
select {
case p <- b:
case <-ctx.Done():
return
}
}
}()
return p
}
func relayWriteJSON(conn net.Conn, j []byte) bool {
var m RelayCommandMessage
if err := json.Unmarshal(j, &m); err != nil {
log.Println("Command unmarshalling failed: " + err.Error())
return false
}
b, ok := m.AppendTo(make([]byte, 4))
if !ok {
log.Println("Command serialization failed")
return false
}
binary.BigEndian.PutUint32(b[:4], uint32(len(b)-4))
if _, err := conn.Write(b); err != nil {
log.Println("Command send failed: " + err.Error())
return false
}
if *debug {
log.Printf("-> %v\n", b)
}
return true
}
// -----------------------------------------------------------------------------
func clientReadJSON(ctx context.Context, ws *websocket.Conn) []byte {
t, j, err := ws.Read(ctx)
if err != nil {
log.Println("Command receive failed: " + err.Error())
return nil
}
if t != websocket.MessageText {
log.Println(
"Command receive failed: " + "binary messages are not supported")
return nil
}
if *debug {
log.Printf("?> %s\n", j)
}
return j
}
func clientWriteBinary(ctx context.Context, ws *websocket.Conn, b []byte) bool {
if err := ws.Write(ctx, websocket.MessageBinary, b); err != nil {
log.Println("Event send failed: " + err.Error())
return false
}
return true
}
func clientWriteError(ctx context.Context, ws *websocket.Conn, err error) bool {
b, ok := (&RelayEventMessage{
EventSeq: 0,
Data: RelayEventData{
Variant: &RelayEventDataError{
CommandSeq: 0,
Error: err.Error(),
},
},
}).AppendTo(nil)
if ok {
log.Println("Event serialization failed")
return false
}
return clientWriteBinary(ctx, ws, b)
}
func handleWS(w http.ResponseWriter, r *http.Request) {
opts := &websocket.AcceptOptions{
InsecureSkipVerify: true,
CompressionMode: websocket.CompressionContextTakeover,
// This is for the payload; set higher to avoid overhead.
CompressionThreshold: 64 << 10,
}
// AppleWebKit can be broken with compression.
if agent := r.UserAgent(); strings.Contains(agent, " Version/") &&
(strings.HasPrefix(agent, "Mozilla/5.0 (Macintosh; ") ||
strings.HasPrefix(agent, "Mozilla/5.0 (iPhone; ")) {
opts.CompressionMode = websocket.CompressionDisabled
}
ws, err := websocket.Accept(w, r, opts)
if err != nil {
log.Println("Client rejected: " + err.Error())
return
}
defer ws.Close(websocket.StatusGoingAway, "Goodbye")
ctx, cancel := context.WithCancel(r.Context())
defer cancel()
conn, err := net.Dial("tcp", addressConnect)
if err != nil {
log.Println("Connection failed: " + err.Error())
clientWriteError(ctx, ws, err)
return
}
defer conn.Close()
// To decrease latencies, events are received and decoded in parallel
// to their sending, and we try to batch them together.
relayFrames := relayMakeReceiver(ctx, conn)
batchFrames := func() []byte {
batch, ok := <-relayFrames
if !ok {
return nil
}
Batch:
for {
select {
case b, ok := <-relayFrames:
if !ok {
break Batch
}
batch = append(batch, b...)
default:
break Batch
}
}
return batch
}
// We don't need to intervene, so it's just two separate pipes so far.
go func() {
defer cancel()
for {
j := clientReadJSON(ctx, ws)
if j == nil {
return
}
relayWriteJSON(conn, j)
}
}()
go func() {
defer cancel()
for {
b := batchFrames()
if b == nil {
return
}
clientWriteBinary(ctx, ws, b)
}
}()
<-ctx.Done()
}
// -----------------------------------------------------------------------------
var page = template.Must(template.New("/").Parse(`<!DOCTYPE html>
<html>
<head>
<title>xP</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<base href="{{ .Root }}/">
<link rel="stylesheet" href="xP.css" />
</head>
<body>
<script src="mithril.js">
</script>
<script>
let proxy = '{{ .Proxy }}'
</script>
<script type="module" src="xP.js">
</script>
</body>
</html>`))
func handleDefault(w http.ResponseWriter, r *http.Request) {
wsURI := addressWS
if wsURI == "" {
wsURI = fmt.Sprintf("ws://%s/ws", r.Host)
}
args := struct {
Root string
Proxy string
}{
Root: webResourcesHash,
Proxy: wsURI,
}
if err := page.Execute(w, &args); err != nil {
log.Println("Template execution failed: " + err.Error())
}
}
func hashFS(root fs.FS) []byte {
hasher := sha1.New()
callback := func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// Note that this can be fooled.
fmt.Fprintln(hasher, path)
if !d.IsDir() {
file, err := root.Open(path)
if err != nil {
return err
}
defer file.Close()
io.Copy(hasher, file)
}
return nil
}
if err := fs.WalkDir(root, ".", callback); err != nil {
log.Fatalln(err)
}
return hasher.Sum(nil)
}
func main() {
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(),
"Usage: %s [OPTION...] BIND CONNECT [WSURI]\n\n", os.Args[0])
flag.PrintDefaults()
}
flag.Parse()
if flag.NArg() < 2 || flag.NArg() > 3 {
flag.Usage()
os.Exit(1)
}
addressBind, addressConnect = flag.Arg(0), flag.Arg(1)
if flag.NArg() > 2 {
addressWS = flag.Arg(2)
}
subResources, err := fs.Sub(webResources, "public")
if err != nil {
log.Fatalln(err)
}
if *webRoot != "" {
subResources = os.DirFS(*webRoot)
}
// The simplest way of ensuring that web browsers don't use
// stale cached copies of our files.
webResourcesHash = hex.EncodeToString(hashFS(subResources))
http.Handle("/"+webResourcesHash+"/",
http.StripPrefix("/"+webResourcesHash+"/",
http.FileServerFS(subResources)))
http.Handle("/ws", http.HandlerFunc(handleWS))
http.Handle("/", http.HandlerFunc(handleDefault))
s := &http.Server{
Addr: addressBind,
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
MaxHeaderBytes: 32 << 10,
}
log.Fatalln(s.ListenAndServe())
}

2
xR/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/xR
/proto.go

17
xR/Makefile Normal file
View File

@@ -0,0 +1,17 @@
.POSIX:
AWK = env LC_ALL=C awk
tools = ../liberty/tools
generated = proto.go
outputs = xR $(generated)
all: $(outputs)
generate: $(generated)
proto.go: $(tools)/lxdrgen.awk $(tools)/lxdrgen-go.awk ../xC.lxdr
$(AWK) -f $(tools)/lxdrgen.awk -f $(tools)/lxdrgen-go.awk \
-v PrefixCamel=Relay ../xC.lxdr > $@
xR: xR.go ../xK-version $(generated)
go build -ldflags "-X 'main.projectVersion=$$(cat ../xK-version)'" -o $@ \
-gcflags=all="-N -l"
clean:
rm -f $(outputs)

5
xR/go.mod Normal file
View File

@@ -0,0 +1,5 @@
module janouch.name/xK/xR
go 1.23.0
toolchain go1.24.0

41
xR/xR.adoc Normal file
View File

@@ -0,0 +1,41 @@
xR(1)
=====
:doctype: manpage
:manmanual: xK Manual
:mansource: xK {release-version}
Name
----
xR - xC relay protocol analyzer
Synopsis
--------
*xR* [_OPTION_]... RELAY-ADDRESS...
Description
-----------
*xR* connects to an *xC* relay and prints all incoming events one per line
in JSON format. The JSON objects have two additional fields:
when::
The time of reception (or sending) as a nanosecond precision
RFC 3339 UTC timestamp.
raw::
The incoming event (or outgoing command) in raw binary form.
Options
-------
*-debug*::
Print any outgoing commands as well, which may help in debugging any issues.
*-version*::
Output version information and exit.
Reporting bugs
--------------
Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests.
See also
--------
*xC*(1)

134
xR/xR.go Normal file
View File

@@ -0,0 +1,134 @@
// Copyright (c) 2025, Přemysl Eric Janouch <p@janouch.name>
// SPDX-License-Identifier: 0BSD
package main
import (
"encoding/binary"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"log"
"net"
"os"
"time"
)
var (
debug = flag.Bool("debug", false, "enable debug output")
version = flag.Bool("version", false, "show version and exit")
projectName = "xR"
projectVersion = "?"
)
func now() string {
return time.Now().UTC().Format(time.RFC3339Nano)
}
func relayReadFrame(r io.Reader) bool {
var length uint32
if err := binary.Read(
r, binary.BigEndian, &length); errors.Is(err, io.EOF) {
return false
} else if err != nil {
log.Fatalln("Event receive failed: " + err.Error())
}
b := make([]byte, length)
if _, err := io.ReadFull(r, b); errors.Is(err, io.EOF) {
return false
} else if err != nil {
log.Fatalln("Event receive failed: " + err.Error())
}
m := struct {
When string `json:"when"`
Binary []byte `json:"raw"`
RelayEventMessage
}{
When: now(),
Binary: b,
}
if after, ok := m.RelayEventMessage.ConsumeFrom(b); !ok {
log.Println("Event deserialization failed")
} else if len(after) != 0 {
log.Println("Event deserialization failed: trailing data")
return true
}
j, err := json.Marshal(m)
if err != nil {
log.Fatalln("Event marshalling failed: " + err.Error())
}
fmt.Printf("%s\n", j)
return true
}
func run(addressConnect string) {
conn, err := net.Dial("tcp", addressConnect)
if err != nil {
log.Println("Connection failed: " + err.Error())
return
}
defer conn.Close()
// We can only support this one protocol version
// that proto.go has been generated for.
m := RelayCommandMessage{CommandSeq: 0, Data: RelayCommandData{
Variant: &RelayCommandDataHello{Version: RelayVersion},
}}
b, ok := m.AppendTo(make([]byte, 4))
if !ok {
log.Fatalln("Command serialization failed")
}
binary.BigEndian.PutUint32(b[:4], uint32(len(b)-4))
if _, err := conn.Write(b); err != nil {
log.Fatalln("Command send failed: " + err.Error())
}
// You can differentiate the direction by the presence
// of .data.command or .data.event.
if *debug {
j, err := json.Marshal(struct {
When string `json:"when"`
Binary []byte `json:"raw"`
RelayCommandMessage
}{
When: now(),
Binary: b,
RelayCommandMessage: m,
})
if err != nil {
log.Fatalln("Command marshalling failed: " + err.Error())
}
fmt.Printf("%s\n", j)
}
for relayReadFrame(conn) {
}
}
func main() {
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(),
"Usage: %s [OPTION...] CONNECT\n\n", os.Args[0])
flag.PrintDefaults()
}
flag.Parse()
if *version {
fmt.Printf("%s %s (relay protocol version %d)\n",
projectName, projectVersion, RelayVersion)
return
}
if flag.NArg() != 1 {
flag.Usage()
os.Exit(1)
}
// TODO(p): This program should be able to run as a filter as well.
run(flag.Arg(0))
}

3
xS/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/xS
/xS-replies.go
/xS.1

18
xS/Makefile Normal file
View File

@@ -0,0 +1,18 @@
.POSIX:
.SUFFIXES:
AWK = env LC_ALL=C awk
outputs = xS xS-replies.go xS.1
all: $(outputs)
xS: xS.go xS-replies.go ../xK-version
go build -ldflags "-X 'main.projectVersion=$$(cat ../xK-version)'" -o $@
xS-replies.go: xS-gen-replies.awk xS-replies
$(AWK) -f xS-gen-replies.awk xS-replies > $@
xS.1: ../xK-version ../liberty/tools/asciiman.awk xS.adoc
env "asciidoc-release-version=$$(cat ../xK-version)" \
$(AWK) -f ../liberty/tools/asciiman.awk xS.adoc > $@
test: all
go test
clean:
rm -f $(outputs)

3
xS/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module janouch.name/xK/xS
go 1.19

132
xS/irc.go Normal file
View File

@@ -0,0 +1,132 @@
package main
import (
"path/filepath"
"regexp"
"strings"
)
func ircToLower(c byte) byte {
switch c {
case '[':
return '{'
case ']':
return '}'
case '\\':
return '|'
case '~':
return '^'
}
if c >= 'A' && c <= 'Z' {
return c + ('a' - 'A')
}
return c
}
func ircToUpper(c byte) byte {
switch c {
case '{':
return '['
case '}':
return ']'
case '|':
return '\\'
case '^':
return '~'
}
if c >= 'a' && c <= 'z' {
return c - ('a' - 'A')
}
return c
}
// Convert identifier to a canonical form for case-insensitive comparisons.
// ircToUpper is used so that statically initialized maps can be in uppercase.
func ircToCanon(ident string) string {
var canon []byte
for _, c := range []byte(ident) {
canon = append(canon, ircToUpper(c))
}
return string(canon)
}
func ircEqual(s1, s2 string) bool {
return ircToCanon(s1) == ircToCanon(s2)
}
func ircFnmatch(pattern string, s string) bool {
pattern, s = ircToCanon(pattern), ircToCanon(s)
// FIXME: This should not support [] ranges and handle '/' specially.
// We could translate the pattern to a regular expression.
matched, _ := filepath.Match(pattern, s)
return matched
}
var reMsg = regexp.MustCompile(
`^(?:@([^ ]*) +)?(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(.*)?$`)
var reArgs = regexp.MustCompile(`:.*| [^: ][^ ]*`)
type message struct {
tags map[string]string // IRC 3.2 message tags
nick string // optional nickname
user string // optional username
host string // optional hostname or IP address
command string // command name
params []string // arguments
}
func ircUnescapeMessageTag(value string) string {
var buf []byte
escape := false
for i := 0; i < len(value); i++ {
if escape {
switch value[i] {
case ':':
buf = append(buf, ';')
case 's':
buf = append(buf, ' ')
case 'r':
buf = append(buf, '\r')
case 'n':
buf = append(buf, '\n')
default:
buf = append(buf, value[i])
}
escape = false
} else if value[i] == '\\' {
escape = true
} else {
buf = append(buf, value[i])
}
}
return string(buf)
}
func ircParseMessageTags(tags string, out map[string]string) {
for _, tag := range strings.Split(tags, ";") {
if tag == "" {
// Ignore empty.
} else if equal := strings.IndexByte(tag, '='); equal < 0 {
out[tag] = ""
} else {
out[tag[:equal]] = ircUnescapeMessageTag(tag[equal+1:])
}
}
}
func ircParseMessage(line string) *message {
m := reMsg.FindStringSubmatch(line)
if m == nil {
return nil
}
msg := message{nil, m[2], m[3], m[4], m[5], nil}
if m[1] != "" {
msg.tags = make(map[string]string)
ircParseMessageTags(m[1], msg.tags)
}
for _, x := range reArgs.FindAllString(m[6], -1) {
msg.params = append(msg.params, x[1:])
}
return &msg
}

20
xS/xS-gen-replies.awk Executable file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/awk -f
/^[0-9]+ *(ERR|RPL)_[A-Z]+ *".*"$/ {
match($0, /".*"/)
ids[$1] = $2
texts[$2] = substr($0, RSTART, RLENGTH)
}
END {
print "package main"
print ""
print "const ("
for (i in ids)
printf("\t%s = %s\n", ids[i], i)
print ")"
print ""
print "var defaultReplies = map[int]string{"
for (i in ids)
print "\t" ids[i] ": " texts[ids[i]] ","
print "}"
}

87
xS/xS-replies Normal file
View File

@@ -0,0 +1,87 @@
1 RPL_WELCOME ":Welcome to the Internet Relay Network %s!%s@%s"
2 RPL_YOURHOST ":Your host is %s, running version %s"
3 RPL_CREATED ":This server was created %s"
4 RPL_MYINFO "%s %s %s %s"
5 RPL_ISUPPORT "%s :are supported by this server"
211 RPL_STATSLINKINFO "%s %d %d %d %d %d %d"
212 RPL_STATSCOMMANDS "%s %d %d %d"
219 RPL_ENDOFSTATS "%c :End of STATS report"
221 RPL_UMODEIS "+%s"
242 RPL_STATSUPTIME ":Server Up %d days %d:%02d:%02d"
251 RPL_LUSERCLIENT ":There are %d users and %d services on %d servers"
252 RPL_LUSEROP "%d :operator(s) online"
253 RPL_LUSERUNKNOWN "%d :unknown connection(s)"
254 RPL_LUSERCHANNELS "%d :channels formed"
255 RPL_LUSERME ":I have %d clients and %d servers"
301 RPL_AWAY "%s :%s"
302 RPL_USERHOST ":%s"
303 RPL_ISON ":%s"
305 RPL_UNAWAY ":You are no longer marked as being away"
306 RPL_NOWAWAY ":You have been marked as being away"
311 RPL_WHOISUSER "%s %s %s * :%s"
312 RPL_WHOISSERVER "%s %s :%s"
313 RPL_WHOISOPERATOR "%s :is an IRC operator"
314 RPL_WHOWASUSER "%s %s %s * :%s"
315 RPL_ENDOFWHO "%s :End of WHO list"
317 RPL_WHOISIDLE "%s %d :seconds idle"
318 RPL_ENDOFWHOIS "%s :End of WHOIS list"
319 RPL_WHOISCHANNELS "%s :%s"
322 RPL_LIST "%s %d :%s"
323 RPL_LISTEND ":End of LIST"
324 RPL_CHANNELMODEIS "%s +%s"
329 RPL_CREATIONTIME "%s %d"
331 RPL_NOTOPIC "%s :No topic is set"
332 RPL_TOPIC "%s :%s"
333 RPL_TOPICWHOTIME "%s %s %d"
341 RPL_INVITING "%s %s"
346 RPL_INVITELIST "%s %s"
347 RPL_ENDOFINVITELIST "%s :End of channel invite list"
348 RPL_EXCEPTLIST "%s %s"
349 RPL_ENDOFEXCEPTLIST "%s :End of channel exception list"
351 RPL_VERSION "%s.%d %s :%s"
352 RPL_WHOREPLY "%s %s %s %s %s %s :%d %s"
353 RPL_NAMREPLY "%c %s :%s"
364 RPL_LINKS "%s %s :%d %s"
365 RPL_ENDOFLINKS "%s :End of LINKS list"
366 RPL_ENDOFNAMES "%s :End of NAMES list"
367 RPL_BANLIST "%s %s"
368 RPL_ENDOFBANLIST "%s :End of channel ban list"
369 RPL_ENDOFWHOWAS "%s :End of WHOWAS"
372 RPL_MOTD ":- %s"
375 RPL_MOTDSTART ":- %s Message of the day - "
376 RPL_ENDOFMOTD ":End of MOTD command"
391 RPL_TIME "%s :%s"
401 ERR_NOSUCHNICK "%s :No such nick/channel"
402 ERR_NOSUCHSERVER "%s :No such server"
403 ERR_NOSUCHCHANNEL "%s :No such channel"
404 ERR_CANNOTSENDTOCHAN "%s :Cannot send to channel"
406 ERR_WASNOSUCHNICK "%s :There was no such nickname"
409 ERR_NOORIGIN ":No origin specified"
410 ERR_INVALIDCAPCMD "%s :%s"
411 ERR_NORECIPIENT ":No recipient given (%s)"
412 ERR_NOTEXTTOSEND ":No text to send"
421 ERR_UNKNOWNCOMMAND "%s: Unknown command"
422 ERR_NOMOTD ":MOTD File is missing"
423 ERR_NOADMININFO "%s :No administrative info available"
431 ERR_NONICKNAMEGIVEN ":No nickname given"
432 ERR_ERRONEOUSNICKNAME "%s :Erroneous nickname"
433 ERR_NICKNAMEINUSE "%s :Nickname is already in use"
441 ERR_USERNOTINCHANNEL "%s %s :They aren't on that channel"
442 ERR_NOTONCHANNEL "%s :You're not on that channel"
443 ERR_USERONCHANNEL "%s %s :is already on channel"
445 ERR_SUMMONDISABLED ":SUMMON has been disabled"
446 ERR_USERSDISABLED ":USERS has been disabled"
451 ERR_NOTREGISTERED ":You have not registered"
461 ERR_NEEDMOREPARAMS "%s :Not enough parameters"
462 ERR_ALREADYREGISTERED ":Unauthorized command (already registered)"
467 ERR_KEYSET "%s :Channel key already set"
471 ERR_CHANNELISFULL "%s :Cannot join channel (+l)"
472 ERR_UNKNOWNMODE "%c :is unknown mode char to me for %s"
473 ERR_INVITEONLYCHAN "%s :Cannot join channel (+i)"
474 ERR_BANNEDFROMCHAN "%s :Cannot join channel (+b)"
475 ERR_BADCHANNELKEY "%s :Cannot join channel (+k)"
476 ERR_BADCHANMASK "%s :Bad Channel Mask"
481 ERR_NOPRIVILEGES ":Permission Denied- You're not an IRC operator"
482 ERR_CHANOPRIVSNEEDED "%s :You're not channel operator"
501 ERR_UMODEUNKNOWNFLAG ":Unknown MODE flag"
502 ERR_USERSDONTMATCH ":Cannot change mode for other users"

57
xS/xS.adoc Normal file
View File

@@ -0,0 +1,57 @@
xS(1)
=====
:doctype: manpage
:manmanual: xK Manual
:mansource: xK {release-version}
Name
----
xS - IRC daemon
Synopsis
--------
*xS* [_OPTION_]...
Description
-----------
*xS* 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.
Options
-------
*-debug*::
Do not daemonize, print more information on the standard error stream
to help debug various issues.
*-systemd*::
Log using the format specified in *sd-daemon*(3).
*-h*, *-help*::
Display a help message and exit.
*-version*::
Output version information and exit.
*-writedefaultcfg*::
Write a configuration file with defaults, show its path and exit.
+
The file will be appropriately commented.
Files
-----
*xS* follows the XDG Base Directory Specification.
_~/.config/xS/xS.conf_::
_/etc/xdg/xS/xS.conf_::
The daemon's configuration file. Use the *-writedefaultcfg* option
to create a new one for editing.
Reporting bugs
--------------
Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests.
See also
--------
*sd-daemon*(3)

3399
xS/xS.go Normal file

File diff suppressed because it is too large Load Diff

168
xS/xS_test.go Normal file
View File

@@ -0,0 +1,168 @@
//
// Copyright (c) 2015 - 2018, 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.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
package main
import (
"crypto/tls"
"net"
"os"
"reflect"
"syscall"
"testing"
)
func TestSplitString(t *testing.T) {
var splitStringTests = []struct {
s, delims string
ignoreEmpty bool
result []string
}{
{",a,,bc", ",", false, []string{"", "a", "", "bc"}},
{",a,,bc", ",", true, []string{"a", "bc"}},
{"a,;bc,", ",;", false, []string{"a", "", "bc", ""}},
{"a,;bc,", ",;", true, []string{"a", "bc"}},
{"", ",", false, []string{""}},
{"", ",", true, nil},
}
for i, d := range splitStringTests {
got := splitString(d.s, d.delims, d.ignoreEmpty)
if !reflect.DeepEqual(got, d.result) {
t.Errorf("case %d: %v should be %v\n", i, got, d.result)
}
}
}
func socketpair() (*os.File, *os.File, error) {
pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
if err != nil {
return nil, nil, err
}
// See go #24331, this makes 1.11 use the internal poller
// while there wasn't a way to achieve that before.
if err := syscall.SetNonblock(int(pair[0]), true); err != nil {
return nil, nil, err
}
if err := syscall.SetNonblock(int(pair[1]), true); err != nil {
return nil, nil, err
}
fa := os.NewFile(uintptr(pair[0]), "a")
if fa == nil {
return nil, nil, os.ErrInvalid
}
fb := os.NewFile(uintptr(pair[1]), "b")
if fb == nil {
fa.Close()
return nil, nil, os.ErrInvalid
}
return fa, fb, nil
}
func TestDetectTLS(t *testing.T) {
detectTLSFromFunc := func(t *testing.T, writer func(net.Conn)) bool {
// net.Pipe doesn't use file descriptors, we need a socketpair.
sockA, sockB, err := socketpair()
if err != nil {
t.Fatal(err)
}
defer sockA.Close()
defer sockB.Close()
fcB, err := net.FileConn(sockB)
if err != nil {
t.Fatal(err)
}
go writer(fcB)
fcA, err := net.FileConn(sockA)
if err != nil {
t.Fatal(err)
}
sc, err := fcA.(syscall.Conn).SyscallConn()
if err != nil {
t.Fatal(err)
}
return detectTLS(sc)
}
t.Run("SSL_2.0", func(t *testing.T) {
if !detectTLSFromFunc(t, func(fc net.Conn) {
// The obsolete, useless, unsupported SSL 2.0 record format.
_, _ = fc.Write([]byte{0x80, 0x01, 0x01})
}) {
t.Error("could not detect SSL")
}
})
t.Run("crypto_tls", func(t *testing.T) {
if !detectTLSFromFunc(t, func(fc net.Conn) {
conn := tls.Client(fc, &tls.Config{InsecureSkipVerify: true})
_ = conn.Handshake()
}) {
t.Error("could not detect TLS")
}
})
t.Run("text", func(t *testing.T) {
if detectTLSFromFunc(t, func(fc net.Conn) {
_, _ = fc.Write([]byte("ПРЕВЕД"))
}) {
t.Error("detected UTF-8 as TLS")
}
})
t.Run("EOF", func(t *testing.T) {
type connCloseWriter interface {
net.Conn
CloseWrite() error
}
if detectTLSFromFunc(t, func(fc net.Conn) {
_ = fc.(connCloseWriter).CloseWrite()
}) {
t.Error("detected EOF as TLS")
}
})
}
func TestIRC(t *testing.T) {
msg := ircParseMessage(
`@first=a\:\s\r\n\\;2nd :srv hi there :good m8 :how are you?`)
if !reflect.DeepEqual(msg.tags, map[string]string{
"first": "a; \r\n\\",
"2nd": "",
}) {
t.Error("tags parsed incorrectly")
}
if msg.nick != "srv" || msg.user != "" || msg.host != "" {
t.Error("server name parsed incorrectly")
}
if msg.command != "hi" {
t.Error("command name parsed incorrectly")
}
if !reflect.DeepEqual(msg.params,
[]string{"there", "good m8 :how are you?"}) {
t.Error("params parsed incorrectly")
}
if !ircEqual("[fag]^", "{FAG}~") {
t.Error("string case comparison not according to RFC 2812")
}
// TODO: More tests.
}

164
xT/CMakeLists.txt Normal file
View File

@@ -0,0 +1,164 @@
# As per Qt 6.8 documentation, at least 3.16 is necessary
cmake_minimum_required (VERSION 3.21.1)
file (READ ../xK-version project_version)
configure_file (../xK-version xK-version.tag COPYONLY)
string (STRIP "${project_version}" project_version)
# This is an entirely separate CMake project.
project (xT VERSION "${project_version}"
DESCRIPTION "Qt frontend for xC" LANGUAGES CXX)
set (CMAKE_CXX_STANDARD 17)
set (CMAKE_CXX_STANDARD_REQUIRED ON)
find_package (Qt6 REQUIRED COMPONENTS Widgets Network Multimedia)
qt_standard_project_setup ()
add_compile_options ("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options ("$<$<CXX_COMPILER_ID:GNU>:-Wall;-Wextra>")
add_compile_options ("$<$<CXX_COMPILER_ID:Clang>:-Wall;-Wextra>")
set (project_config "${PROJECT_BINARY_DIR}/config.h")
configure_file ("${PROJECT_SOURCE_DIR}/config.h.in" "${project_config}")
include_directories ("${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}")
# Produce a beep sample
find_program (sox_EXECUTABLE sox REQUIRED)
set (beep "${PROJECT_BINARY_DIR}/beep.wav")
add_custom_command (OUTPUT "${beep}"
COMMAND ${sox_EXECUTABLE} -b 16 -Dr 44100 -n "${beep}"
synth 0.1 0 25 triangle 800 vol 0.5 fade t 0 -0 0.005 pad 0 0.05
COMMENT "Generating a beep sample" VERBATIM)
set_property (SOURCE "${beep}" APPEND PROPERTY QT_RESOURCE_ALIAS beep.wav)
# Rasterize SVG icons
set (root "${PROJECT_SOURCE_DIR}/..")
set (CMAKE_MODULE_PATH "${root}/liberty/cmake")
include (IconUtils)
# It might generally be better to use QtSvg, though it is an extra dependency.
# The icon_to_png macro is not intended to be used like this.
foreach (icon xT xT-highlighted)
icon_to_png (${icon} "${PROJECT_SOURCE_DIR}/${icon}.svg"
48 "${PROJECT_BINARY_DIR}/resources" icon_png)
set_property (SOURCE "${icon_png}"
APPEND PROPERTY QT_RESOURCE_ALIAS "${icon}.png")
list (APPEND icon_rsrc_list "${icon_png}")
endforeach ()
if (APPLE)
set (MACOSX_BUNDLE_ICON_FILE xT.icns)
icon_to_icns ("${PROJECT_SOURCE_DIR}/xT.svg"
"${MACOSX_BUNDLE_ICON_FILE}" icon_icns)
else ()
# The largest size is mainly for an appropriately sized Windows icon
set (icon_base "${PROJECT_BINARY_DIR}/icons")
set (icon_png_list)
foreach (icon_size 16 32 48 256)
icon_to_png (xT "${PROJECT_SOURCE_DIR}/xT.svg"
${icon_size} "${icon_base}" icon_png)
list (APPEND icon_png_list "${icon_png}")
endforeach ()
add_custom_target (icons ALL DEPENDS ${icon_png_list})
if (WIN32)
list (REMOVE_ITEM icon_png_list "${icon_png}")
set (icon_ico "${PROJECT_BINARY_DIR}/xT.ico")
icon_for_win32 ("${icon_ico}" "${icon_png_list}" "${icon_png}")
set (resource_file "${PROJECT_BINARY_DIR}/xT.rc")
list (APPEND project_sources "${resource_file}")
add_custom_command (OUTPUT "${resource_file}"
COMMAND ${CMAKE_COMMAND} -E echo "1 ICON \"xT.ico\""
> ${resource_file} VERBATIM)
set_property (SOURCE "${resource_file}"
APPEND PROPERTY OBJECT_DEPENDS ${icon_ico})
endif ()
endif ()
# Build the main executable and link it
find_program (awk_EXECUTABLE awk ${find_program_REQUIRE})
add_custom_command (OUTPUT xC-proto.cpp
COMMAND ${CMAKE_COMMAND} -E env LC_ALL=C ${awk_EXECUTABLE}
-f ${root}/liberty/tools/lxdrgen.awk
-f ${root}/liberty/tools/lxdrgen-cpp.awk
-v PrefixCamel=Relay
${root}/xC.lxdr > xC-proto.cpp
DEPENDS
${root}/liberty/tools/lxdrgen.awk
${root}/liberty/tools/lxdrgen-cpp.awk
${root}/xC.lxdr
COMMENT "Generating xC relay protocol code" VERBATIM)
add_custom_target (xC-proto DEPENDS ${PROJECT_BINARY_DIR}/xC-proto.cpp)
list (APPEND project_sources "${root}/liberty/tools/lxdrgen-cpp-qt.cpp")
qt_add_executable (xT
xT.cpp ${project_config} ${project_sources} "${icon_icns}")
add_dependencies (xT xC-proto)
qt_add_resources (xT "rsrc" PREFIX / FILES "${beep}" ${icon_rsrc_list})
target_link_libraries (xT PRIVATE Qt6::Widgets Qt6::Network Qt6::Multimedia)
set_target_properties (xT PROPERTIES WIN32_EXECUTABLE ON MACOSX_BUNDLE ON
MACOSX_BUNDLE_GUI_IDENTIFIER name.janouch.xT)
# https://stackoverflow.com/questions/79079161 and resolved in Qt Creator 16.
set (QT_QML_GENERATE_QMLLS_INI ON)
# The files to be installed
include (GNUInstallDirs)
if (ANDROID)
install (TARGETS xT DESTINATION .)
elseif (APPLE OR WIN32)
install (TARGETS xT
BUNDLE DESTINATION .
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# XXX: QTBUG-127075, which can be circumvented by manually running
# macdeployqt on xT.app before the install.
qt_generate_deploy_app_script (TARGET xT OUTPUT_SCRIPT deploy_xT)
install (SCRIPT "${deploy_xT}")
else ()
install (TARGETS xT DESTINATION ${CMAKE_INSTALL_BINDIR})
install (FILES ../LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
install (FILES xT.svg
DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
install (DIRECTORY ${icon_base}
DESTINATION ${CMAKE_INSTALL_DATADIR})
install (FILES xT.desktop
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
endif ()
# Within MSYS2, windeployqt doesn't copy the compiler runtime,
# which is always linked dynamically by the Qt binaries.
# TODO(p): Consider whether or not to use MSYS2 to cross-compile, and how.
if (WIN32)
install (CODE [=[
set (bindir "${CMAKE_INSTALL_PREFIX}/bin")
execute_process (COMMAND cygpath -m /
OUTPUT_VARIABLE cygroot OUTPUT_STRIP_TRAILING_WHITESPACE)
if (cygroot)
execute_process (COMMAND ldd "${bindir}/xT.exe"
OUTPUT_VARIABLE ldd_output OUTPUT_STRIP_TRAILING_WHITESPACE)
string (REGEX MATCHALL " /mingw64/bin/[^ ]+ " libs "${ldd_output}")
foreach (lib ${libs})
string (STRIP "${lib}" lib)
file (COPY "${cygroot}${lib}" DESTINATION "${bindir}")
endforeach()
endif ()
]=])
endif ()
# CPack
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")
set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/../LICENSE")
set (CPACK_GENERATOR "TGZ;ZIP")
set (CPACK_PACKAGE_FILE_NAME
"${PROJECT_NAME}-${PROJECT_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME} ${PROJECT_VERSION}")
set (CPACK_SOURCE_GENERATOR "TGZ;ZIP")
set (CPACK_SOURCE_IGNORE_FILES "/build;/CMakeLists.txt.user")
set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
include (CPack)

7
xT/config.h.in Normal file
View File

@@ -0,0 +1,7 @@
#ifndef CONFIG_H
#define CONFIG_H
#define PROJECT_NAME "${PROJECT_NAME}"
#define PROJECT_VERSION "${project_version}"
#endif // ! CONFIG_H

29
xT/xT-highlighted.svg Normal file
View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1.1" width="48" height="48" viewBox="0 0 48 48"
xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="green-x">
<stop stop-color="hsl(66, 100%, 80%)" offset="0" />
<stop stop-color="hsl(66, 100%, 50%)" offset="1" />
</radialGradient>
<radialGradient id="orange">
<stop stop-color="hsl(36, 100%, 60%)" offset="0" />
<stop stop-color="hsl(23, 100%, 60%)" offset="1" />
</radialGradient>
<filter id="shadow" x="-50%" y="-50%" width="200%" height="200%">
<feDropShadow dx="0" dy="0" stdDeviation="0.05"
flood-color="rgba(0, 0, 0, .5)" />
</filter>
</defs>
<!-- XXX: librsvg screws up shadows on rotated objects. -->
<g filter="url(#shadow)" transform="translate(24 3) scale(16)">
<path fill="url(#orange)" stroke="hsl(36, 100%, 20%)" stroke-width="0.1"
d="M-.8 0 H.8 V.5 H.25 V2.625 H-.25 V.5 H-.8 Z" />
</g>
<g filter="url(#shadow)" transform="translate(24 28) rotate(-45) scale(16)">
<path fill="url(#green-x)" stroke="hsl(66, 100%, 20%)" stroke-width="0.1"
d="M-.25 -1 H.25 V-.25 H1 V.25 H.25 V1 H-.25 V.25 H-1 V-.25 H-.25 Z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1734
xT/xT.cpp Normal file

File diff suppressed because it is too large Load Diff

8
xT/xT.desktop Normal file
View File

@@ -0,0 +1,8 @@
[Desktop Entry]
Type=Application
Name=xT
GenericName=IRC Client
Icon=xT
Exec=xT
StartupNotify=false
Categories=Network;Chat;IRCClient;

29
xT/xT.svg Normal file
View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1.1" width="48" height="48" viewBox="0 0 48 48"
xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="grey-x">
<stop stop-color="hsl(66, 0%, 90%)" offset="0" />
<stop stop-color="hsl(66, 0%, 80%)" offset="1" />
</radialGradient>
<radialGradient id="orange">
<stop stop-color="hsl(36, 100%, 60%)" offset="0" />
<stop stop-color="hsl(23, 100%, 60%)" offset="1" />
</radialGradient>
<filter id="shadow" x="-50%" y="-50%" width="200%" height="200%">
<feDropShadow dx="0" dy="0" stdDeviation="0.05"
flood-color="rgba(0, 0, 0, .5)" />
</filter>
</defs>
<!-- XXX: librsvg screws up shadows on rotated objects. -->
<g filter="url(#shadow)" transform="translate(24 28) rotate(-45) scale(16)">
<path fill="url(#grey-x)" stroke="hsl(66, 0%, 30%)" stroke-width="0.1"
d="M-.25 -1 H.25 V-.25 H1 V.25 H.25 V1 H-.25 V.25 H-1 V-.25 H-.25 Z" />
</g>
<g filter="url(#shadow)" transform="translate(24 3) scale(16)">
<path fill="url(#orange)" stroke="hsl(36, 100%, 20%)" stroke-width="0.1"
d="M-.8 0 H.8 V.5 H.25 V2.625 H-.25 V.5 H-.8 Z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

11
xW/.clang-format Normal file
View File

@@ -0,0 +1,11 @@
BasedOnStyle: LLVM
ColumnLimit: 80
IndentWidth: 4
TabWidth: 4
UseTab: ForContinuationAndIndentation
AlwaysBreakAfterReturnType: AllDefinitions
BreakBeforeBraces: Linux
SpaceAfterCStyleCast: true
AlignAfterOpenBracket: DontAlign
AlignOperands: DontAlign
SpacesBeforeTrailingComments: 2

85
xW/CMakeLists.txt Normal file
View File

@@ -0,0 +1,85 @@
# The last version with Windows XP support is 3.13, we want to keep that
cmake_minimum_required (VERSION 3.10)
file (READ ../xK-version project_version)
configure_file (../xK-version xK-version.tag COPYONLY)
string (STRIP "${project_version}" project_version)
# This is an entirely separate CMake project--the main executables only build
# on Windows within Cygwin, and this Windows executable only builds on Linux
# cross-compiled, so you'd want to build them independently anyway.
project (xW VERSION "${project_version}"
DESCRIPTION "Win32 frontend for xC" LANGUAGES CXX)
set (CMAKE_CXX_STANDARD 17)
add_definitions (-DUNICODE -D_UNICODE)
add_compile_options ("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options ("$<$<CXX_COMPILER_ID:GNU>:-Wall;-Wextra>")
add_compile_options ("$<$<CXX_COMPILER_ID:Clang>:-Wall;-Wextra>")
add_link_options ("$<$<CXX_COMPILER_ID:GNU>:-static;-municode>")
add_link_options ("$<$<CXX_COMPILER_ID:Clang>:-static;-municode>")
set (project_config ${PROJECT_BINARY_DIR}/config.h)
configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${project_config})
include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR})
# Produce a beep sample
if (NOT ${CMAKE_VERSION} VERSION_LESS 3.18.0)
set (find_program_REQUIRE REQUIRED)
endif ()
find_program (sox_EXECUTABLE sox ${find_program_REQUIRE})
add_custom_command (OUTPUT beep.wav
COMMAND ${sox_EXECUTABLE} -b 16 -Dr 44100 -n beep.wav
synth 0.1 0 25 triangle 800 vol 0.5 fade t 0 -0 0.005 pad 0 0.05
COMMENT "Generating a beep sample" VERBATIM)
# Rasterize SVG icons
set (root "${PROJECT_SOURCE_DIR}/..")
set (CMAKE_MODULE_PATH ${root}/liberty/cmake)
include (IconUtils)
set (icon_ico_list)
foreach (icon xW xW-highlighted)
set (icon_png_list)
foreach (icon_size 16 32 48)
icon_to_png (${icon} ${PROJECT_SOURCE_DIR}/${icon}.svg
${icon_size} ${PROJECT_BINARY_DIR}/icons icon_png)
list (APPEND icon_png_list ${icon_png})
endforeach ()
icon_to_png (${icon} ${PROJECT_SOURCE_DIR}/${icon}.svg
256 ${PROJECT_BINARY_DIR}/icons icon_png)
set (icon_ico ${PROJECT_BINARY_DIR}/${icon}.ico)
icon_for_win32 (${icon_ico} "${icon_png_list}" "${icon_png}")
list (APPEND icon_ico_list ${icon_ico})
endforeach ()
set_property (SOURCE xW.rc
APPEND PROPERTY OBJECT_DEPENDS ${icon_ico_list} beep.wav)
# Build the main executable and link it
find_program (awk_EXECUTABLE awk ${find_program_REQUIRE})
add_custom_command (OUTPUT xC-proto.cpp
COMMAND ${CMAKE_COMMAND} -E env LC_ALL=C ${awk_EXECUTABLE}
-f ${root}/liberty/tools/lxdrgen.awk
-f ${root}/liberty/tools/lxdrgen-cpp.awk
-v PrefixCamel=Relay
${root}/xC.lxdr > xC-proto.cpp
DEPENDS
${root}/liberty/tools/lxdrgen.awk
${root}/liberty/tools/lxdrgen-cpp.awk
${root}/xC.lxdr
COMMENT "Generating xC relay protocol code" VERBATIM)
add_custom_target (xC-proto DEPENDS ${PROJECT_BINARY_DIR}/xC-proto.cpp)
add_executable (xW WIN32 xW.cpp xW.rc xW.manifest ${project_config}
${root}/liberty/tools/lxdrgen-cpp-win32.cpp)
target_link_libraries (xW comctl32 ws2_32 winmm)
add_dependencies (xW xC-proto)
# At least with MinGW, this is a fully independent portable executable
install (TARGETS xW DESTINATION .)
set (CPACK_GENERATOR ZIP)
include (CPack)

14
xW/config.h.in Normal file
View File

@@ -0,0 +1,14 @@
#ifndef CONFIG_H
#define CONFIG_H
#define PROJECT_NAME "${PROJECT_NAME}"
#define PROJECT_VERSION "${project_version}"
#define PROJECT_DESCRIPTION "${PROJECT_DESCRIPTION}"
#define PROJECT_AUTHOR "Přemysl Eric Janouch"
#define PROJECT_MAJOR (${PROJECT_VERSION_MAJOR}-0)
#define PROJECT_MINOR (${PROJECT_VERSION_MINOR}-0)
#define PROJECT_PATCH (${PROJECT_VERSION_PATCH}-0)
#define PROJECT_TWEAK (${PROJECT_VERSION_TWEAK}-0)
#endif // ! CONFIG_H

24
xW/xW-highlighted.svg Normal file
View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1.1" width="48" height="48" viewBox="0 0 48 48"
xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="outer">
<rect x="-1" y="-0.15" width="5" height="3.30" />
</clipPath>
<clipPath id="inner">
<rect x="-1" y="0" width="5" height="3" />
</clipPath>
</defs>
<g transform="translate(6, 6) scale(12)" stroke-linecap="square">
<g clip-path="url(#outer)">
<path stroke="#ffffff" stroke-width="1.5" d="M 0.5,0 2.5,3" />
<path stroke="#ffffff" stroke-width="1.5" d="M 0.5,3 2.5,0" />
</g>
<g clip-path="url(#inner)">
<path stroke="#ff0000" stroke-width="0.9" d="M 0.5,0 2.5,3" />
<path stroke="#ff0000" stroke-width="0.9" d="M 0.5,3 2.5,0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 806 B

18
xW/xW-resources.h Normal file
View File

@@ -0,0 +1,18 @@
#define IDI_ICON 1
#define IDI_HIGHLIGHTED 2
#define IDR_BEEP 3
#define IDA_ACCELERATORS 10
// Named after input_add_functions() in xC.
#define ID_PREVIOUS_BUFFER 11
#define ID_NEXT_BUFFER 12
#define ID_SWITCH_BUFFER 13
#define ID_GOTO_HIGHLIGHT 14
#define ID_GOTO_ACTIVITY 15
#define ID_TOGGLE_UNIMPORTANT 16
#define ID_DISPLAY_FULL_LOG 17
#define IDD_CONNECT 20
#define IDC_STATIC 21
#define IDC_HOST 22
#define IDC_PORT 23

2035
xW/xW.cpp Normal file

File diff suppressed because it is too large Load Diff

25
xW/xW.manifest Normal file
View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity name="xW" version="1.0.0.0" type="win32" />
<dependency>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Windows.Common-Controls"
version="6.0.0.0" type="win32" processorArchitecture="*"
publicKeyToken="6595b64144ccf1df" language="*" />
</dependentAssembly>
</dependency>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
</application>
</compatibility>
</assembly>

Some files were not shown because too many files have changed in this diff Show More