1333 lines
31 KiB
C++
1333 lines
31 KiB
C++
/*
|
||
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
|
||
* All rights reserved. Distributed under the terms of the MIT license.
|
||
*/
|
||
|
||
#include "IrcProtocol.h"
|
||
|
||
#include <iostream>
|
||
|
||
#include <Catalog.h>
|
||
#include <Directory.h>
|
||
#include <FindDirectory.h>
|
||
#include <Font.h>
|
||
#include <Resources.h>
|
||
#include <SecureSocket.h>
|
||
#include <Socket.h>
|
||
|
||
#include <libinterface/BitmapUtils.h>
|
||
|
||
#include <AppConstants.h>
|
||
#include <Cardie.h>
|
||
#include <ChatProtocolMessages.h>
|
||
#include <Flags.h>
|
||
|
||
|
||
#undef B_TRANSLATION_CONTEXT
|
||
#define B_TRANSLATION_CONTEXT "IrcProtocol"
|
||
|
||
|
||
const int32 IRC_CMD = 'ICmd';
|
||
|
||
|
||
status_t
|
||
connect_thread(void* data)
|
||
{
|
||
IrcProtocol* protocol = (IrcProtocol*)data;
|
||
protocol->Connect();
|
||
status_t status = protocol->Loop();
|
||
exit(status);
|
||
}
|
||
|
||
|
||
IrcProtocol::IrcProtocol()
|
||
:
|
||
fSocket(NULL),
|
||
fNick(NULL),
|
||
fIdent(NULL),
|
||
fReady(false),
|
||
fWriteLocked(false)
|
||
{
|
||
}
|
||
|
||
|
||
IrcProtocol::~IrcProtocol()
|
||
{
|
||
Shutdown();
|
||
}
|
||
|
||
|
||
status_t
|
||
IrcProtocol::Init(ChatProtocolMessengerInterface* interface)
|
||
{
|
||
fMessenger = interface;
|
||
return B_OK;
|
||
}
|
||
|
||
|
||
status_t
|
||
IrcProtocol::Shutdown()
|
||
{
|
||
_SaveContacts();
|
||
|
||
BString cmd = "QUIT :";
|
||
cmd << fPartText;
|
||
_SendIrc(cmd);
|
||
|
||
kill_thread(fRecvThread);
|
||
return B_OK;
|
||
}
|
||
|
||
|
||
status_t
|
||
IrcProtocol::UpdateSettings(BMessage* settings)
|
||
{
|
||
fNick = settings->FindString("nick");
|
||
fPartText = settings->GetString("part", "Cardie[0.1]: i've been liquified!");
|
||
fUser = settings->FindString("ident");
|
||
fRealName = settings->FindString("real_name");
|
||
fServer = settings->FindString("server");
|
||
fPassword = settings->FindString("password");
|
||
fPort = settings->FindInt32("port");
|
||
fSsl = settings->GetBool("ssl", false);
|
||
|
||
fRecvThread = spawn_thread(connect_thread, "what_a_tangled_web_we_weave",
|
||
B_NORMAL_PRIORITY, (void*)this);
|
||
|
||
if (fRecvThread < B_OK)
|
||
return B_ERROR;
|
||
return B_OK;
|
||
}
|
||
|
||
|
||
status_t
|
||
IrcProtocol::Process(BMessage* msg)
|
||
{
|
||
int32 im_what = msg->FindInt32("im_what");
|
||
switch (im_what) {
|
||
case IRC_CMD:
|
||
{
|
||
BStringList words;
|
||
BString line = msg->GetString("misc_str", "");
|
||
line.Split(" ", true, words);
|
||
|
||
BString command = words.First();
|
||
if (command.ICompare("WHOIS") == 0)
|
||
fWhoIsRequested = true;
|
||
else if (command.ICompare("WHO") == 0)
|
||
fWhoRequested = true;
|
||
|
||
_SendIrc(line);
|
||
break;
|
||
}
|
||
case IM_SET_OWN_STATUS:
|
||
{
|
||
int32 status = msg->FindInt32("status");
|
||
BString status_msg = msg->FindString("message");
|
||
|
||
BMessage statusSet(IM_MESSAGE);
|
||
statusSet.AddInt32("im_what", IM_OWN_STATUS_SET);
|
||
|
||
switch (status) {
|
||
case STATUS_ONLINE:
|
||
statusSet.AddInt32("status", STATUS_ONLINE);
|
||
resume_thread(fRecvThread);
|
||
break;
|
||
case STATUS_OFFLINE:
|
||
statusSet.AddInt32("status", STATUS_OFFLINE);
|
||
Shutdown();
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
if (status == STATUS_ONLINE || status == STATUS_OFFLINE)
|
||
_SendMsg(&statusSet);
|
||
break;
|
||
}
|
||
case IM_SEND_MESSAGE:
|
||
{
|
||
BString chat_id = msg->FindString("chat_id");
|
||
BString body = msg->FindString("body");
|
||
if (chat_id.IsEmpty() == false || body.IsEmpty() == false) {
|
||
BStringList lines;
|
||
body.Split("\n", true, lines);
|
||
|
||
for (int i = 0; i < lines.CountStrings(); i++) {
|
||
BString cmd = "PRIVMSG ";
|
||
cmd << chat_id << " :" << lines.StringAt(i);
|
||
_SendIrc(cmd);
|
||
|
||
BMessage sent(IM_MESSAGE);
|
||
sent.AddInt32("im_what", IM_MESSAGE_SENT);
|
||
sent.AddString("user_id", fIdent);
|
||
sent.AddString("chat_id", chat_id);
|
||
sent.AddString("body", lines.StringAt(i));
|
||
_SendMsg(&sent);
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
case IM_CREATE_CHAT:
|
||
{
|
||
BString user_id;
|
||
if (msg->FindString("user_id", &user_id) != B_OK)
|
||
break;
|
||
BString user_name = _IdentNick(user_id);
|
||
|
||
if (user_name != user_id) {
|
||
BMessage created(IM_MESSAGE);
|
||
created.AddInt32("im_what", IM_CHAT_CREATED);
|
||
created.AddString("chat_id", user_name);
|
||
created.AddString("user_id", user_id);
|
||
_SendMsg(&created);
|
||
fChannels.Add(user_name);
|
||
break;
|
||
}
|
||
// If it's not a known user, we need to get their ID/nick somehow
|
||
// … that is, through the WHO.
|
||
fWhoIm = user_id;
|
||
BString cmd("WHOIS ");
|
||
cmd << user_id << "\n";
|
||
_SendIrc(cmd);
|
||
break;
|
||
|
||
}
|
||
case IM_JOIN_ROOM:
|
||
case IM_CREATE_ROOM:
|
||
case IM_ROOM_INVITE_ACCEPT:
|
||
{
|
||
BString chat_id;
|
||
if (msg->FindString("chat_id", &chat_id) == B_OK) {
|
||
BString cmd = "JOIN ";
|
||
cmd << chat_id;
|
||
_SendIrc(cmd);
|
||
}
|
||
break;
|
||
}
|
||
case IM_LEAVE_ROOM:
|
||
{
|
||
BString chat_id;
|
||
if (msg->FindString("chat_id", &chat_id) == B_OK) {
|
||
if (_IsChannelName(chat_id) == true) {
|
||
BString cmd = "PART ";
|
||
cmd << chat_id << " * :" << fPartText;
|
||
_SendIrc(cmd);
|
||
}
|
||
else {
|
||
BMessage left(IM_MESSAGE);
|
||
left.AddInt32("im_what", IM_ROOM_LEFT);
|
||
left.AddString("chat_id", chat_id);
|
||
_SendMsg(&left);
|
||
fChannels.Remove(chat_id);
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
case IM_GET_ROOM_METADATA:
|
||
{
|
||
BString chat_id;
|
||
if (msg->FindString("chat_id", &chat_id) == B_OK) {
|
||
BMessage meta(IM_MESSAGE);
|
||
meta.AddInt32("im_what", IM_ROOM_METADATA);
|
||
meta.AddString("chat_id", chat_id);
|
||
meta.AddInt32("room_default_flags",
|
||
ROOM_LOG_LOCALLY | ROOM_POPULATE_LOGS);
|
||
_SendMsg(&meta);
|
||
}
|
||
break;
|
||
}
|
||
case IM_GET_ROOM_PARTICIPANTS:
|
||
{
|
||
BString chat_id;
|
||
if (msg->FindString("chat_id", &chat_id) != B_OK)
|
||
break;
|
||
|
||
// Rooms are populated with RPL_WHOREPLY, chats RPL_WHOISUSER
|
||
BString cmd;
|
||
if (_IsChannelName(chat_id) == true)
|
||
cmd = "WHO ";
|
||
else
|
||
cmd = "WHOIS ";
|
||
cmd << chat_id << "\n";
|
||
_SendIrc(cmd);
|
||
break;
|
||
}
|
||
case IM_ROOM_BAN_PARTICIPANT:
|
||
case IM_ROOM_KICK_PARTICIPANT:
|
||
{
|
||
BString chat_id = msg->FindString("chat_id");
|
||
BString user_id = msg->FindString("user_id");
|
||
BString body;
|
||
|
||
if (chat_id.IsEmpty() == true || user_id.IsEmpty() == true)
|
||
break;
|
||
|
||
if (im_what == IM_ROOM_BAN_PARTICIPANT) {
|
||
BString cmd("MODE ");
|
||
cmd << chat_id << " +b " << _IdentNick(user_id) << "!*@*";
|
||
_SendIrc(cmd);
|
||
}
|
||
|
||
if (msg->FindString("body", &body) != B_OK
|
||
&& im_what == IM_ROOM_BAN_PARTICIPANT)
|
||
body = B_TRANSLATE("You've been banned, nerd");
|
||
else if (body.IsEmpty() == true)
|
||
body = B_TRANSLATE("Watch the door on your way out");
|
||
|
||
BString cmd("KICK ");
|
||
cmd << chat_id << " " << _IdentNick(user_id);
|
||
cmd << " :" << body;
|
||
_SendIrc(cmd);
|
||
break;
|
||
}
|
||
case IM_ROOM_UNBAN_PARTICIPANT:
|
||
{
|
||
BString chat_id = msg->FindString("chat_id");
|
||
BString user_id = msg->FindString("user_id");
|
||
|
||
if (chat_id.IsEmpty() == false && user_id.IsEmpty() == false) {
|
||
BString cmd("MODE ");
|
||
cmd << chat_id << " -b " << _IdentNick(user_id) << "!*@*";
|
||
_SendIrc(cmd);
|
||
}
|
||
break;
|
||
}
|
||
case IM_ROOM_SEND_INVITE:
|
||
{
|
||
BString chat_id = msg->FindString("chat_id");
|
||
BString user_id = msg->FindString("user_id");
|
||
if (chat_id.IsEmpty() == false && user_id.IsEmpty() == false) {
|
||
BString cmd("INVITE ");
|
||
cmd << _IdentNick(user_id) << " " << chat_id;
|
||
_SendIrc(cmd);
|
||
}
|
||
break;
|
||
}
|
||
case IM_SET_OWN_NICKNAME:
|
||
{
|
||
BString user_name;
|
||
if (msg->FindString("user_name", &user_name) == B_OK) {
|
||
BString cmd("NICK ");
|
||
cmd << user_name;
|
||
_SendIrc(cmd);
|
||
}
|
||
break;
|
||
}
|
||
case IM_ROSTER_ADD_CONTACT:
|
||
{
|
||
BString user_nick;
|
||
if (msg->FindString("user_id", &user_nick) == B_OK)
|
||
_AddContact(user_nick);
|
||
break;
|
||
}
|
||
case IM_ROSTER_REMOVE_CONTACT:
|
||
{
|
||
BString user_id;
|
||
if (msg->FindString("user_id", &user_id) == B_OK)
|
||
_RemoveContact(_NickIdent(user_id));
|
||
break;
|
||
}
|
||
case IM_GET_CONTACT_INFO:
|
||
case IM_GET_EXTENDED_CONTACT_INFO:
|
||
{
|
||
BString user_id;
|
||
if (msg->FindString("user_id", &user_id) != B_OK)
|
||
break;
|
||
|
||
BMessage info(IM_MESSAGE);
|
||
if (im_what == IM_GET_CONTACT_INFO)
|
||
info.AddInt32("im_what", IM_CONTACT_INFO);
|
||
else
|
||
info.AddInt32("im_what", IM_EXTENDED_CONTACT_INFO);
|
||
info.AddString("user_id", user_id);
|
||
info.AddString("user_name", _IdentNick(user_id));
|
||
if (fOfflineContacts.HasString(_IdentNick(user_id)) == true)
|
||
info.AddInt32("status", (int32)STATUS_OFFLINE);
|
||
else
|
||
info.AddInt32("status", (int32)STATUS_ONLINE);
|
||
_SendMsg(&info);
|
||
break;
|
||
}
|
||
default:
|
||
std::cout << "Unhandled message for IRC:\n";
|
||
msg->PrintToStream();
|
||
return B_ERROR;
|
||
}
|
||
return B_OK;
|
||
}
|
||
|
||
|
||
BMessage
|
||
IrcProtocol::SettingsTemplate(const char* name)
|
||
{
|
||
BMessage settings;
|
||
if (strcmp(name, "account") == 0)
|
||
settings = _AccountTemplate();
|
||
else if (strcmp(name, "join_room") == 0 || strcmp(name, "create_room") == 0)
|
||
settings = _RoomTemplate();
|
||
else if (strcmp(name, "roster") == 0)
|
||
settings = _RosterTemplate();
|
||
return settings;
|
||
}
|
||
|
||
|
||
BObjectList<BMessage>
|
||
IrcProtocol::Commands()
|
||
{
|
||
BMessage handle(IM_MESSAGE);
|
||
handle.AddInt32("im_what", IRC_CMD);
|
||
handle.AddString("cmd_name", "/");
|
||
|
||
BMessage* cmd = new BMessage();
|
||
cmd->AddString("_name", "/");
|
||
cmd->AddString("_desc", B_TRANSLATE("Send a raw IRC command to the server. E.g., '// HELP' or '// WHOIS'."));
|
||
cmd->AddBool("_proto", true);
|
||
cmd->AddMessage("_msg", &handle);
|
||
cmd->AddString("class", "ChatCommand");
|
||
|
||
BObjectList<BMessage> cmds;
|
||
cmds.AddItem(cmd);
|
||
return cmds;
|
||
}
|
||
|
||
|
||
BBitmap*
|
||
IrcProtocol::Icon() const
|
||
{
|
||
return ReadNodeIcon(fAddOnPath.Path(), B_LARGE_ICON, true);
|
||
}
|
||
|
||
|
||
status_t
|
||
IrcProtocol::Connect()
|
||
{
|
||
fSocket = fSsl ? new BSecureSocket : new BSocket;
|
||
|
||
if (fSocket->Connect(BNetworkAddress(fServer, fPort)) != B_OK)
|
||
return B_ERROR;
|
||
|
||
if (fPassword.IsEmpty() == false) {
|
||
BString passMsg = "PASS ";
|
||
passMsg << fPassword;
|
||
_SendIrc(passMsg);
|
||
}
|
||
|
||
BString userMsg = "USER ";
|
||
userMsg << fUser << " * 0 :" << fRealName;
|
||
_SendIrc(userMsg);
|
||
|
||
BString nickMsg = "NICK ";
|
||
nickMsg << fNick;
|
||
_SendIrc(nickMsg);
|
||
return B_OK;
|
||
}
|
||
|
||
|
||
status_t
|
||
IrcProtocol::Loop()
|
||
{
|
||
fWhoIsRequested = false;
|
||
fWhoRequested = false;
|
||
while (fSocket != NULL && fSocket->IsConnected() == true)
|
||
_ProcessLine(_ReadUntilNewline(fSocket, &fRemainingBuf));
|
||
return B_OK;
|
||
}
|
||
|
||
|
||
void
|
||
IrcProtocol::_ProcessLine(BString line)
|
||
{
|
||
BStringList words;
|
||
line.RemoveCharsSet("\n\r");
|
||
line.Split(" ", true, words);
|
||
BString sender = _LineSender(words);
|
||
BString code = _LineCode(words);
|
||
BStringList params = _LineParameters(words, line);
|
||
|
||
int32 numeric;
|
||
if ((numeric = atoi(code.String())) > 0)
|
||
_ProcessNumeric(numeric, sender, params, line);
|
||
else
|
||
_ProcessCommand(code, sender, params, line);
|
||
}
|
||
|
||
|
||
void
|
||
IrcProtocol::_ProcessNumeric(int32 numeric, BString sender, BStringList params,
|
||
BString line)
|
||
{
|
||
if (numeric > 400) {
|
||
_ProcessNumericError(numeric, sender, params, line);
|
||
return;
|
||
}
|
||
|
||
// Act on the numeric as appropriate
|
||
switch (numeric) {
|
||
case RPL_WELCOME:
|
||
{
|
||
if (params.CountStrings() == 2)
|
||
fNick = params.First();
|
||
BString cmd("WHOIS ");
|
||
cmd << fNick << "\n";
|
||
_SendIrc(cmd);
|
||
break;
|
||
}
|
||
case RPL_WHOISUSER:
|
||
{
|
||
BString nick = params.StringAt(1);
|
||
BString user = params.StringAt(2);
|
||
BString host = params.StringAt(3);
|
||
BString ident = user;
|
||
ident << "@" << host;
|
||
|
||
fIdentNicks.RemoveItemFor(ident);
|
||
fIdentNicks.AddItem(ident, nick);
|
||
|
||
// If is a contact, let's go!
|
||
_UpdateContact(nick, ident, true);
|
||
|
||
// Contains the own user's contact info― protocol ready!
|
||
if (fReady == false && nick == fNick) {
|
||
fUser = user.String();
|
||
_MakeReady(nick, ident);
|
||
}
|
||
// Used in the creation of a one-on-one chat
|
||
else if (fWhoIm == user || fWhoIm == nick) {
|
||
fWhoIm = "";
|
||
BMessage created(IM_MESSAGE);
|
||
created.AddInt32("im_what", IM_CHAT_CREATED);
|
||
created.AddString("chat_id", nick);
|
||
created.AddString("user_id", ident);
|
||
_SendMsg(&created);
|
||
fChannels.Add(nick);
|
||
}
|
||
// Used to populate a one-on-one chat's userlist… lol, I know.
|
||
else if (fWhoIsRequested == false && fChannels.HasString(nick)) {
|
||
BMessage user(IM_MESSAGE);
|
||
user.AddInt32("im_what", IM_ROOM_PARTICIPANTS);
|
||
user.AddString("chat_id", nick);
|
||
user.AddString("user_id", ident);
|
||
user.AddString("user_name", nick);
|
||
_SendMsg(&user);
|
||
}
|
||
break;
|
||
}
|
||
case RPL_WHOREPLY:
|
||
{
|
||
BString channel = params.StringAt(1);
|
||
BString user = params.StringAt(2);
|
||
BString host = params.StringAt(3);
|
||
BString nick = params.StringAt(5);
|
||
BString role = params.StringAt(6);
|
||
BString ident = user;
|
||
ident << "@" << host;
|
||
|
||
fIdentNicks.RemoveItemFor(ident);
|
||
fIdentNicks.AddItem(ident, nick);
|
||
|
||
// Used to populate a room's userlist (one-by-one… :p)
|
||
if (fWhoRequested == false && _IsChannelName(channel)) {
|
||
// Send the participant themself
|
||
BMessage user(IM_MESSAGE);
|
||
user.AddInt32("im_what", IM_ROOM_PARTICIPANTS);
|
||
user.AddString("chat_id", channel);
|
||
user.AddString("user_id", ident);
|
||
user.AddString("user_name", nick);
|
||
_SendMsg(&user);
|
||
|
||
// Now let's crunch the appropriate role…
|
||
bool away = false;
|
||
UserRole priority = ROOM_MEMBER;
|
||
for (int i=0; i < role.CountBytes(0, role.CountChars()); i++) {
|
||
char c = role.ByteAt(i);
|
||
switch (c) {
|
||
case 'G':
|
||
case 'H':
|
||
away = false;
|
||
break;
|
||
case 'A':
|
||
away = true;
|
||
break;
|
||
case '%':
|
||
priority = ROOM_HALFOP;
|
||
break;
|
||
case '@':
|
||
priority = ROOM_OPERATOR;
|
||
break;
|
||
case '*':
|
||
priority = IRC_OPERATOR;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// And send the user's role
|
||
BMessage sensei(IM_MESSAGE);
|
||
sensei.AddInt32("im_what", IM_ROOM_ROLECHANGED);
|
||
sensei.AddString("chat_id", channel);
|
||
sensei.AddString("user_id", ident);
|
||
sensei.AddInt32("role_priority", priority);
|
||
sensei.AddString("role_title", _RoleTitle(priority));
|
||
sensei.AddInt32("role_perms", _RolePerms(priority));
|
||
_SendMsg(&sensei);
|
||
|
||
// Also status! Can't forget that
|
||
BMessage status(IM_MESSAGE);
|
||
status.AddInt32("im_what", IM_USER_STATUS_SET);
|
||
status.AddString("user_id", ident);
|
||
if (away == true)
|
||
status.AddInt32("status", STATUS_AWAY);
|
||
else
|
||
status.AddInt32("status", STATUS_ONLINE);
|
||
_SendMsg(&status);
|
||
}
|
||
break;
|
||
}
|
||
case RPL_TOPIC:
|
||
{
|
||
BString chat_id = params.StringAt(1);
|
||
BString subject = params.Last();
|
||
|
||
BMessage topic(IM_MESSAGE);
|
||
topic.AddInt32("im_what", IM_ROOM_SUBJECT_SET);
|
||
topic.AddString("subject", subject);
|
||
topic.AddString("chat_id", chat_id);
|
||
_SendMsg(&topic);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Now, to determine if the line should be sent to system buffer
|
||
switch (numeric) {
|
||
case RPL_ENDOFWHO:
|
||
fWhoRequested = false;
|
||
break;
|
||
case RPL_ENDOFWHOIS:
|
||
fWhoIsRequested = false;
|
||
break;
|
||
case RPL_MOTDSTART:
|
||
case RPL_MOTD:
|
||
case RPL_ENDOFMOTD:
|
||
{
|
||
BString body = params.Last();
|
||
if (numeric == RPL_MOTDSTART)
|
||
body = "――MOTD start――";
|
||
else if (numeric == RPL_ENDOFMOTD)
|
||
body = "――MOTD end――";
|
||
BMessage send(IM_MESSAGE);
|
||
send.AddInt32("im_what", IM_MESSAGE_RECEIVED);
|
||
send.AddString("body", body);
|
||
_SendMsg(&send);
|
||
break;
|
||
}
|
||
case RPL_WHOREPLY:
|
||
if (fWhoRequested == false)
|
||
break;
|
||
case RPL_WHOISUSER:
|
||
if (fWhoIsRequested == false)
|
||
break;
|
||
default:
|
||
BMessage send(IM_MESSAGE);
|
||
send.AddInt32("im_what", IM_MESSAGE_RECEIVED);
|
||
send.AddString("body", line);
|
||
_SendMsg(&send);
|
||
}
|
||
}
|
||
|
||
|
||
void
|
||
IrcProtocol::_ProcessNumericError(int32 numeric, BString sender,
|
||
BStringList params, BString line)
|
||
{
|
||
switch (numeric) {
|
||
case ERR_NICKNAMEINUSE:
|
||
{
|
||
fNick << "_";
|
||
BString cmd("NICK ");
|
||
cmd << fNick << "\n";
|
||
_SendIrc(cmd);
|
||
break;
|
||
}
|
||
default:
|
||
{
|
||
BMessage err(IM_MESSAGE);
|
||
err.AddInt32("im_what", IM_MESSAGE_RECEIVED);
|
||
err.AddString("body", line);
|
||
_SendMsg(&err);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
void
|
||
IrcProtocol::_ProcessCommand(BString command, BString sender,
|
||
BStringList params, BString line)
|
||
{
|
||
// If protocol uninitialized and the user's ident is mentioned― use it!
|
||
if (fReady == false && _SenderNick(sender) == fNick)
|
||
_MakeReady(_SenderNick(sender), _SenderIdent(sender));
|
||
|
||
if (sender == "PING")
|
||
{
|
||
BString cmd = "PONG ";
|
||
cmd << params.Last() << "\n";
|
||
_SendIrc(cmd);
|
||
}
|
||
else if (command == "PRIVMSG")
|
||
{
|
||
BString chat_id = params.First();
|
||
BString user_id = _SenderIdent(sender);
|
||
BString user_name = _SenderNick(sender);
|
||
BString body = params.Last();
|
||
if (_IsChannelName(chat_id) == false)
|
||
chat_id = _SenderNick(sender);
|
||
if (fChannels.HasString(chat_id) == false)
|
||
fChannels.Add(chat_id);
|
||
|
||
_UpdateContact(user_name, user_id, true);
|
||
|
||
BMessage chat(IM_MESSAGE);
|
||
chat.AddInt32("im_what", IM_MESSAGE_RECEIVED);
|
||
chat.AddString("chat_id", chat_id);
|
||
chat.AddString("user_id", user_id);
|
||
chat.AddString("user_name", user_name);
|
||
_AddFormatted(&chat, "body", body);
|
||
_SendMsg(&chat);
|
||
}
|
||
else if (command == "NOTICE")
|
||
{
|
||
BString chat_id = params.First();
|
||
BMessage send(IM_MESSAGE);
|
||
send.AddInt32("im_what", IM_MESSAGE_RECEIVED);
|
||
|
||
if (_IsChannelName(chat_id) == false)
|
||
chat_id = _SenderNick(sender);
|
||
if (fChannels.HasString(chat_id) == false)
|
||
fChannels.Add(chat_id);
|
||
|
||
if (chat_id != "AUTH" || chat_id != "*")
|
||
send.AddString("chat_id", chat_id);
|
||
|
||
if (sender.IsEmpty() == false) {
|
||
send.AddString("user_id", _SenderIdent(sender));
|
||
send.AddString("user_name", _SenderNick(sender));
|
||
}
|
||
send.AddString("body", params.Last());
|
||
_SendMsg(&send);
|
||
}
|
||
else if (command == "TOPIC")
|
||
{
|
||
BString chat_id = params.First();
|
||
BString subject = params.Last();
|
||
|
||
BMessage topic(IM_MESSAGE);
|
||
topic.AddInt32("im_what", IM_ROOM_SUBJECT_SET);
|
||
topic.AddString("subject", subject);
|
||
topic.AddString("chat_id", chat_id);
|
||
_SendMsg(&topic);
|
||
}
|
||
else if (command == "JOIN")
|
||
{
|
||
BString chat_id = params.First();
|
||
BString user_id = _SenderIdent(sender);
|
||
BString user_name = _SenderNick(sender);
|
||
_UpdateContact(user_name, user_id, true);
|
||
|
||
BMessage joined(IM_MESSAGE);
|
||
joined.AddString("chat_id", chat_id);
|
||
if (_SenderIdent(sender) == fIdent) {
|
||
joined.AddInt32("im_what", IM_ROOM_JOINED);
|
||
fChannels.Add(chat_id);
|
||
}
|
||
else {
|
||
joined.AddInt32("im_what", IM_ROOM_PARTICIPANT_JOINED);
|
||
joined.AddString("user_id", user_id);
|
||
joined.AddString("user_name", user_name);
|
||
fIdentNicks.AddItem(user_id, user_name);
|
||
}
|
||
_SendMsg(&joined);
|
||
|
||
BMessage status(IM_MESSAGE);
|
||
status.AddInt32("im_what", IM_USER_STATUS_SET);
|
||
status.AddString("user_id", user_id);
|
||
status.AddInt32("status", STATUS_ONLINE);
|
||
_SendMsg(&status);
|
||
}
|
||
else if (command == "PART")
|
||
{
|
||
BString chat_id = params.First();
|
||
BString body = B_TRANSLATE("left: ");
|
||
body << params.Last();
|
||
|
||
BMessage left(IM_MESSAGE);
|
||
left.AddString("chat_id", chat_id);
|
||
left.AddString("body", body);
|
||
if (_SenderIdent(sender) == fIdent) {
|
||
left.AddInt32("im_what", IM_ROOM_LEFT);
|
||
fChannels.Remove(chat_id);
|
||
}
|
||
else {
|
||
left.AddInt32("im_what", IM_ROOM_PARTICIPANT_LEFT);
|
||
left.AddString("user_id", _SenderIdent(sender));
|
||
left.AddString("user_name", _SenderNick(sender));
|
||
}
|
||
_SendMsg(&left);
|
||
}
|
||
else if (command == "KICK")
|
||
{
|
||
BString chat_id = params.First();
|
||
BString user_id = params.StringAt(1);
|
||
|
||
BMessage foot(IM_MESSAGE);
|
||
foot.AddInt32("im_what", IM_ROOM_PARTICIPANT_KICKED);
|
||
foot.AddString("chat_id", chat_id);
|
||
foot.AddString("user_name", _IdentNick(user_id));
|
||
foot.AddString("user_id", _NickIdent(user_id));
|
||
if (params.CountStrings() == 3)
|
||
foot.AddString("body", params.StringAt(2));
|
||
_SendMsg(&foot);
|
||
}
|
||
else if (command == "QUIT")
|
||
{
|
||
BString user_id = _SenderIdent(sender);
|
||
BString user_name = _SenderNick(sender);
|
||
_UpdateContact(user_name, user_id, false);
|
||
|
||
BString body = B_TRANSLATE("quit: ");
|
||
body << params.Last();
|
||
|
||
for (int i = 0; i < fChannels.CountStrings(); i++) {
|
||
if (_IsChannelName(fChannels.StringAt(i)) == false)
|
||
continue;
|
||
BMessage left(IM_MESSAGE);
|
||
left.AddInt32("im_what", IM_ROOM_PARTICIPANT_LEFT);
|
||
left.AddString("user_id", user_id);
|
||
left.AddString("user_name", user_name);
|
||
left.AddString("chat_id", fChannels.StringAt(i));
|
||
_SendMsg(&left);
|
||
}
|
||
|
||
BMessage status(IM_MESSAGE);
|
||
status.AddInt32("im_what", IM_USER_STATUS_SET);
|
||
status.AddString("user_id", user_id);
|
||
status.AddInt32("status", STATUS_OFFLINE);
|
||
_SendMsg(&status);
|
||
}
|
||
else if (command == "INVITE")
|
||
{
|
||
BMessage invite(IM_MESSAGE);
|
||
invite.AddInt32("im_what", IM_ROOM_INVITE_RECEIVED);
|
||
invite.AddString("chat_id", params.Last());
|
||
invite.AddString("user_id", _SenderIdent(sender));
|
||
_SendMsg(&invite);
|
||
}
|
||
else if (command == "NICK")
|
||
{
|
||
BString ident = _SenderIdent(sender);
|
||
BString user_name = params.Last();
|
||
|
||
BMessage nick(IM_MESSAGE);
|
||
nick.AddString("user_name", user_name);
|
||
if (ident == fIdent) {
|
||
nick.AddInt32("im_what", IM_OWN_NICKNAME_SET);
|
||
fNick = user_name;
|
||
}
|
||
else {
|
||
nick.AddInt32("im_what", IM_USER_NICKNAME_SET);
|
||
nick.AddString("user_id", ident);
|
||
|
||
_RenameContact(ident, user_name);
|
||
fIdentNicks.RemoveItemFor(ident);
|
||
fIdentNicks.AddItem(ident, user_name);
|
||
}
|
||
_SendMsg(&nick);
|
||
}
|
||
}
|
||
|
||
|
||
void
|
||
IrcProtocol::_MakeReady(BString nick, BString ident)
|
||
{
|
||
fNick = nick;
|
||
fIdent = ident;
|
||
|
||
fReady = true;
|
||
BMessage ready(IM_MESSAGE);
|
||
ready.AddInt32("im_what", IM_PROTOCOL_READY);
|
||
_SendMsg(&ready);
|
||
|
||
BMessage self(IM_MESSAGE);
|
||
self.AddInt32("im_what", IM_OWN_CONTACT_INFO);
|
||
self.AddString("user_id", fIdent);
|
||
self.AddString("user_name", fNick);
|
||
_SendMsg(&self);
|
||
|
||
_SendIrc("MOTD\n");
|
||
|
||
_LoadContacts();
|
||
}
|
||
|
||
|
||
BString
|
||
IrcProtocol::_LineSender(BStringList words)
|
||
{
|
||
BString sender;
|
||
if (words.CountStrings() > 1) {
|
||
sender = words.First();
|
||
if (sender.StartsWith(":") == true)
|
||
sender.RemoveFirst(":");
|
||
else if (sender.StartsWith("*:") == true)
|
||
sender.RemoveFirst("*:");
|
||
}
|
||
return sender;
|
||
}
|
||
|
||
|
||
BString
|
||
IrcProtocol::_LineCode(BStringList words)
|
||
{
|
||
BString code;
|
||
if (words.CountStrings() > 2)
|
||
code = words.StringAt(1);
|
||
return code;
|
||
}
|
||
|
||
|
||
BStringList
|
||
IrcProtocol::_LineParameters(BStringList words, BString line)
|
||
{
|
||
BStringList params;
|
||
BString current;
|
||
for (int i = 2; i < words.CountStrings(); i++)
|
||
if ((current = words.StringAt(i)).StartsWith(":") == false)
|
||
params.Add(current);
|
||
else
|
||
break;
|
||
|
||
// Last parameter is preceded by a colon
|
||
int32 index = line.RemoveChars(0, 1).FindFirst(" :");
|
||
if (index != B_ERROR)
|
||
params.Add(line.RemoveChars(0, index + 2));
|
||
return params;
|
||
}
|
||
|
||
|
||
void
|
||
IrcProtocol::_SendMsg(BMessage* msg)
|
||
{
|
||
msg->AddString("protocol", Signature());
|
||
if (fReady == true)
|
||
fMessenger->SendMessage(msg);
|
||
else if (DEBUG_ENABLED == true) {
|
||
std::cout << "Tried sending message when not ready: \n";
|
||
msg->PrintToStream();
|
||
}
|
||
}
|
||
|
||
|
||
void
|
||
IrcProtocol::_SendIrc(BString cmd)
|
||
{
|
||
cmd << "\r\n";
|
||
if (fSocket != NULL && fSocket->IsConnected() == true) {
|
||
while (fWriteLocked == true)
|
||
snooze(1000);
|
||
fWriteLocked = true;
|
||
fSocket->Write(cmd.String(), cmd.CountBytes(0, cmd.CountChars()));
|
||
fWriteLocked = false;
|
||
}
|
||
else {
|
||
BMessage disable(IM_MESSAGE);
|
||
disable.AddInt32("im_what", IM_PROTOCOL_DISABLE);
|
||
}
|
||
}
|
||
|
||
|
||
BString
|
||
IrcProtocol::_SenderNick(BString sender)
|
||
{
|
||
BStringList split;
|
||
sender.Split("!", true, split);
|
||
return split.First();
|
||
}
|
||
|
||
|
||
BString
|
||
IrcProtocol::_SenderIdent(BString sender)
|
||
{
|
||
BStringList split;
|
||
sender.Split("!", true, split);
|
||
return split.Last();
|
||
}
|
||
|
||
|
||
BString
|
||
IrcProtocol::_IdentNick(BString ident)
|
||
{
|
||
bool found = false;
|
||
BString nick = fIdentNicks.ValueFor(ident, &found);
|
||
if (found == true)
|
||
return nick;
|
||
return ident;
|
||
}
|
||
|
||
|
||
BString
|
||
IrcProtocol::_NickIdent(BString nick)
|
||
{
|
||
for (int i = 0; i < fIdentNicks.CountItems(); i++)
|
||
if (fIdentNicks.ValueAt(i) == nick)
|
||
return fIdentNicks.KeyAt(i);
|
||
return nick;
|
||
}
|
||
|
||
|
||
bool
|
||
IrcProtocol::_IsChannelName(BString name)
|
||
{
|
||
return (name.StartsWith("!") || name.StartsWith("&") || name.StartsWith("#")
|
||
|| name.StartsWith("+"));
|
||
}
|
||
|
||
|
||
#define disable_all_faces(current) { \
|
||
if (bold > -1) _ToggleAndAdd(msg, B_BOLD_FACE, &bold, current); \
|
||
if (italics > -1) _ToggleAndAdd(msg, B_ITALIC_FACE, &italics, current); \
|
||
if (strike > -1) _ToggleAndAdd(msg, B_STRIKEOUT_FACE, &strike, current); \
|
||
if (underline > -1) _ToggleAndAdd(msg, B_UNDERSCORE_FACE, &underline, current); \
|
||
};
|
||
|
||
|
||
void
|
||
IrcProtocol::_AddFormatted(BMessage* msg, const char* name, BString text)
|
||
{
|
||
BString newText;
|
||
int32 italics = -1, bold = -1, underline = -1, strike = -1, mono = -1;
|
||
|
||
int32 length = text.CountBytes(0, text.CountChars());
|
||
for (int32 j=0, i=0; j < length; j++) {
|
||
char c = text.ByteAt(j);
|
||
|
||
switch (c) {
|
||
case 0x02:
|
||
_ToggleAndAdd(msg, B_BOLD_FACE, &bold, i);
|
||
break;
|
||
case 0x1d:
|
||
_ToggleAndAdd(msg, B_ITALIC_FACE, &italics, i);
|
||
break;
|
||
case 0x1f:
|
||
_ToggleAndAdd(msg, B_UNDERSCORE_FACE, &underline, i);
|
||
break;
|
||
case 0x1e:
|
||
_ToggleAndAdd(msg, B_STRIKEOUT_FACE, &strike, i);
|
||
break;
|
||
case 0x0f:
|
||
disable_all_faces(i);
|
||
break;
|
||
default:
|
||
newText << c;
|
||
i++;
|
||
}
|
||
}
|
||
disable_all_faces(length);
|
||
msg->AddString(name, newText);
|
||
}
|
||
|
||
|
||
void
|
||
IrcProtocol::_ToggleAndAdd(BMessage* msg, uint16 face, int32* start,
|
||
int32 current)
|
||
{
|
||
if (*start == -1)
|
||
*start = current;
|
||
else {
|
||
msg->AddInt32("face_start", *start);
|
||
msg->AddInt32("face_length", current - *start);
|
||
msg->AddUInt16("face", face);
|
||
*start = -1;
|
||
}
|
||
}
|
||
|
||
|
||
void
|
||
IrcProtocol::_UpdateContact(BString nick, BString ident, bool online)
|
||
{
|
||
if (fContacts.HasString(nick) == false)
|
||
return;
|
||
|
||
if (online == true && fOfflineContacts.HasString(nick) == true) {
|
||
_RemoveContact(nick);
|
||
_AddContact(ident);
|
||
fOfflineContacts.Remove(nick);
|
||
}
|
||
else if (online == false && fOfflineContacts.HasString(nick) == false) {
|
||
fOfflineContacts.Add(nick);
|
||
}
|
||
}
|
||
|
||
|
||
void
|
||
IrcProtocol::_AddContact(BString nick)
|
||
{
|
||
fContacts.Add(nick);
|
||
BString user_id = _NickIdent(nick);
|
||
|
||
BMessage added(IM_MESSAGE);
|
||
added.AddInt32("im_what", IM_ROSTER);
|
||
added.AddString("user_id", user_id);
|
||
_SendMsg(&added);
|
||
|
||
if (user_id == nick)
|
||
fOfflineContacts.Add(nick);
|
||
}
|
||
|
||
|
||
void
|
||
IrcProtocol::_RemoveContact(BString user_id)
|
||
{
|
||
BString nick = _IdentNick(user_id);
|
||
fContacts.Remove(nick);
|
||
fOfflineContacts.Remove(nick);
|
||
|
||
BMessage removed(IM_MESSAGE);
|
||
removed.AddInt32("im_what", IM_ROSTER_CONTACT_REMOVED);
|
||
removed.AddString("user_id", _NickIdent(user_id));
|
||
_SendMsg(&removed);
|
||
}
|
||
|
||
|
||
void
|
||
IrcProtocol::_RenameContact(BString user_id, BString newNick)
|
||
{
|
||
BString oldNick = _IdentNick(user_id);
|
||
|
||
if (fContacts.HasString(oldNick) == true) {
|
||
fContacts.Remove(_IdentNick(user_id));
|
||
fOfflineContacts.Remove(_IdentNick(user_id));
|
||
fContacts.Add(newNick);
|
||
}
|
||
}
|
||
|
||
|
||
void
|
||
IrcProtocol::_LoadContacts()
|
||
{
|
||
BMessage contacts;
|
||
BFile file(_ContactsCache(), B_READ_ONLY);
|
||
if (file.InitCheck() == B_OK)
|
||
contacts.Unflatten(&file);
|
||
|
||
int i = 0;
|
||
BString user_name;
|
||
while (contacts.FindString("user_name", i, &user_name) == B_OK) {
|
||
_AddContact(user_name);
|
||
i++;
|
||
}
|
||
}
|
||
|
||
|
||
void
|
||
IrcProtocol::_SaveContacts()
|
||
{
|
||
BMessage contacts;
|
||
for (int i = 0; i < fContacts.CountStrings(); i++)
|
||
contacts.AddString("user_name", fContacts.StringAt(i));
|
||
|
||
BFile file(_ContactsCache(), B_WRITE_ONLY | B_CREATE_FILE);
|
||
if (file.InitCheck() == B_OK)
|
||
contacts.Flatten(&file);
|
||
}
|
||
|
||
|
||
int32
|
||
IrcProtocol::_RolePerms(UserRole role)
|
||
{
|
||
switch (role) {
|
||
case ROOM_HALFOP:
|
||
return PERM_READ | PERM_WRITE | PERM_NICK | PERM_KICK
|
||
| PERM_ROOM_SUBJECT;
|
||
case IRC_OPERATOR:
|
||
case ROOM_OPERATOR:
|
||
return PERM_READ | PERM_WRITE | PERM_NICK | PERM_KICK | PERM_BAN
|
||
| PERM_ROOM_SUBJECT | PERM_ROLECHANGE;
|
||
}
|
||
return PERM_READ | PERM_WRITE | PERM_NICK;
|
||
}
|
||
|
||
|
||
const char*
|
||
IrcProtocol::_RoleTitle(UserRole role)
|
||
{
|
||
switch (role) {
|
||
case ROOM_HALFOP:
|
||
return B_TRANSLATE("Half-Op");
|
||
case ROOM_OPERATOR:
|
||
return B_TRANSLATE("Operator");
|
||
case IRC_OPERATOR:
|
||
return B_TRANSLATE("IRC Wizard");
|
||
}
|
||
return B_TRANSLATE("Member");
|
||
}
|
||
|
||
|
||
const char*
|
||
IrcProtocol::_CachePath()
|
||
{
|
||
BPath path;
|
||
if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
|
||
return NULL;
|
||
path.Append(APP_NAME "/Cache/Accounts");
|
||
path.Append(fName);
|
||
if (create_directory(path.Path(), 0755) != B_OK)
|
||
return NULL;
|
||
return path.Path();
|
||
}
|
||
|
||
|
||
const char*
|
||
IrcProtocol::_ContactsCache()
|
||
{
|
||
BPath path(_CachePath());
|
||
path.Append("contact_list");
|
||
return path.Path();
|
||
}
|
||
|
||
|
||
BString
|
||
IrcProtocol::_ReadUntilNewline(BDataIO* io, BString* extraBuffer)
|
||
{
|
||
BString total;
|
||
char buf[1024] = { '\0' };
|
||
|
||
// Use buffer from last read if any text remains
|
||
if (extraBuffer->IsEmpty() == false) {
|
||
BString trimRet = _TrimStringToNewline(extraBuffer);
|
||
if (trimRet.IsEmpty() == true)
|
||
total << extraBuffer;
|
||
else
|
||
return trimRet;
|
||
}
|
||
|
||
while (!(strstr(buf, "\n"))) {
|
||
io->Read(buf, 1023);
|
||
total << buf;
|
||
if (DEBUG_ENABLED)
|
||
std::cerr << buf << std::endl;
|
||
}
|
||
|
||
BString currentLine = _TrimStringToNewline(&total);
|
||
extraBuffer->SetTo(total);
|
||
return currentLine;
|
||
}
|
||
|
||
|
||
BString
|
||
IrcProtocol::_TrimStringToNewline(BString* str)
|
||
{
|
||
BString line;
|
||
int32 lineEnd = str->FindFirst('\n');
|
||
|
||
if (lineEnd != B_ERROR) {
|
||
str->CopyCharsInto(line, 0, lineEnd + 1);
|
||
str->RemoveChars(0, lineEnd + 1);
|
||
}
|
||
return line;
|
||
}
|
||
|
||
|
||
BMessage
|
||
IrcProtocol::_AccountTemplate()
|
||
{
|
||
BMessage settings;
|
||
|
||
BMessage server;
|
||
server.AddString("name", "server");
|
||
server.AddString("description", B_TRANSLATE("Server:"));
|
||
server.AddString("default", "irc.oftc.net");
|
||
server.AddString("error", B_TRANSLATE("Please enter a valid server address."));
|
||
server.AddInt32("type", B_STRING_TYPE);
|
||
settings.AddMessage("setting", &server);
|
||
|
||
BMessage port;
|
||
port.AddString("name", "port");
|
||
port.AddString("description", B_TRANSLATE("Port:"));
|
||
port.AddInt32("default", 6697);
|
||
port.AddString("error", B_TRANSLATE("We need a port-number to know which door to knock on! Likely 6667/6697."));
|
||
port.AddInt32("type", B_INT32_TYPE);
|
||
settings.AddMessage("setting", &port);
|
||
|
||
BMessage ssl;
|
||
ssl.AddString("name", "ssl");
|
||
ssl.AddString("description", B_TRANSLATE("SSL"));
|
||
ssl.AddBool("default", true);
|
||
ssl.AddInt32("type", B_BOOL_TYPE);
|
||
settings.AddMessage("setting", &ssl);
|
||
|
||
BMessage nick;
|
||
nick.AddString("name", "nick");
|
||
nick.AddString("description", B_TRANSLATE("Nickname:"));
|
||
nick.AddString("default", "Haikunaut");
|
||
nick.AddString("error", B_TRANSLATE("You need a default nickname― The Nameless are not welcome on IRC."));
|
||
nick.AddInt32("type", B_STRING_TYPE);
|
||
settings.AddMessage("setting", &nick);
|
||
|
||
BMessage ident;
|
||
ident.AddString("name", "ident");
|
||
ident.AddString("description", B_TRANSLATE("Username:"));
|
||
ident.AddString("error", B_TRANSLATE("You need a username in order to connect!"));
|
||
ident.AddInt32("type", B_STRING_TYPE);
|
||
settings.AddMessage("setting", &ident);
|
||
|
||
BMessage password;
|
||
password.AddString("name", "password");
|
||
password.AddString("description", B_TRANSLATE("Password:"));
|
||
password.AddInt32("type", B_STRING_TYPE);
|
||
settings.AddMessage("setting", &password);
|
||
|
||
BMessage realName;
|
||
realName.AddString("name", "real_name");
|
||
realName.AddString("description", B_TRANSLATE("Real name:"));
|
||
realName.AddInt32("type", B_STRING_TYPE);
|
||
settings.AddMessage("setting", &realName);
|
||
|
||
BMessage part;
|
||
part.AddString("name", "part");
|
||
part.AddString("description", B_TRANSLATE("Part message:"));
|
||
part.AddInt32("type", B_STRING_TYPE);
|
||
part.AddString("default", "Cardie[0.1]: i've been liquified!");
|
||
settings.AddMessage("setting", &part);
|
||
|
||
return settings;
|
||
}
|
||
|
||
|
||
BMessage
|
||
IrcProtocol::_RoomTemplate()
|
||
{
|
||
BMessage settings;
|
||
|
||
BMessage id;
|
||
id.AddString("name", "chat_id");
|
||
id.AddString("description", B_TRANSLATE("Channel:"));
|
||
id.AddString("error", B_TRANSLATE("Please enter a channel― skipping it doesn't make sense!"));
|
||
id.AddInt32("type", B_STRING_TYPE);
|
||
settings.AddMessage("setting", &id);
|
||
|
||
return settings;
|
||
}
|
||
|
||
|
||
BMessage
|
||
IrcProtocol::_RosterTemplate()
|
||
{
|
||
BMessage settings;
|
||
|
||
BMessage nick;
|
||
nick.AddString("name", "user_id");
|
||
nick.AddString("description", B_TRANSLATE("Nick:"));
|
||
nick.AddString("error", B_TRANSLATE("How can someone be your friend if you don't know their name?"));
|
||
nick.AddInt32("type", B_STRING_TYPE);
|
||
settings.AddMessage("setting", &nick);
|
||
|
||
return settings;
|
||
}
|