4905dbbe6c
Currently, add-ons are disconnected when ChatProtocol::Shutdown() is called, which the add-on can do by itself― but there is no standard way for add-ons to notify the app about their Shutdown. Because of this, they tend to not call Shutdown()― instead (as in the case of the Jabber add-on), they display a BAlert (IM_ERROR) notifying the user of the connection error, but the account is considered active by Cardie (and its threads are still existant, including its ProtocolLooper). Zombies are bad, so this is redesigned somewhat with this commit: Protocols should no longer call ChatProtocol::Shutdown() themselves, they must send an IM_MESSAGE of IM_PROTOCOL_DISABLE to the app. This will delete its ProtocolLooper, which in turn will send a notification to the user and delete the ChatProtocol, and so calling ChatProtocol::Shutdown(). In the included protocols, an IM_ERROR is sent right before IM_PROTOCOL_DISABLE is sent if due to a connection error. This is not required, but it is courteous to inform your user about the "why." :)
474 lines
9.3 KiB
C++
474 lines
9.3 KiB
C++
/*
|
|
* Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
|
|
* All rights reserved. Distributed under the terms of the MIT license.
|
|
*/
|
|
|
|
#include "IrcProtocol.h"
|
|
|
|
#include <cstdio>
|
|
|
|
#include <StringList.h>
|
|
|
|
#include <ChatProtocolMessages.h>
|
|
|
|
|
|
IrcProtocol* current_proto;
|
|
|
|
|
|
status_t
|
|
connect_thread(void* data)
|
|
{
|
|
IrcProtocol* proto = (IrcProtocol*)data;
|
|
current_proto = proto;
|
|
|
|
BMessage* settings = proto->fSettings;
|
|
if (!settings)
|
|
return B_ERROR;
|
|
|
|
const char* nick = settings->FindString("nick");
|
|
const char* ident = settings->FindString("ident");
|
|
const char* real_name = settings->FindString("real_name");
|
|
const char* server = settings->FindString("server");
|
|
const char* password = settings->FindString("password");
|
|
int32 port = settings->FindInt32("port");
|
|
bool ssl = false;
|
|
|
|
if (!nick || !ident || !server)
|
|
return B_ERROR;
|
|
|
|
// libircclient wants a "#" in front of SSL addresses
|
|
BString joinServer;
|
|
if (ssl == true)
|
|
joinServer << "#";
|
|
joinServer << server;
|
|
|
|
// Now create the session
|
|
irc_callbacks_t callbacks = get_callbacks();
|
|
irc_session_t* session = irc_create_session(&callbacks);
|
|
proto->fSession = session;
|
|
|
|
irc_ctx_t ctx;
|
|
ctx.nick.SetTo(nick);
|
|
irc_set_ctx(session, &ctx);
|
|
|
|
if (!session)
|
|
return B_ERROR;
|
|
|
|
// Start connection
|
|
if (irc_connect(session, joinServer.String(), port, password, nick, ident,
|
|
real_name))
|
|
{
|
|
BMessage error(IM_ERROR);
|
|
error.AddString("error", "Could not connect");
|
|
error.AddString("details", irc_strerror(irc_errno(session)));
|
|
_SendMessage(&error);
|
|
_SendMessage(new BMessage(IM_PROTOCOL_DISABLE));
|
|
return B_ERROR;
|
|
}
|
|
|
|
// Start network loop
|
|
if (irc_run(session)) {
|
|
BMessage error(IM_ERROR);
|
|
error.AddString("error", "Connection or I/O error");
|
|
error.AddString("details", irc_strerror(irc_errno(session)));
|
|
_SendMessage(&error);
|
|
_SendMessage(new BMessage(IM_PROTOCOL_DISABLE));
|
|
return B_ERROR;
|
|
}
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
IrcProtocol::IrcProtocol()
|
|
{
|
|
}
|
|
|
|
|
|
status_t
|
|
IrcProtocol::Init(ChatProtocolMessengerInterface* interface)
|
|
{
|
|
fMessenger = interface;
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
IrcProtocol::Shutdown()
|
|
{
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
IrcProtocol::Process(BMessage* msg)
|
|
{
|
|
BString chat_id;
|
|
if (msg->FindString("chat_id", &chat_id) != B_OK)
|
|
return B_ERROR;
|
|
|
|
switch (msg->FindInt32("im_what"))
|
|
{
|
|
case IM_SEND_MESSAGE:
|
|
{
|
|
BString body;
|
|
if (msg->FindString("body", &body) != B_OK)
|
|
return B_ERROR;
|
|
irc_cmd_msg(fSession, chat_id.String(), body.String());
|
|
|
|
irc_ctx_t* ctx = (irc_ctx_t*)irc_get_ctx(fSession);
|
|
|
|
BMessage sendMsg(*msg);
|
|
sendMsg.ReplaceInt32("im_what", IM_MESSAGE_SENT);
|
|
sendMsg.AddString("user_id", ctx->id);
|
|
_SendMessage(&sendMsg);
|
|
break;
|
|
}
|
|
case IM_JOIN_ROOM:
|
|
case IM_CREATE_ROOM:
|
|
{
|
|
irc_cmd_join(fSession, chat_id.String(), "");
|
|
break;
|
|
}
|
|
case IM_LEAVE_ROOM:
|
|
{
|
|
irc_cmd_part(fSession, chat_id.String());
|
|
break;
|
|
}
|
|
case IM_GET_ROOM_PARTICIPANTS:
|
|
{
|
|
irc_cmd_names(fSession, chat_id.String());
|
|
break;
|
|
}
|
|
case IM_ROOM_SEND_INVITE:
|
|
{
|
|
BString user_id;
|
|
if (msg->FindString("user_id", &user_id) != B_OK)
|
|
return B_ERROR;
|
|
irc_cmd_invite(fSession, user_id.String(), chat_id.String());
|
|
break;
|
|
}
|
|
case IM_ROOM_KICK_PARTICIPANT:
|
|
{
|
|
BString user_id;
|
|
if (msg->FindString("user_id", &user_id) != B_OK)
|
|
return B_ERROR;
|
|
irc_cmd_kick(fSession, user_id.String(), chat_id.String(),
|
|
msg->FindString("body"));
|
|
break;
|
|
}
|
|
}
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
IrcProtocol::UpdateSettings(BMessage* msg)
|
|
{
|
|
fSettings = msg;
|
|
fServerThread = spawn_thread(connect_thread, "connect_thread",
|
|
B_NORMAL_PRIORITY, (void*)this);
|
|
|
|
if (fServerThread < B_OK)
|
|
return B_ERROR;
|
|
|
|
resume_thread(fServerThread);
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
BMessage
|
|
IrcProtocol::SettingsTemplate(const char* name)
|
|
{
|
|
size_t size;
|
|
BMessage temp;
|
|
const void* buff = fResources.LoadResource(B_MESSAGE_TYPE, name, &size);
|
|
|
|
if (buff != NULL)
|
|
temp.Unflatten((const char*)buff);
|
|
return temp;
|
|
}
|
|
|
|
|
|
BObjectList<BMessage>
|
|
IrcProtocol::Commands()
|
|
{
|
|
return BObjectList<BMessage>();
|
|
}
|
|
|
|
|
|
BObjectList<BMessage>
|
|
IrcProtocol::UserPopUpItems()
|
|
{
|
|
return BObjectList<BMessage>();
|
|
}
|
|
|
|
|
|
BObjectList<BMessage>
|
|
IrcProtocol::ChatPopUpItems()
|
|
{
|
|
return BObjectList<BMessage>();
|
|
}
|
|
|
|
BObjectList<BMessage>
|
|
IrcProtocol::MenuBarItems()
|
|
{
|
|
return BObjectList<BMessage>();
|
|
}
|
|
|
|
|
|
const char*
|
|
IrcProtocol::Signature() const
|
|
{
|
|
return "irc";
|
|
}
|
|
|
|
|
|
const char*
|
|
IrcProtocol::FriendlySignature() const
|
|
{
|
|
return "IRC";
|
|
}
|
|
|
|
|
|
BBitmap*
|
|
IrcProtocol::Icon() const
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void
|
|
IrcProtocol::SetAddOnPath(BPath path)
|
|
{
|
|
fAddOnPath = path;
|
|
fResources.SetTo(path.Path());
|
|
}
|
|
|
|
|
|
BPath
|
|
IrcProtocol::AddOnPath()
|
|
{
|
|
return fAddOnPath;
|
|
}
|
|
|
|
|
|
const char*
|
|
IrcProtocol::GetName()
|
|
{
|
|
return fName.String();
|
|
}
|
|
|
|
|
|
void
|
|
IrcProtocol::SetName(const char* name)
|
|
{
|
|
fName.SetTo(name);
|
|
}
|
|
|
|
|
|
uint32
|
|
IrcProtocol::GetEncoding()
|
|
{
|
|
return 0xffff;
|
|
}
|
|
|
|
|
|
ChatProtocolMessengerInterface*
|
|
IrcProtocol::MessengerInterface() const
|
|
{
|
|
return fMessenger;
|
|
}
|
|
|
|
|
|
irc_callbacks_t
|
|
get_callbacks()
|
|
{
|
|
irc_callbacks_t callbacks;
|
|
callbacks.event_connect = event_connect;
|
|
callbacks.event_numeric = event_numeric;
|
|
callbacks.event_join = event_join;
|
|
callbacks.event_part = event_part;
|
|
callbacks.event_channel = event_channel;
|
|
callbacks.event_privmsg = event_privmsg;
|
|
callbacks.event_nick = event_nick;
|
|
return callbacks;
|
|
}
|
|
|
|
|
|
void
|
|
event_connect(irc_session_t* session, const char* event,
|
|
const char* origin, const char** params, unsigned int count)
|
|
{
|
|
BMessage readyMsg(IM_MESSAGE);
|
|
readyMsg.AddInt32("im_what", IM_PROTOCOL_READY);
|
|
_SendMessage(&readyMsg);
|
|
}
|
|
|
|
|
|
void
|
|
event_numeric(irc_session_t* session, unsigned int event,
|
|
const char* origin, const char** params, unsigned int count)
|
|
{
|
|
irc_ctx_t* ctx = (irc_ctx_t*)irc_get_ctx(session);
|
|
|
|
switch (event) {
|
|
case LIBIRC_RFC_RPL_NAMREPLY:
|
|
{
|
|
BStringList user_id;
|
|
|
|
BString user_list(params[3]);
|
|
user_list.Split(" ", false, user_id);
|
|
|
|
BStringList user_name(user_id);
|
|
int32 index = user_name.IndexOf(ctx->nick);
|
|
if (index >= 0)
|
|
user_id.Replace(index, BString(ctx->id));
|
|
|
|
BMessage list(IM_MESSAGE);
|
|
list.AddInt32("im_what", IM_ROOM_PARTICIPANTS);
|
|
list.AddString("chat_id", params[2]);
|
|
list.AddStrings("user_id", user_id);
|
|
list.AddStrings("user_name", user_name);
|
|
_SendMessage(&list);
|
|
break;
|
|
}
|
|
case LIBIRC_RFC_RPL_TOPIC:
|
|
{
|
|
BMessage topic(IM_MESSAGE);
|
|
topic.AddInt32("im_what", IM_ROOM_SUBJECT_SET);
|
|
topic.AddString("chat_id", params[1]);
|
|
topic.AddString("subject", params[2]);
|
|
_SendMessage(&topic);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
event_join(irc_session_t* session, const char* event, const char* joiner,
|
|
const char** channel, unsigned int count)
|
|
{
|
|
int32 im_what = IM_ROOM_PARTICIPANT_JOINED;
|
|
|
|
irc_ctx_t* ctx = (irc_ctx_t*)irc_get_ctx(session);
|
|
if (_IsOwnUser(joiner, session) == true)
|
|
im_what = IM_ROOM_JOINED;
|
|
|
|
BMessage joinedMsg(IM_MESSAGE);
|
|
joinedMsg.AddInt32("im_what", im_what);
|
|
joinedMsg.AddString("user_id", joiner);
|
|
joinedMsg.AddString("chat_id", channel[0]);
|
|
_SendMessage(&joinedMsg);
|
|
}
|
|
|
|
|
|
void
|
|
event_part(irc_session_t* session, const char* event, const char* quitter,
|
|
const char** chanReason, unsigned int count)
|
|
{
|
|
int32 im_what = IM_ROOM_PARTICIPANT_LEFT;
|
|
if (_IsOwnUser(quitter, session) == true)
|
|
im_what = IM_ROOM_LEFT;
|
|
|
|
BString body;
|
|
if (chanReason[1] != NULL)
|
|
body << chanReason[1];
|
|
|
|
BMessage quitMsg(IM_MESSAGE);
|
|
quitMsg.AddInt32("im_what", im_what);
|
|
quitMsg.AddString("user_id", quitter);
|
|
quitMsg.AddString("chat_id", chanReason[0]);
|
|
if (body.IsEmpty() == false)
|
|
quitMsg.AddString("body", body);
|
|
_SendMessage(&quitMsg);
|
|
}
|
|
|
|
|
|
void
|
|
event_channel(irc_session_t* session, const char* event, const char* sender,
|
|
const char** chanBody, unsigned int count)
|
|
{
|
|
BMessage msgMsg(IM_MESSAGE);
|
|
msgMsg.AddInt32("im_what", IM_MESSAGE_RECEIVED);
|
|
msgMsg.AddString("user_id", sender);
|
|
msgMsg.AddString("chat_id", chanBody[0]);
|
|
msgMsg.AddString("body", chanBody[1]);
|
|
_SendMessage(&msgMsg);
|
|
}
|
|
|
|
|
|
void
|
|
event_privmsg(irc_session_t* session, const char* event, const char* sender,
|
|
const char** selfBody, unsigned int count)
|
|
{
|
|
BMessage msgMsg(IM_MESSAGE);
|
|
msgMsg.AddInt32("im_what", IM_MESSAGE_RECEIVED);
|
|
msgMsg.AddString("user_id", sender);
|
|
msgMsg.AddString("chat_id", _UserNick(sender));
|
|
msgMsg.AddString("body", selfBody[1]);
|
|
_SendMessage(&msgMsg);
|
|
}
|
|
|
|
|
|
// TODO: Nick-change API
|
|
void
|
|
event_nick(irc_session_t* session, const char* event, const char* oldNick,
|
|
const char** newNick, unsigned int count)
|
|
{
|
|
irc_ctx_t* ctx = (irc_ctx_t*)irc_get_ctx(session);
|
|
|
|
if (_IsOwnUser(oldNick, session) == true) {
|
|
ctx->nick.SetTo(newNick[0]);
|
|
irc_set_ctx(session, ctx);
|
|
}
|
|
}
|
|
|
|
|
|
BString
|
|
_UserNick(const char* userId)
|
|
{
|
|
BStringList split;
|
|
BString id(userId);
|
|
id.Split("!", false, split);
|
|
|
|
BString name = split.StringAt(0);
|
|
if (name.StartsWith("@") == true)
|
|
name.RemoveFirst("@");
|
|
|
|
return name;
|
|
}
|
|
|
|
|
|
bool
|
|
_IsOwnUser(const char* userId, irc_session_t* session)
|
|
{
|
|
bool ret = false;
|
|
irc_ctx_t* ctx = (irc_ctx_t*)irc_get_ctx(session);
|
|
|
|
if (ctx->nick == _UserNick(userId)) {
|
|
ret = true;
|
|
if (ctx->id.IsEmpty() == true) {
|
|
ctx->id.SetTo(userId);
|
|
irc_set_ctx(session, ctx);
|
|
|
|
// The app hasn't been informed of user_id, then!
|
|
BMessage ownInfo(IM_MESSAGE);
|
|
ownInfo.AddInt32("im_what", IM_OWN_CONTACT_INFO);
|
|
ownInfo.AddString("user_id", userId);
|
|
_SendMessage(&ownInfo);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
void
|
|
_SendMessage(BMessage* msg)
|
|
{
|
|
if (!msg)
|
|
return;
|
|
msg->AddString("protocol", current_proto->Signature());
|
|
current_proto->MessengerInterface()->SendMessage(msg);
|
|
}
|