diff --git a/examples/d3d/d3d-msvc.visualdproj b/examples/d3d/d3d-msvc.visualdproj index 985d53d8..a80d15b0 100644 --- a/examples/d3d/d3d-msvc.visualdproj +++ b/examples/d3d/d3d-msvc.visualdproj @@ -409,6 +409,12 @@ *.obj;*.cmd;*.build;*.json;*.dep + + + + + + diff --git a/examples/ircclient/src/ircclient/net/client.d b/examples/ircclient/src/ircclient/net/client.d index 1c06386b..b9ec9d5c 100644 --- a/examples/ircclient/src/ircclient/net/client.d +++ b/examples/ircclient/src/ircclient/net/client.d @@ -2,8 +2,10 @@ module ircclient.net.client; public import dlangui.core.asyncsocket; import dlangui.core.logger; -import std.string : empty, format; +import dlangui.core.collections; +import std.string : empty, format, startsWith; import std.conv : to; +import std.utf : toUTF32, toUTF8; interface IRCClientCallback { void onIRCConnect(IRCClient client); @@ -16,6 +18,10 @@ interface IRCClientCallback { enum IRCCommand : int { UNKNOWN, + CHANNEL_TOPIC = 332, + CHANNEL_TOPIC_SET_BY = 333, + CHANNEL_NAMES_LIST = 353, + CHANNEL_NAMES_LIST_END = 366, USER = 1000, PRIVMSG, // :source PRIVMSG :Message NOTICE, // :source NOTICE :Message @@ -101,6 +107,16 @@ class IRCMessage { if (params.length > 0) target = params[0]; break; + case CHANNEL_TOPIC: + case CHANNEL_TOPIC_SET_BY: + case CHANNEL_NAMES_LIST_END: + if (params.length > 1) + target = params[1]; + break; + case CHANNEL_NAMES_LIST: + if (params.length > 2 && params[1] == "=") + target = params[2]; + break; default: break; } @@ -114,6 +130,8 @@ class IRCAddress { string channel; string nick; string username; + this() { + } this(string s) { full = s; string s1 = parseDelimitedParameter(s, '!'); @@ -134,6 +152,75 @@ class IRCAddress { } } +class IRCUser { + string nick; + int flags; + this(string s) { + nick = s; + } +} + +class UserList { + Collection!IRCUser _users; + @property int length() { return _users.length; } + @property IRCUser opIndex(int index) { return _users[index]; } + void fromList(string userList) { + _users.clear(); + for(;;) { + string s = parseDelimitedParameter(userList); + if (s.empty) + break; + IRCUser u = new IRCUser(s); + _users.add(u); + } + } +} + +class IRCChannel { + string name; + string topic; + string topicSetBy; + long topicSetWhen; + UserList users; + this(string name) { + this.name = name; + users = new UserList(); + } + char[] userListBuffer; + void setUserList(string userList) { + users.fromList(userList); + } + @property dstring[] userNames() { + dstring[] res; + for(int i = 0; i < users.length; i++) { + res ~= toUTF32(users[i].nick); + } + return res; + } + void handleMessage(IRCMessage msg) { + switch (msg.commandId) with (IRCCommand) { + case CHANNEL_TOPIC: + topic = msg.message; + break; + case CHANNEL_TOPIC_SET_BY: + topicSetBy = msg.params.length == 3 ? msg.params[1] : null; + break; + case CHANNEL_NAMES_LIST: + if (userListBuffer.length > 0) + userListBuffer ~= " "; + userListBuffer ~= msg.message; + break; + case CHANNEL_NAMES_LIST_END: + setUserList(userListBuffer.dup); + Log.d("user list for " ~ name ~ " : " ~ userListBuffer); + userListBuffer = null; + break; + default: + break; + } + } +} + /// IRC Client connection implementation class IRCClient : AsyncSocketCallback { protected: @@ -143,6 +230,7 @@ protected: string _host; ushort _port; string _nick; + IRCChannel[string] _channels; void onDataReceived(AsyncSocket socket, ubyte[] data) { _readbuf ~= cast(char[])data; // split by lines @@ -182,6 +270,16 @@ protected: case NOTICE: _callback.onIRCNotice(this, msg.sourceAddress, msg.target, msg.message); break; + case CHANNEL_TOPIC: + case CHANNEL_TOPIC_SET_BY: + case CHANNEL_NAMES_LIST: + case CHANNEL_NAMES_LIST_END: + if (msg.target.startsWith("#")) { + auto channel = channelByName(msg.target, true); + channel.handleMessage(msg); + _callback.onIRCMessage(this, msg); + } + break; default: _callback.onIRCMessage(this, msg); break; @@ -206,6 +304,23 @@ public: if (_socket) destroy(_socket); } + IRCChannel removeChannel(string name) { + if (auto p = name in _channels) { + _channels.remove(name); + return *p; + } + return null; + } + IRCChannel channelByName(string name, bool createIfNotExist = false) { + if (auto p = name in _channels) { + return *p; + } + if (!createIfNotExist) + return null; + IRCChannel res = new IRCChannel(name); + _channels[name] = res; + return res; + } @property string host() { return _host; } @property ushort port() { return _port; } @property string hostPort() { return "%s:%d".format(_host, _port); } diff --git a/examples/ircclient/src/main.d b/examples/ircclient/src/main.d index f712739b..aaa8694b 100644 --- a/examples/ircclient/src/main.d +++ b/examples/ircclient/src/main.d @@ -207,21 +207,32 @@ class IRCFrame : AppFrame, IRCClientCallback { void onIRCMessage(IRCClient client, IRCMessage message) { IRCWindow w = getOrCreateWindowFor(client.hostPort); - if (message.commandId < 1000) { - // custom server messages - w.addLine(message.message); - return; - } - if (message.commandId == IRCCommand.JOIN || message.commandId == IRCCommand.PART) { - if (message.sourceAddress && !message.sourceAddress.nick.empty && message.target.startsWith("#")) { - w = getOrCreateWindowFor(message.target); - if (message.commandId == IRCCommand.JOIN) { - w.addLine("* " ~ message.sourceAddress.longName ~ " has joined " ~ message.target); - } else { - w.addLine("* " ~ message.sourceAddress.longName ~ " has left " ~ message.target ~ (message.message.empty ? "" : ("(Reason: " ~ message.message ~ ")"))); + switch (message.commandId) with (IRCCommand) { + case JOIN: + case PART: + if (message.sourceAddress && !message.sourceAddress.nick.empty && message.target.startsWith("#")) { + w = getOrCreateWindowFor(message.target); + if (message.commandId == JOIN) { + w.addLine("* " ~ message.sourceAddress.longName ~ " has joined " ~ message.target); + } else { + w.addLine("* " ~ message.sourceAddress.longName ~ " has left " ~ message.target ~ (message.message.empty ? "" : ("(Reason: " ~ message.message ~ ")"))); + } } - } - return; + return; + case CHANNEL_NAMES_LIST_END: + if (message.target.startsWith("#")) { + w = getOrCreateWindowFor(message.target); + IRCChannel channel = _client.channelByName(message.target); + w.updateUserList(channel); + } + return; + default: + if (message.commandId < 1000) { + // custom server messages + w.addLine(message.message); + return; + } + break; } w.addLine(message.msg); } @@ -258,7 +269,7 @@ class IRCWindow : VerticalLayout, EditorActionHandler { _listBox.minWidth = 100; _listBox.maxWidth = 200; _listBox.orientation = Orientation.Vertical; - _listBox.items = ["Nick1"d, "Nick2"d]; + //_listBox.items = ["Nick1"d, "Nick2"d]; hlayout.addChild(new ResizerWidget(null, Orientation.Horizontal)); hlayout.addChild(_listBox); _kind = IRCWindowKind.Channel; @@ -278,6 +289,10 @@ class IRCWindow : VerticalLayout, EditorActionHandler { if (visible) window.update(); } + void updateUserList(IRCChannel channel) { + _listBox.items = channel.userNames; + window.update(); + } bool onEditorAction(const Action action) { if (!_editLine.text.empty) { string s = toUTF8(_editLine.text); @@ -288,8 +303,8 @@ class IRCWindow : VerticalLayout, EditorActionHandler { string cmd = parseDelimitedParameter(s); if (cmd == "/quit") { - _client.quit(param); - return; + _client.quit(s); + return true; } string param = parseDelimitedParameter(s); @@ -303,6 +318,7 @@ class IRCWindow : VerticalLayout, EditorActionHandler { _client.privMsg(param, s); } else { Log.d("Unknown command: " ~ cmd); + addLine("Supported commands: /nick /join /part /msg /quit"); } } else { // message