Public Access
M1 step 4a: IRC protocol core (host-tested)
lib/irc: IrcMessage parse/serialize, base64, ReconnectPolicy (5 s .. 5 min), IrcConfig (validated, persisted) and IrcSession: registration with SASL PLAIN or NickServ, nick fallback, PING, Buffers capped at 50 lines with unread counts, Mentions, CTCP ACTION/VERSION, topics, /names on request only, others' join/part/quit hidden, user commands, and rejoining after a pause. No networking: the session returns lines to send, Log entries and Notifications. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
#include "irc_config.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
|
||||
namespace roro {
|
||||
|
||||
namespace {
|
||||
bool validNick(const std::string& n) {
|
||||
if (n.empty() || n.size() > 30) return false;
|
||||
if (std::isdigit(static_cast<unsigned char>(n[0])) || n[0] == '-') return false;
|
||||
for (char c : n)
|
||||
if (!std::isalnum(static_cast<unsigned char>(c)) && !std::strchr("[]\\`_^{|}-", c)) return false;
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void IrcConfig::load(const std::string& defaultNick) {
|
||||
nick = defaultNick;
|
||||
std::string s;
|
||||
int32_t i;
|
||||
if (store_.getString("irc_host", s)) host = s;
|
||||
if (store_.getInt("irc_port", i)) port = i;
|
||||
if (store_.getInt("irc_tls", i)) tls = i != 0;
|
||||
if (store_.getInt("irc_selfsign", i)) allowSelfSigned = i != 0;
|
||||
store_.getString("irc_pin", pinnedSha256);
|
||||
if (store_.getString("irc_nick", s)) nick = s;
|
||||
store_.getString("irc_sasl_user", saslUser);
|
||||
store_.getString("irc_sasl_pass", saslPassword);
|
||||
store_.getString("irc_ns_pass", nickservPassword);
|
||||
if (store_.getString("irc_join", s)) autojoin = parseChannels(s);
|
||||
}
|
||||
|
||||
std::string IrcConfig::validate() const {
|
||||
if (host.empty() || host.size() > 63) return "Server must be 1 to 63 characters";
|
||||
if (port < 1 || port > 65535) return "Port must be 1 to 65535";
|
||||
if (!validNick(nick)) return "Nick: letters, digits and []\\`_^{|}- only, not starting with a digit";
|
||||
for (auto& c : autojoin)
|
||||
if (c.size() < 2 || (c[0] != '#' && c[0] != '&')) return "IRC channels start with # or &";
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string IrcConfig::save() {
|
||||
std::string error = validate();
|
||||
if (!error.empty()) return error;
|
||||
store_.putString("irc_host", host);
|
||||
store_.putInt("irc_port", port);
|
||||
store_.putInt("irc_tls", tls ? 1 : 0);
|
||||
store_.putInt("irc_selfsign", allowSelfSigned ? 1 : 0);
|
||||
store_.putString("irc_pin", pinnedSha256);
|
||||
store_.putString("irc_nick", nick);
|
||||
store_.putString("irc_sasl_user", saslUser);
|
||||
store_.putString("irc_sasl_pass", saslPassword);
|
||||
store_.putString("irc_ns_pass", nickservPassword);
|
||||
store_.putString("irc_join", formatChannels(autojoin));
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<std::string> IrcConfig::parseChannels(const std::string& text) {
|
||||
std::vector<std::string> out;
|
||||
std::string current;
|
||||
for (char c : text + " ") {
|
||||
if (c == ' ' || c == ',') {
|
||||
if (!current.empty()) out.push_back(current);
|
||||
current.clear();
|
||||
} else {
|
||||
current += c;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string IrcConfig::formatChannels(const std::vector<std::string>& channels) {
|
||||
std::string out;
|
||||
for (auto& c : channels) out += (out.empty() ? "" : " ") + c;
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace roro
|
||||
Reference in New Issue
Block a user