an experimental irc client

irc: refactor insertMessage

rockorager.dev 0757eff8 de640965

verified
+68 -64
+68 -64
src/irc.zig
··· 306 306 307 307 pub fn insertMessage(self: *Channel, msg: Message) !void { 308 308 try self.messages.append(msg); 309 - std.sort.insertion(Message, self.messages.items, {}, Message.compareTime); 310 309 if (self.scroll.msg_offset) |offset| { 311 310 self.scroll.msg_offset = offset + 1; 311 + } 312 + if (msg.timestamp_s > self.last_read) { 313 + self.has_unread = true; 314 + if (msg.containsPhrase(self.client.nickname())) { 315 + self.has_unread_highlight = true; 316 + } 312 317 } 313 318 } 314 319 ··· 1426 1431 return Command.parse(src[i..end]); 1427 1432 } 1428 1433 1434 + pub fn containsPhrase(self: Message, phrase: []const u8) bool { 1435 + switch (self.command()) { 1436 + .PRIVMSG, .NOTICE => {}, 1437 + else => return false, 1438 + } 1439 + var iter = self.paramIterator(); 1440 + // We only handle PRIVMSG and NOTICE which have syntax <target> :<content>. Skip the target 1441 + _ = iter.next() orelse return false; 1442 + 1443 + const content = iter.next() orelse return false; 1444 + return std.mem.indexOf(u8, content, phrase) != null; 1445 + } 1446 + 1429 1447 pub fn paramIterator(msg: Message) ParamIterator { 1430 1448 const src = msg.bytes; 1431 1449 var i: usize = 0; ··· 2171 2189 .PRIVMSG, .NOTICE => { 2172 2190 // syntax: <target> :<message> 2173 2191 const msg2 = Message.init(try self.app.alloc.dupe(u8, msg.bytes)); 2192 + 2193 + // We handle batches separately. When we encounter a PRIVMSG from a batch, we use 2194 + // the original target from the batch start. We also never notify from a batched 2195 + // message. Batched messages also require sorting 2196 + if (msg2.getTag("batch")) |tag| { 2197 + const entry = client.batches.getEntry(tag) orelse @panic("TODO"); 2198 + var channel = entry.value_ptr.*; 2199 + try channel.insertMessage(msg2); 2200 + std.sort.insertion(Message, channel.messages.items, {}, Message.compareTime); 2201 + channel.at_oldest = false; 2202 + return; 2203 + } 2204 + 2174 2205 var iter = msg2.paramIterator(); 2175 2206 const target = blk: { 2176 2207 const tgt = iter.next() orelse return; 2177 2208 if (mem.eql(u8, tgt, client.nickname())) { 2178 - // If the target is us, it likely has our 2179 - // hostname in it. 2180 - const source = msg2.source() orelse return; 2181 - const n = mem.indexOfScalar(u8, source, '!') orelse source.len; 2182 - break :blk source[0..n]; 2209 + // If the target is us, we use the sender nick as the identifier 2210 + break :blk msg2.senderNick() orelse unreachable; 2183 2211 } else break :blk tgt; 2184 2212 }; 2213 + // Get the channel 2214 + var channel = try client.getOrCreateChannel(target); 2215 + // Add the message to the channel. We don't need to sort because these come 2216 + // chronologically 2217 + try channel.insertMessage(msg2); 2185 2218 2186 - // We handle batches separately. When we encounter a 2187 - // PRIVMSG from a batch, we use the original target 2188 - // from the batch start. We also never notify from a 2189 - // batched message. Batched messages also require 2190 - // sorting 2191 - if (msg2.getTag("batch")) |tag| { 2192 - const entry = client.batches.getEntry(tag) orelse @panic("TODO"); 2193 - var channel = entry.value_ptr.*; 2194 - try channel.insertMessage(msg2); 2195 - channel.at_oldest = false; 2196 - if (msg2.timestamp_s > channel.last_read) { 2197 - channel.has_unread = true; 2198 - const content = iter.next() orelse return; 2199 - if (std.mem.indexOf(u8, content, client.nickname())) |_| { 2200 - channel.has_unread_highlight = true; 2201 - } 2202 - } 2203 - } else { 2204 - // standard handling 2205 - var channel = try client.getOrCreateChannel(target); 2206 - try channel.insertMessage(msg2); 2207 - const content = iter.next() orelse return; 2208 - var has_highlight = false; 2209 - { 2210 - const sender: []const u8 = blk: { 2211 - const src = msg2.source() orelse break :blk ""; 2212 - const l = std.mem.indexOfScalar(u8, src, '!') orelse 2213 - std.mem.indexOfScalar(u8, src, '@') orelse 2214 - src.len; 2215 - break :blk src[0..l]; 2216 - }; 2217 - try lua.onMessage(self.app.lua, client, channel.name, sender, content); 2218 - } 2219 - if (std.mem.indexOf(u8, content, client.nickname())) |_| { 2220 - var buf: [64]u8 = undefined; 2221 - const title_or_err = if (msg2.source()) |source| 2222 - std.fmt.bufPrint(&buf, "{s} - {s}", .{ channel.name, source }) 2223 - else 2224 - std.fmt.bufPrint(&buf, "{s}", .{channel.name}); 2225 - const title = title_or_err catch title: { 2226 - const len = @min(buf.len, channel.name.len); 2227 - @memcpy(buf[0..len], channel.name[0..len]); 2228 - break :title buf[0..len]; 2229 - }; 2230 - try ctx.sendNotification(title, content); 2231 - has_highlight = true; 2232 - } 2233 - if (msg2.timestamp_s > channel.last_read) { 2234 - channel.has_unread_highlight = has_highlight; 2235 - channel.has_unread = true; 2236 - } 2219 + // Get values for our lua callbacks 2220 + const content = iter.next() orelse return; 2221 + const sender = msg2.senderNick() orelse ""; 2237 2222 2238 - // Set the typing time to 0 2239 - const sender = msg2.senderNick() orelse ""; 2240 - for (channel.members.items) |*member| { 2241 - if (!std.mem.eql(u8, member.user.nick, sender)) { 2242 - continue; 2223 + // Do the lua callback 2224 + try lua.onMessage(self.app.lua, client, channel.name, sender, content); 2225 + 2226 + // Send a notification if this has our nick 2227 + if (msg2.containsPhrase(client.nickname())) { 2228 + var buf: [64]u8 = undefined; 2229 + const title_or_err = if (sender.len > 0) 2230 + std.fmt.bufPrint(&buf, "{s} - {s}", .{ channel.name, sender }) 2231 + else 2232 + std.fmt.bufPrint(&buf, "{s}", .{channel.name}); 2233 + const title = title_or_err catch title: { 2234 + const len = @min(buf.len, channel.name.len); 2235 + @memcpy(buf[0..len], channel.name[0..len]); 2236 + break :title buf[0..len]; 2237 + }; 2238 + try ctx.sendNotification(title, content); 2239 + 2240 + if (client.caps.@"message-tags") { 2241 + // Set the typing time to 0. We only need to do this when the server 2242 + // supports message-tags 2243 + for (channel.members.items) |*member| { 2244 + if (!std.mem.eql(u8, member.user.nick, sender)) { 2245 + continue; 2246 + } 2247 + member.typing = 0; 2248 + return; 2243 2249 } 2244 - member.typing = 0; 2245 - return; 2246 2250 } 2247 2251 } 2248 2252 },