dialect 3.1.0

IRC parsing library


To use this package, run the following command in your project's root directory:

Manual usage
Put the following dependency into your project's dependences section:


This package provides sub packages which can be used individually:

dialect:assertgen - Tool for generating asserts for unittest blocks

dialect Linux/macOS/Windows Linux Windows Commits since last release

IRC parsing library.

API documentation can be found here.

struct IRCEvent
{
    enum Type { ... }  // IRC event types

    Type type;
    IRCUser sender;
    IRCUser target;
    string channel;
    string subchannel;
    string content;
    string[16] aux;
    Nullable!long[16] count;
    string tags;
    uint num;
    long time;
    string raw;
    string errors;

    version(TwitchSupport)
    {
        string emotes;
        string id;
    }
}
struct IRCUser
{
    version(BotElements)
    {
        enum Class { ... }  // user roles in a channel; operator, staff, ...
        Class class_;
    }

    string nickname;
    string realName;
    string ident;
    string address;
    string account;
    long updated;

    version(TwitchSupport)
    {
        string displayName;
        string badges;
        string colour;
        uint id;
    }
}
struct IRCChannel
{
    static struct Mode { ... }

    string name;
    string topic;
    string modechars;
    Mode[] modes;
    bool[string] users;
    bool[string][char] mods;
    long created;
}
struct IRCServer
{
    enum Daemon { ... }

    Daemon daemon;
    string address;
    ushort port;

    // [...]
}
struct IRCClient
{
    string nickname;
    string user;
    string realName;
}
struct IRCParser
{
    IRCClient client;
    IRCServer server;
    this(IRCClient, IRCServer);
    @disable this(this);

    IRCEvent toIRCEvent(const string);  // <-- entry point of use
}

Available build configurations

  • library
  • twitch includes extra parsing needed to interface with Twitch servers
  • bot includes some code specifically useful for bot applications
  • twitchbot combines twitch and bot

It is pure and @safe in the library and bot configurations.

How to use

See the examples directory for a simple bot client that connects to an IRC server and joins a channel.

This project is bring-your-own-client and is not a bot framework. For that you're likely better off with the reference implementation bot and writing a plugin that suits your needs.

Longer story
  • Write a client that connects to an IRC server.
  • Create an IRCClient and configure its members. (required for context when parsing)
  • Create an IRCServer and configure its members. (it may work without but just give it at minimum a host address)
  • Create an IRCParser by passing your client and server to its constructor. Pass it between functions by ref.
  • Read a string from the server and parse it into an IRCEvent with parser.toIRCEvent(stringFromServer).
  • Switch on the IRCEvent.type member and handle the event accordingly. Remember to PONG on PING.
  • Draw the rest of the owl.
Like so
IRCClient client;
client.nickname = "...";

IRCServer server;
server.address = "...";

IRCParser parser = IRCParser(client, server);

{
    const fromServer = `:zorael!~NaN@address.tld MODE #channel +v nickname`;
    auto event = parser.toIRCEvent(fromServer);

    with (event)
    {
        assert(type == IRCEvent.Type.MODE);
        assert(sender.nickname == "zorael");
        assert(sender.ident == "~NaN");
        assert(sender.address == "address.tld");
        assert(channel == "#channel");
        assert(content == "nickname");
        assert(aux[0] == "+v");
    }
}
{
    const fromServer = ":cherryh.freenode.net 435 oldnick newnick #d :Cannot change nickname while banned on channel";
    auto event = parser.toIRCEvent(fromServer);

    with (event)
    {
        assert(type == IRCEvent.Type.ERR_BANONCHAN);
        assert(sender.address == "cherryh.freenode.net");
        assert(target.nickname == "oldnick");
        assert(channel == "#d");
        assert(content == "Cannot change nickname while banned on channel");
        assert(aux[0] == "newnick");
        assert(num == 435);
    }
}
{
    const fromServer = `@badge-info=;badges=;color=;display-name=AnAnonymousGifter;emotes=;flags=;id=01af180f-5efd-40c8-94fb-d0a346c7fg86;login=ananonymousgifter;mod=0;msg-id=subgift;msg-param-fun-string=FunStringFour;msg-param-gift-months=1;msg-param-goal-contribution-type=SUB_POINTS;msg-param-goal-current-contributions=15624;msg-param-goal-target-contributions=20000;msg-param-goal-user-contributions=1;msg-param-months=24;msg-param-origin-id=54\s41\s9a\s69\s6c\sb4\s3c\s8b\s0b\se4\sdf\s4c\sba\s5b\s9b\s23\s4c\sa7\s9b\sc4;msg-param-recipient-display-name=SomeoneOnTwitch;msg-param-recipient-id=547202201;msg-param-recipient-user-name=someoneontwitch;msg-param-sub-plan-name=Channel\sSubscription\s(some_streamer);msg-param-sub-plan=1000;room-id=4920718204;subscriber=0;system-msg=An\sanonymous\suser\sgifted\sa\sTier\s1\ssub\sto\sSomeoneOnTwitch!\s;tmi-sent-ts=1685982143345;user-id=274518607;user-type= :tmi.twitch.tv USERNOTICE #some_streamer`;
    auto event = parser.toIRCEvent(fromServer);

    with (event)
    {
        assert(type == IRCEvent.Type.TWITCH_SUBGIFT);
        assert(sender.nickname == "ananonymousgifter");
        assert(sender.address == "tmi.twitch.tv");
        assert(sender.account == "ananonymousgifter");
        assert(sender.displayName == "AnAnonymousGifter");
        assert(sender.badges == "*");
        assert(sender.id == 274518607);
        assert(target.nickname == "someoneontwitch");
        assert(target.account == "someoneontwitch");
        assert(target.displayName == "SomeoneOnTwitch");
        assert(target.id == 547202201);
        assert(channel == "#some_streamer");
        assert(content == "An anonymous user gifted a Tier 1 sub to SomeoneOnTwitch!");
        assert(aux[0] == "1000");
        assert(aux[1] == "FunStringFour");
        assert(aux[2] == "Channel Subscription (some_streamer)");
        assert(aux[5] == "SUB_POINTS");
        assert(count[0] == 1);
        assert(count[2] == 20000);
        assert(count[3] == 15624);
        assert(count[4] == 1);
        assert(tags == "badge-info=;badges=;color=;display-name=AnAnonymousGifter;emotes=;flags=;id=01af180f-5efd-40c8-94fb-d0a346c7fg86;login=ananonymousgifter;mod=0;msg-id=subgift;msg-param-fun-string=FunStringFour;msg-param-gift-months=1;msg-param-goal-contribution-type=SUB_POINTS;msg-param-goal-current-contributions=15624;msg-param-goal-target-contributions=20000;msg-param-goal-user-contributions=1;msg-param-months=24;msg-param-origin-id=54\\s41\\s9a\\s69\\s6c\\sb4\\s3c\\s8b\\s0b\\se4\\sdf\\s4c\\sba\\s5b\\s9b\\s23\\s4c\\sa7\\s9b\\sc4;msg-param-recipient-display-name=SomeoneOnTwitch;msg-param-recipient-id=547202201;msg-param-recipient-user-name=someoneontwitch;msg-param-sub-plan-name=Channel\\sSubscription\\s(some_streamer);msg-param-sub-plan=1000;room-id=4920718204;subscriber=0;system-msg=An\\sanonymous\\suser\\sgifted\\sa\\sTier\\s1\\ssub\\sto\\sSomeoneOnTwitch!\\s;tmi-sent-ts=1685982143345;user-id=274518607;user-type=");
        assert(id == "01af180f-5efd-40c8-94fb-d0a346c7fg86");
    }
}

See the tests directory for more example parses.

Unit test generation

Compiling the assertgen dub subpackage builds a command-line tool with which it is easy to generate unit test assert blocks like the ones above. These can then be pasted into an appropriate file in the tests directory, and ideally submitted as a GitHub pull request for upstream inclusion. You can use it to contribute known-good parses and increase coverage of event types.

Simply run dub run :assertgen and follow the on-screen instructions.

Enter daemon (plus optional daemon literal) [solanum]: unreal UnrealIRCd
Enter network [Libera.Chat]: foobar
Enter server address [irc.libera.chat]: irc.server.tld

// 8<  --  8<  --  8<  --  8<  --  8<  --  8<  --  8<  --  8<  --  8<

[...]

// Paste a raw event string and hit Enter to generate an assert block. Ctrl+C to exit.

:irc.server.tld PRIVMSG #channel :i am a fish

{
    enum input = `:irc.server.tld PRIVMSG #channel :i am a fish`;
    immutable event = parser.toIRCEvent(input);

    with (event)
    {
        assert(type == IRCEvent.Type.CHAN);
        assert(sender.address == "irc.server.tld");
        assert(channel == "#channel");
        assert(content == "i am a fish");
    }
}

The output will by default also be saved to an assertgen.log file in the current directory.

See the --help listing for more flags, passed through dub with dub run :assertgen -- --help.

Caveats

Starting with v3.0.0, a more recent compiler version is required. This is to allow for use of named arguments. You need a compiler based on D version 2.108 or later (April 2024). For ldc this translates to a minimum of version 1.38, while for gdc you broadly need release series 14.

If your repositories (or other software sources) don't have compilers recent enough, you can use the official install.sh installation script to download current ones, or any version of choice.

Releases of the library prior to v3.0.0 remain available for older compilers.

Note that while IRC is standardised, servers still come in many flavours, some of which outright conflict with others. Supporting conflicting event types is a challenge and requires manually adding special-casing to the parser. The groundwork for this is in place and is working (see dialect.defs.Typenums), but it is understandably not fully exhaustive. If you encounter an event that is not parsed correctly, please file an issue.

Please report bugs. Unreported bugs can only be fixed by accident.

Roadmap

  • nothing right now, ideas needed

Built with

License

This project is licensed under the Boost Software License 1.0 - see the LICENSE10.txt file for details.

Authors:
  • JR
Sub packages:
dialect:assertgen
Dependencies:
lu
Versions:
3.1.0 2025-Feb-01
3.0.1 2025-Jan-13
3.0.0 2024-Aug-11
2.2.0 2024-Jan-27
2.1.0 2024-Jan-05
Show all 49 versions
Download Stats:
  • 0 downloads today

  • 128 downloads this week

  • 349 downloads this month

  • 25456 downloads total

Score:
2.9
Short URL:
dialect.dub.pm