//***********************************************************
// 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(NULL, "SRMsg", "DefaultTyping", 1);
return res != 0;
}
static void TimerAnswer(MCONTACT hContact, const TalkBot::MessageInfo* info)
{
T2Utf msg(info->Answer.c_str());
size_t bufsize = mir_strlen(msg);
ProtoChainSend(hContact, PSS_MESSAGE, 0, (LPARAM)msg);
DBEVENTINFO dbei = {};
dbei.cbBlob = (int)bufsize;
dbei.pBlob = (PBYTE)(char*)msg;
dbei.eventType = EVENTTYPE_MESSAGE;
dbei.flags = DBEF_SENT;
dbei.szModule = BOLTUN_NAME;
dbei.timestamp = (DWORD)time(nullptr);
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 && db_get_b(hContact, BOLTUN_KEY,
DB_CONTACT_WARNED, FALSE) == FALSE)
{
DoAnswer(hContact, new TalkBot::MessageInfo((const wchar_t*)Config.WarnText), true);
db_set_b(hContact, BOLTUN_KEY, 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);
}