//*********************************************************** // Copyright 2003-2008 Alexander S. Kiselev, Valentin Pavlyuchenko // // This file is part of Boltun. // // Boltun is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 2 of the License, or // (at your option) any later version. // // Boltun is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Boltun. If not, see . // //*********************************************************** #include "stdafx.h" #include "newpluginapi.h" #include "m_database.h" #include "m_system.h" #include "m_protosvc.h" using namespace std; extern TalkBot* bot; typedef void(*ActionHandler)(MCONTACT hContact, const TalkBot::MessageInfo *inf); typedef struct _QueueElement { MCONTACT hContact; const TalkBot::MessageInfo *inf; ActionHandler Handler; bool Sticky; int TimeOffset; _QueueElement(MCONTACT contact, ActionHandler handler, int timeOffset, const TalkBot::MessageInfo *info = nullptr, bool sticky = false) :hContact(contact), Handler(handler), TimeOffset(timeOffset), inf(info), Sticky(sticky) { } } QueueElement; static list actionQueue; static set typingContacts; UINT_PTR timerID = 0; mir_cs cs; mir_cs typingContactsLock; void UpdateTimer(); VOID CALLBACK TimerProc(HWND, UINT, UINT_PTR, DWORD) { mir_cslockfull lck(cs); QueueElement q = actionQueue.front(); actionQueue.pop_front(); UpdateTimer(); lck.unlock(); q.Handler(q.hContact, q.inf); } void UpdateTimer() { if (timerID) KillTimer(nullptr, timerID); if (actionQueue.size()) timerID = SetTimer(nullptr, 0, actionQueue.front().TimeOffset, TimerProc); else timerID = 0; } static bool NotifyTyping(MCONTACT hContact) { int res = db_get_b(hContact, "SRMsg", "SupportTyping", 2); if (res == 2) res = db_get_b(0, "SRMsg", "DefaultTyping", 1); return res != 0; } static void TimerAnswer(MCONTACT hContact, const TalkBot::MessageInfo* info) { T2Utf msg(info->Answer.c_str()); ProtoChainSend(hContact, PSS_MESSAGE, 0, (LPARAM)msg); DBEVENTINFO dbei = {}; dbei.cbBlob = (int)mir_strlen(msg); dbei.pBlob = msg; dbei.eventType = EVENTTYPE_MESSAGE; dbei.flags = DBEF_SENT | DBEF_UTF; dbei.szModule = BOLTUN_NAME; dbei.timestamp = (DWORD)time(0); db_event_add(hContact, &dbei); bot->AnswerGiven(hContact, *info); delete info; mir_cslock lck(typingContactsLock); typingContacts.erase(hContact); } static void StartTyping(MCONTACT hContact, const TalkBot::MessageInfo*) { CallService(MS_PROTO_SELFISTYPING, hContact, PROTOTYPE_SELFTYPING_ON); mir_cslock lck(typingContactsLock); typingContacts.insert(hContact); } void DoAnswer(MCONTACT hContact, const TalkBot::MessageInfo *info, bool sticky = false) { if (info->Answer[0] == '\0') return; int waitTime, thinkTime = 0; int defWaitTime = Config.AnswerPauseTime * 1000; if (Config.PauseDepends) waitTime = defWaitTime * (int)info->Answer.length() / 25; else waitTime = defWaitTime; if (Config.PauseRandom) { //Let it be up to 4 times longer. waitTime = waitTime * (rand() % 300) / 100 + waitTime; } if (waitTime == 0) waitTime = 50; //it's essential, because otherwise message will be added later //then its response, that will cause incorrect ordering of //messages in the opened history (reopening will //help, but anyway it's no good) if (NotifyTyping(hContact) && Config.AnswerThinkTime) { thinkTime = Config.AnswerThinkTime * 1000; if (Config.PauseRandom) { //Let it be up to 4 times longer. thinkTime = thinkTime * (rand() % 300) / 100 + thinkTime; } } mir_cslock lck(cs); //Check if this contact's timer handler is now waiting for a cs. bool needTimerRearrange = false; if (!actionQueue.empty() && actionQueue.front().hContact == hContact) { needTimerRearrange = true; KillTimer(nullptr, timerID); } if (!actionQueue.empty()) { list::iterator it = actionQueue.end(); --it; while (true) { if ((*it).hContact == hContact) { if ((*it).Sticky) break; list::iterator tmp = it; if (tmp != actionQueue.begin()) --tmp; actionQueue.erase(it); it = tmp; if (actionQueue.empty()) break; } if (it == actionQueue.begin()) break; --it; } } { mir_cslock tcl(typingContactsLock); if (typingContacts.find(hContact) != typingContacts.end()) { CallService(MS_PROTO_SELFISTYPING, hContact, (LPARAM)PROTOTYPE_SELFTYPING_OFF); typingContacts.erase(hContact); } } if (actionQueue.empty()) needTimerRearrange = true; if (thinkTime) actionQueue.push_back(QueueElement(hContact, StartTyping, thinkTime, nullptr, sticky)); actionQueue.push_back(QueueElement(hContact, TimerAnswer, waitTime, info, sticky)); if (needTimerRearrange) UpdateTimer(); } void AnswerToContact(MCONTACT hContact, const wchar_t* messageToAnswer) { if (Config.TalkWarnContacts && g_plugin.getByte(hContact, DB_CONTACT_WARNED, FALSE) == FALSE) { DoAnswer(hContact, new TalkBot::MessageInfo((const wchar_t*)Config.WarnText), true); g_plugin.setByte(hContact, DB_CONTACT_WARNED, TRUE); } else DoAnswer(hContact, bot->Reply(hContact, messageToAnswer, false)); } void StartChatting(MCONTACT hContact) { DoAnswer(hContact, new TalkBot::MessageInfo(bot->GetInitMessage(hContact)), true); }