123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767 |
- /*
- * Copyright 2021, Jaidyn Levesque <jadedctrl@teknik.io>
- * All rights reserved. Distributed under the terms of the MIT license.
- */
- #include "Conversation.h"
- #include <Beep.h>
- #include <Catalog.h>
- #include <DateTimeFormat.h>
- #include <Locale.h>
- #include <Notification.h>
- #include <StringFormat.h>
- #include "AppConstants.h"
- #include "AppPreferences.h"
- #include "ChatOMatic.h"
- #include "ChatProtocolMessages.h"
- #include "RenderView.h"
- #include "ChatCommand.h"
- #include "ConversationItem.h"
- #include "ConversationView.h"
- #include "Flags.h"
- #include "ImageCache.h"
- #include "MainWindow.h"
- #include "NotifyMessage.h"
- #include "ProtocolLooper.h"
- #include "ProtocolManager.h"
- #include "Server.h"
- #include "TheApp.h"
- #include "Utils.h"
- Conversation::Conversation(BString id, BMessenger msgn)
- :
- fID(id),
- fName(id),
- fMessenger(msgn),
- fChatView(NULL),
- fLooper(NULL),
- fIcon(ImageCache::Get()->GetImage("kOnePersonIcon")),
- fDateFormatter(),
- fRoomFlags(0),
- fDisallowedFlags(0),
- fNotifyMessageCount(0),
- fNotifyMentionCount(0),
- fUserIcon(false)
- {
- fConversationItem = new ConversationItem(fName.String(), this);
- RegisterObserver(fConversationItem);
- }
- Conversation::~Conversation()
- {
- ((TheApp*)be_app)->GetMainWindow()->RemoveConversation(this);
- if (fLooper != NULL)
- fLooper->RemoveConversation(this);
- delete fChatView;
- delete fConversationItem;
- }
- BString
- Conversation::GetId() const
- {
- return fID;
- }
- void
- Conversation::ImMessage(BMessage* msg)
- {
- int32 im_what = msg->FindInt32("im_what");
- switch(im_what)
- {
- case IM_MESSAGE_RECEIVED:
- {
- #undef B_TRANSLATION_CONTEXT
- #define B_TRANSLATION_CONTEXT "Conversation ― Notifications"
- _EnsureUser(msg);
- _LogChatMessage(msg);
- GetView()->MessageReceived(msg);
- BString text = msg->FindString("body");
- Contact* contact = GetOwnContact();
- BWindow* win = fChatView->Window();
- bool winFocus = (win != NULL &&
- (win->IsFront() && !(win->IsMinimized())));
- bool mentioned = ((contact->GetName().IsEmpty() == false
- && text.IFindFirst(contact->GetName()) != B_ERROR)
- || (text.IFindFirst(contact->GetId()) != B_ERROR));
- // Sound the bell, if appropriate
- if (winFocus == false) {
- if (mentioned == true
- && AppPreferences::Get()->SoundOnMention == true)
- system_beep(APP_MENTION_BEEP);
- else if (AppPreferences::Get()->SoundOnMessageReceived == true
- && ((fUsers.CountItems() <=2 && (fRoomFlags & ROOM_NOTIFY_DM))
- || (fRoomFlags & ROOM_NOTIFY_ALL)))
- system_beep(APP_MESSAGE_BEEP);
- }
- // Send a notification, if appropriate
- if (winFocus == false && AppPreferences::Get()->NotifyNewMessage) {
- BString notifyTitle = B_TRANSLATE("New mention");
- BString notifyText = B_TRANSLATE("You've been summoned from "
- "%source%.");
- if (mentioned == false) {
- fNotifyMessageCount++;
- notifyTitle.SetTo(B_TRANSLATE("New message"));
- notifyText.SetTo("");
- BStringFormat pmFormat(B_TRANSLATE("{0, plural,"
- "=1{You've got a new message from %source%.}"
- "other{You've got # new messages from %source%.}}"));
- pmFormat.Format(notifyText, fNotifyMessageCount);
- }
- else
- fNotifyMentionCount++;
- notifyText.ReplaceAll("%source%", GetName());
- if ((fUsers.CountItems() <= 2 && (fRoomFlags & ROOM_NOTIFY_DM))
- || (fRoomFlags & ROOM_NOTIFY_ALL) || mentioned == true)
- {
- BBitmap* icon = IconBitmap();
- if (icon == NULL)
- icon = ProtocolBitmap();
- BNotification notification(B_INFORMATION_NOTIFICATION);
- notification.SetGroup(BString(APP_NAME));
- notification.SetTitle(notifyTitle);
- notification.SetIcon(icon);
- notification.SetContent(notifyText);
- notification.SetMessageID(fID);
- notification.Send();
- }
- }
- // Misc. features Caya contributors planned on adding
- BWindow* mainWin = ((TheApp*)be_app)->GetMainWindow();
- if (win == NULL && AppPreferences::Get()->MarkUnreadWindow == true)
- mainWin->SetTitle(BString(mainWin->Title()).Prepend("[!]"));
- if (win == NULL && AppPreferences::Get()->MoveToCurrentWorkspace)
- mainWin->SetWorkspaces(B_CURRENT_WORKSPACE);
- if (win == NULL && AppPreferences::Get()->RaiseOnMessageReceived)
- mainWin->Activate(true);
- // If unattached, highlight the ConversationItem
- if (win == NULL && mentioned == true)
- NotifyInteger(INT_NEW_MENTION, fNotifyMentionCount);
- else if (win == NULL)
- NotifyInteger(INT_NEW_MESSAGE, fNotifyMessageCount);
- break;
- }
- case IM_MESSAGE_SENT:
- {
- _LogChatMessage(msg);
- GetView()->MessageReceived(msg);
- break;
- }
- case IM_SEND_MESSAGE:
- {
- #undef B_TRANSLATION_CONTEXT
- #define B_TRANSLATION_CONTEXT "Conversation ― Command info"
- BString body;
- if (msg->FindString("body", &body) != B_OK)
- break;
- if (IsCommand(body.String()) == false) {
- fMessenger.SendMessage(msg);
- break;
- }
- BString name = CommandName(body);
- BString args = CommandArgs(body);
- ChatCommand* cmd = _GetServer()->CommandById(name, fLooper->GetInstance());
- if (cmd == NULL) {
- if (name == "me")
- fMessenger.SendMessage(msg);
- else
- _WarnUser(BString(B_TRANSLATE("That isn't a valid command. "
- "Try /help for a list.")));
- break;
- }
- BString error("");
- if (cmd->Parse(args, &error, this) == false)
- _WarnUser(error);
- break;
- }
- case IM_ROOM_METADATA:
- {
- BString name;
- if (msg->FindString("chat_name", &name) == B_OK)
- SetNotifyName(name.String());
- BString subject;
- if (msg->FindString("subject", &subject) == B_OK)
- SetNotifySubject(subject.String());
- int32 defaultFlags;
- if (msg->FindInt32("room_default_flags", &defaultFlags) == B_OK)
- if (fRoomFlags == 0)
- fRoomFlags = defaultFlags;
- int32 disabledFlags;
- if (msg->FindInt32("room_disallowed_flags", &disabledFlags) == B_OK)
- fDisallowedFlags = disabledFlags;
- _CacheRoomFlags();
- break;
- }
- case IM_ROOM_PARTICIPANTS:
- {
- // Get rid of implicity-defined users rq
- for (int i = 0; i < fGuests.CountStrings(); i++) {
- RemoveUser(UserById(fGuests.StringAt(i)));
- fGuests.Remove(i);
- }
- BStringList ids;
- BStringList names;
- msg->FindStrings("user_name", &names);
- if (msg->FindStrings("user_id", &ids) != B_OK)
- break;
- for (int i = 0; i < ids.CountStrings(); i++) {
- BMessage user;
- user.AddString("user_name", names.StringAt(i));
- user.AddString("user_id", ids.StringAt(i));
- _EnsureUser(&user, false);
- }
- break;
- }
- case IM_ROOM_PARTICIPANT_JOINED:
- {
- BString user_id;
- if (msg->FindString("user_id", &user_id) != B_OK)
- break;
- if (UserById(user_id) == NULL) {
- _EnsureUser(msg, false);
- GetView()->MessageReceived(msg);
- }
- break;
- }
- case IM_ROOM_PARTICIPANT_LEFT:
- case IM_ROOM_PARTICIPANT_KICKED:
- case IM_ROOM_PARTICIPANT_BANNED:
- {
- BString user_id = msg->FindString("user_id");
- User* user;
- if (user_id.IsEmpty() == true || (user = UserById(user_id)) == NULL)
- break;
- GetView()->MessageReceived(msg);
- RemoveUser(user);
- break;
- }
- case IM_ROOM_ROLECHANGED:
- {
- BString user_id;
- Role* role = _GetRole(msg);
- if (msg->FindString("user_id", &user_id) != B_OK || role == NULL)
- break;
- SetRole(user_id, role);
- GetView()->MessageReceived(msg);
- break;
- }
- case IM_USER_NICKNAME_SET:
- {
- BString user_id = msg->FindString("user_id");
- BString user_name = msg->FindString("user_name");
- if (user_id.IsEmpty() == false && user_name.IsEmpty() == false) {
- User* user = UserById(user_id);
- BString text(B_TRANSLATE("** %old% has changed their nick to %new%."));
- text.ReplaceAll("%new%", user_name);
- if (user != NULL)
- text.ReplaceAll("%old%", user->GetName());
- else
- text.ReplaceAll("%old%", user_id);
- BMessage* notify = new BMessage(IM_MESSAGE);
- notify->AddInt32("im_what", IM_MESSAGE_RECEIVED);
- notify->AddString("body", text);
- GetView()->MessageReceived(notify);
- }
- break;
- }
- case IM_LOGS_RECEIVED:
- default:
- GetView()->MessageReceived(msg);
- }
- }
- void
- Conversation::ObserveString(int32 what, BString str)
- {
- GetView()->InvalidateUserList();
- }
- void
- Conversation::ObserveInteger(int32 what, int32 value)
- {
- if (what == INT_WINDOW_FOCUSED) {
- fNotifyMessageCount = 0;
- fNotifyMentionCount = 0;
- }
- else
- GetView()->InvalidateUserList();
- }
- void
- Conversation::ObservePointer(int32 what, void* ptr)
- {
- GetView()->InvalidateUserList();
- }
- void
- Conversation::SetNotifyName(const char* name)
- {
- if (BString(name) == fName)
- return;
- fName = name;
- NotifyString(STR_ROOM_NAME, fName.String());
- }
- void
- Conversation::SetNotifySubject(const char* subject)
- {
- if (BString(subject) == fSubject)
- return;
- fSubject = subject;
- NotifyString(STR_ROOM_SUBJECT, fSubject.String());
- }
- bool
- Conversation::SetNotifyIconBitmap(BBitmap* icon)
- {
- if (icon != NULL) {
- fIcon = icon;
- NotifyPointer(PTR_ROOM_BITMAP, (void*)icon);
- return true;
- }
- return false;
- }
- BMessenger
- Conversation::Messenger() const
- {
- return fMessenger;
- }
- void
- Conversation::SetMessenger(BMessenger messenger)
- {
- fMessenger = messenger;
- }
- ProtocolLooper*
- Conversation::GetProtocolLooper() const
- {
- return fLooper;
- }
- void
- Conversation::SetProtocolLooper(ProtocolLooper* looper)
- {
- fLooper = looper;
- _LoadRoomFlags();
- }
- BBitmap*
- Conversation::ProtocolBitmap() const
- {
- ChatProtocol* protocol = fLooper->Protocol();
- ChatProtocolAddOn* addOn
- = ProtocolManager::Get()->ProtocolAddOn(protocol->Signature());
- return addOn->ProtoIcon();
- }
- BBitmap*
- Conversation::IconBitmap() const
- {
- return fIcon;
- }
- BString
- Conversation::GetName() const
- {
- return fName;
- }
- BString
- Conversation::GetSubject() const
- {
- return fSubject;
- }
- ConversationView*
- Conversation::GetView()
- {
- if (fChatView != NULL)
- return fChatView;
- fChatView = new ConversationView(this);
- fChatView->RegisterObserver(fConversationItem);
- fChatView->RegisterObserver(this);
- RegisterObserver(fChatView);
- if (!(fRoomFlags & ROOM_POPULATE_LOGS))
- return fChatView;
- BMessage logMsg;
- if (_GetChatLogs(&logMsg) == B_OK)
- fChatView->MessageReceived(&logMsg);
- return fChatView;
- }
- void
- Conversation::ShowView(bool typing, bool userAction)
- {
- ((TheApp*)be_app)->GetMainWindow()->SetConversation(this);
- }
- ConversationItem*
- Conversation::GetListItem()
- {
- return fConversationItem;
- }
- UserMap
- Conversation::Users()
- {
- return fUsers;
- }
- User*
- Conversation::UserById(BString id)
- {
- bool found = false;
- return fUsers.ValueFor(id, &found);
- }
- void
- Conversation::AddUser(User* user)
- {
- if (user == NULL)
- return;
- BMessage msg;
- msg.AddString("user_id", user->GetId());
- msg.AddString("user_name", user->GetName());
- _EnsureUser(&msg, false);
- _SortConversationList();
- }
- void
- Conversation::RemoveUser(User* user)
- {
- if (user == NULL)
- return;
- fUsers.RemoveItemFor(user->GetId());
- user->UnregisterObserver(this);
- GetView()->UpdateUserList(fUsers);
- _SortConversationList();
- _UpdateIcon();
- NotifyInteger(INT_ROOM_MEMBERS, fUsers.CountItems());
- }
- Contact*
- Conversation::GetOwnContact()
- {
- return fLooper->GetOwnContact();
- }
- void
- Conversation::SetRole(BString id, Role* role)
- {
- Role* oldRole = fRoles.ValueFor(id);
- if (oldRole != NULL) {
- fRoles.RemoveItemFor(id);
- delete oldRole;
- }
- if (role != NULL)
- fRoles.AddItem(id, role);
- }
- Role*
- Conversation::GetRole(BString id)
- {
- return fRoles.ValueFor(id);
- }
- void
- Conversation::SetFlags(int32 flags)
- {
- fRoomFlags = flags;
- _CacheRoomFlags();
- }
- void
- Conversation::_WarnUser(BString message)
- {
- BMessage* warning = new BMessage(IM_MESSAGE);
- warning->AddInt32("im_what", IM_MESSAGE_RECEIVED);
- warning->AddString("body", message.Append('\n', 1).InsertChars("-- ", 0));
- GetView()->MessageReceived(warning);
- }
- void
- Conversation::_LogChatMessage(BMessage* msg)
- {
- // Binary logs
- // TODO: Don't hardcode 31, expose maximum as a setting
- const int32 MAX = 31;
- BMessage logMsg(IM_MESSAGE);
- if (_GetChatLogs(&logMsg) != B_OK) {
- logMsg.what = IM_MESSAGE;
- logMsg.AddInt32("im_what", IM_LOGS_RECEIVED);
- }
- BMessage last;
- if (logMsg.FindMessage("message", MAX, &last) == B_OK)
- logMsg.RemoveData("message", 0);
- msg->AddInt64("when", time(NULL));
- logMsg.AddMessage("message", msg);
- BFile logFile(fCachePath.Path(), B_READ_WRITE | B_OPEN_AT_END | B_CREATE_FILE);
- WriteAttributeMessage(&logFile, "Chat:logs", &logMsg);
- // Plain-text logs
- // Gotta make sure the formatting's pretty!
- BString date;
- fDateFormatter.Format(date, time(0), B_SHORT_DATE_FORMAT, B_MEDIUM_TIME_FORMAT);
- BString id = msg->FindString("user_id");
- BString name = msg->FindString("user_name");
- BString body = msg->FindString("body");
- if (id.IsEmpty() == true && name.IsEmpty() == true)
- return;
- else if (name.IsEmpty() == true) {
- User* user = UserById(id);
- name = user ? user->GetName() : id;
- }
- BString logLine("[");
- logLine << date << "] <" << name << "> " << body << "\n";
- logFile.Write(logLine.String(), logLine.Length());
- }
- status_t
- Conversation::_GetChatLogs(BMessage* msg)
- {
- _EnsureCachePath();
- BFile logFile(fCachePath.Path(), B_READ_WRITE | B_CREATE_FILE);
- return ReadAttributeMessage(&logFile, "Chat:logs", msg);
- }
- void
- Conversation::_CacheRoomFlags()
- {
- _EnsureCachePath();
- BFile cacheFile(fCachePath.Path(), B_READ_WRITE | B_CREATE_FILE);
- if (cacheFile.InitCheck() != B_OK)
- return;
- cacheFile.WriteAttr("Chat:flags", B_INT32_TYPE, 0, &fRoomFlags, sizeof(int32));
- }
- void
- Conversation::_LoadRoomFlags()
- {
- _EnsureCachePath();
- BFile cacheFile(fCachePath.Path(), B_READ_ONLY);
- if (cacheFile.InitCheck() != B_OK)
- return;
- cacheFile.ReadAttr("Chat:flags", B_INT32_TYPE, 0, &fRoomFlags, sizeof(int32));
- }
- void
- Conversation::_EnsureCachePath()
- {
- if (fCachePath.InitCheck() == B_OK)
- return;
- fCachePath.SetTo(RoomCachePath(fLooper->Protocol()->GetName(),
- fID.String()));
- }
- User*
- Conversation::_EnsureUser(BMessage* msg, bool implicit)
- {
- BString id = msg->FindString("user_id");
- BString name = msg->FindString("user_name");
- if (id.IsEmpty() == true) return NULL;
- User* user = UserById(id);
- User* serverUser = fLooper->UserById(id);
- // Not here, but found in server
- if (user == NULL && serverUser != NULL)
- user = serverUser;
- // Not anywhere; create user
- else if (user == NULL) {
- user = new User(id, _GetServer()->Looper());
- user->SetProtocolLooper(fLooper);
- fLooper->AddUser(user);
- }
- // It's been implicitly defined (rather than explicit join), shame!
- if (UserById(id) == NULL && implicit == true) {
- fGuests.Add(id);
- // The response to this will be used to determine if this guest stays
- BMessage msg(IM_MESSAGE);
- msg.AddInt32("im_what", IM_GET_ROOM_PARTICIPANTS);
- msg.AddString("chat_id", fID);
- fLooper->MessageReceived(&msg);
- }
- if (UserById(id) == NULL) {
- fUsers.AddItem(id, user);
- fUsers.AddItem(id, user);
- GetView()->UpdateUserList(fUsers);
- _UpdateIcon(user);
- NotifyInteger(INT_ROOM_MEMBERS, fUsers.CountItems());
- }
- if (name.IsEmpty() == false && user->GetName() != name)
- user->SetNotifyName(name);
- user->RegisterObserver(this);
- return user;
- }
- Role*
- Conversation::_GetRole(BMessage* msg)
- {
- if (!msg)
- return NULL;
- BString title;
- int32 perms;
- int32 priority;
- if (msg->FindString("role_title", &title) != B_OK
- || msg->FindInt32("role_perms", &perms) != B_OK
- || msg->FindInt32("role_priority", &priority) != B_OK)
- return NULL;
- return new Role(title, perms, priority);
- }
- void
- Conversation::_UpdateIcon(User* user)
- {
- if (_IsDefaultIcon(fIcon) == false && fUserIcon == false)
- return;
- // If it's a one-on-one chat, try to use the other user's icon
- if (user != NULL && fUsers.CountItems() == 2
- && user->GetId() != GetOwnContact()->GetId()
- && _IsDefaultIcon(user->AvatarBitmap()) == false) {
- fUserIcon = SetNotifyIconBitmap(user->AvatarBitmap());
- return;
- }
- switch (fUsers.CountItems())
- {
- case 0:
- case 1:
- SetNotifyIconBitmap(ImageCache::Get()->GetImage("kOnePersonIcon"));
- break;
- case 2:
- SetNotifyIconBitmap(ImageCache::Get()->GetImage("kTwoPeopleIcon"));
- break;
- case 3:
- SetNotifyIconBitmap(ImageCache::Get()->GetImage("kThreePeopleIcon"));
- break;
- case 4:
- SetNotifyIconBitmap(ImageCache::Get()->GetImage("kFourPeopleIcon"));
- break;
- default:
- SetNotifyIconBitmap(ImageCache::Get()->GetImage("kMorePeopleIcon"));
- break;
- }
- fUserIcon = false;
- }
- bool
- Conversation::_IsDefaultIcon(BBitmap* icon)
- {
- return (icon == NULL
- || icon == ImageCache::Get()->GetImage("kPersonIcon")
- || icon == ImageCache::Get()->GetImage("kOnePersonIcon")
- || icon == ImageCache::Get()->GetImage("kTwoPeopleIcon")
- || icon == ImageCache::Get()->GetImage("kThreePeopleIcon")
- || icon == ImageCache::Get()->GetImage("kFourPeopleIcon")
- || icon == ImageCache::Get()->GetImage("kMorePeopleIcon"));
- }
- void
- Conversation::_SortConversationList()
- {
- if (fUsers.CountItems() <= 2 || fUsers.CountItems() == 3)
- ((TheApp*)be_app)->GetMainWindow()->SortConversation(this);
- }
- Server*
- Conversation::_GetServer()
- {
- return ((TheApp*)be_app)->GetMainWindow()->GetServer();
- }
|