/* Chat module plugin for Miranda IM Copyright 2000-12 Miranda IM, 2012-14 Miranda NG project, all portions of this codebase are copyrighted to the people listed in contributors.txt. This program 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. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "..\..\core\commonheaders.h" #include "chat.h" int GetRichTextLength(HWND hwnd) { GETTEXTLENGTHEX gtl; gtl.flags = GTL_PRECISE; gtl.codepage = CP_ACP; return (int)SendMessage(hwnd, EM_GETTEXTLENGTHEX, (WPARAM)>l, 0); } TCHAR* RemoveFormatting(const TCHAR* pszWord) { static TCHAR szTemp[10000]; int i = 0; int j = 0; if (pszWord == 0 || pszWord[0] == 0) return NULL; while (j < 9999 && i <= lstrlen(pszWord)) { if (pszWord[i] == '%') { switch (pszWord[i + 1]) { case '%': szTemp[j] = '%'; j++; i++; i++; break; case 'b': case 'u': case 'i': case 'B': case 'U': case 'I': case 'r': case 'C': case 'F': i++; i++; break; case 'c': case 'f': i += 4; break; default: szTemp[j] = pszWord[i]; j++; i++; break; } } else { szTemp[j] = pszWord[i]; j++; i++; } } return (TCHAR*)&szTemp; } BOOL DoTrayIcon(SESSION_INFO *si, GCEVENT *gce) { switch (gce->pDest->iType) { case GC_EVENT_MESSAGE | GC_EVENT_HIGHLIGHT: case GC_EVENT_ACTION | GC_EVENT_HIGHLIGHT: ci.AddEvent(si->hContact, LoadSkinnedIcon(SKINICON_EVENT_MESSAGE), GC_FAKE_EVENT, 0, TranslateT("%s wants your attention in %s"), gce->ptszNick, si->ptszName); break; case GC_EVENT_MESSAGE: ci.AddEvent(si->hContact, ci.hIcons[ICON_MESSAGE], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s speaks in %s"), gce->ptszNick, si->ptszName); break; case GC_EVENT_ACTION: ci.AddEvent(si->hContact, ci.hIcons[ICON_ACTION], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s speaks in %s"), gce->ptszNick, si->ptszName); break; case GC_EVENT_JOIN: ci.AddEvent(si->hContact, ci.hIcons[ICON_JOIN], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s has joined %s"), gce->ptszNick, si->ptszName); break; case GC_EVENT_PART: ci.AddEvent(si->hContact, ci.hIcons[ICON_PART], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s has left %s"), gce->ptszNick, si->ptszName); break; case GC_EVENT_QUIT: ci.AddEvent(si->hContact, ci.hIcons[ICON_QUIT], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s has disconnected"), gce->ptszNick); break; case GC_EVENT_NICK: ci.AddEvent(si->hContact, ci.hIcons[ICON_NICK], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s is now known as %s"), gce->ptszNick, gce->ptszText); break; case GC_EVENT_KICK: ci.AddEvent(si->hContact, ci.hIcons[ICON_KICK], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s kicked %s from %s"), gce->ptszStatus, gce->ptszNick, si->ptszName); break; case GC_EVENT_NOTICE: ci.AddEvent(si->hContact, ci.hIcons[ICON_NOTICE], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("Notice from %s"), gce->ptszNick); break; case GC_EVENT_TOPIC: ci.AddEvent(si->hContact, ci.hIcons[ICON_TOPIC], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("Topic change in %s"), si->ptszName); break; case GC_EVENT_INFORMATION: ci.AddEvent(si->hContact, ci.hIcons[ICON_INFO], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("Information in %s"), si->ptszName); break; case GC_EVENT_ADDSTATUS: ci.AddEvent(si->hContact, ci.hIcons[ICON_ADDSTATUS], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s enables '%s' status for %s in %s"), gce->ptszText, gce->ptszStatus, gce->ptszNick, si->ptszName); break; case GC_EVENT_REMOVESTATUS: ci.AddEvent(si->hContact, ci.hIcons[ICON_REMSTATUS], GC_FAKE_EVENT, CLEF_ONLYAFEW, TranslateT("%s disables '%s' status for %s in %s"), gce->ptszText, gce->ptszStatus, gce->ptszNick, si->ptszName); break; } return TRUE; } ///////////////////////////////////////////////////////////////////////////////////////// static void __stdcall ShowRoomFromPopup(void * pi) { SESSION_INFO *si = (SESSION_INFO*)pi; ci.ShowRoom(si, WINDOW_VISIBLE, TRUE); } static LRESULT CALLBACK PopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_COMMAND: if (HIWORD(wParam) == STN_CLICKED) { SESSION_INFO *si = (SESSION_INFO*)PUGetPluginData(hWnd); CallFunctionAsync(ShowRoomFromPopup, si); PUDeletePopup(hWnd); return TRUE; } break; case WM_CONTEXTMENU: SESSION_INFO *si = (SESSION_INFO*)PUGetPluginData(hWnd); if (si->hContact) if (CallService(MS_CLIST_GETEVENT, (WPARAM)si->hContact, 0)) CallService(MS_CLIST_REMOVEEVENT, (WPARAM)si->hContact, (LPARAM)GC_FAKE_EVENT); if (si->hWnd && KillTimer(si->hWnd, TIMERID_FLASHWND)) FlashWindow(si->hWnd, FALSE); PUDeletePopup(hWnd); break; } return DefWindowProc(hWnd, message, wParam, lParam); } int ShowPopup(MCONTACT hContact, SESSION_INFO *si, HICON hIcon, char* pszProtoName, TCHAR* pszRoomName, COLORREF crBkg, const TCHAR* fmt, ...) { static TCHAR szBuf[4 * 1024]; if (!fmt || fmt[0] == 0 || lstrlen(fmt) > 2000) return 0; va_list marker; va_start(marker, fmt); mir_vsntprintf(szBuf, 4096, fmt, marker); va_end(marker); POPUPDATAT pd = { 0 }; pd.lchContact = hContact; if (hIcon) pd.lchIcon = hIcon; else pd.lchIcon = LoadIconEx("window", FALSE); PROTOACCOUNT *pa = ProtoGetAccount(pszProtoName); mir_sntprintf(pd.lptzContactName, MAX_CONTACTNAME - 1, _T("%s - %s"), (pa == NULL) ? _A2T(pszProtoName) : pa->tszAccountName, cli.pfnGetContactDisplayName(hContact, 0)); lstrcpyn(pd.lptzText, TranslateTS(szBuf), MAX_SECONDLINE); pd.iSeconds = g_Settings->iPopupTimeout; if (g_Settings->iPopupStyle == 2) { pd.colorBack = 0; pd.colorText = 0; } else if (g_Settings->iPopupStyle == 3) { pd.colorBack = g_Settings->crPUBkgColour; pd.colorText = g_Settings->crPUTextColour; } else { pd.colorBack = g_Settings->crLogBackground; pd.colorText = crBkg; } pd.PluginWindowProc = PopupDlgProc; pd.PluginData = si; return PUAddPopupT(&pd); } BOOL DoPopup(SESSION_INFO *si, GCEVENT *gce) { switch (gce->pDest->iType) { case GC_EVENT_MESSAGE | GC_EVENT_HIGHLIGHT: ci.ShowPopup(si->hContact, si, LoadSkinnedIcon(SKINICON_EVENT_MESSAGE), si->pszModule, si->ptszName, ci.aFonts[16].color, TranslateT("%s says: %s"), gce->ptszNick, RemoveFormatting(gce->ptszText)); break; case GC_EVENT_ACTION | GC_EVENT_HIGHLIGHT: ci.ShowPopup(si->hContact, si, LoadSkinnedIcon(SKINICON_EVENT_MESSAGE), si->pszModule, si->ptszName, ci.aFonts[16].color, _T("%s %s"), gce->ptszNick, RemoveFormatting(gce->ptszText)); break; case GC_EVENT_MESSAGE: ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_MESSAGE], si->pszModule, si->ptszName, ci.aFonts[9].color, TranslateT("%s says: %s"), gce->ptszNick, RemoveFormatting(gce->ptszText)); break; case GC_EVENT_ACTION: ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_ACTION], si->pszModule, si->ptszName, ci.aFonts[15].color, _T("%s %s"), gce->ptszNick, RemoveFormatting(gce->ptszText)); break; case GC_EVENT_JOIN: ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_JOIN], si->pszModule, si->ptszName, ci.aFonts[3].color, TranslateT("%s has joined"), gce->ptszNick); break; case GC_EVENT_PART: if (!gce->ptszText) ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_PART], si->pszModule, si->ptszName, ci.aFonts[4].color, TranslateT("%s has left"), gce->ptszNick); else ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_PART], si->pszModule, si->ptszName, ci.aFonts[4].color, TranslateT("%s has left (%s)"), gce->ptszNick, RemoveFormatting(gce->ptszText)); break; case GC_EVENT_QUIT: if (!gce->ptszText) ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_QUIT], si->pszModule, si->ptszName, ci.aFonts[5].color, TranslateT("%s has disconnected"), gce->ptszNick); else ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_QUIT], si->pszModule, si->ptszName, ci.aFonts[5].color, TranslateT("%s has disconnected (%s)"), gce->ptszNick, RemoveFormatting(gce->ptszText)); break; case GC_EVENT_NICK: ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_NICK], si->pszModule, si->ptszName, ci.aFonts[7].color, TranslateT("%s is now known as %s"), gce->ptszNick, gce->ptszText); break; case GC_EVENT_KICK: if (!gce->ptszText) ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_KICK], si->pszModule, si->ptszName, ci.aFonts[6].color, TranslateT("%s kicked %s"), (char *)gce->ptszStatus, gce->ptszNick); else ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_KICK], si->pszModule, si->ptszName, ci.aFonts[6].color, TranslateT("%s kicked %s (%s)"), (char *)gce->ptszStatus, gce->ptszNick, RemoveFormatting(gce->ptszText)); break; case GC_EVENT_NOTICE: ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_NOTICE], si->pszModule, si->ptszName, ci.aFonts[8].color, TranslateT("Notice from %s: %s"), gce->ptszNick, RemoveFormatting(gce->ptszText)); break; case GC_EVENT_TOPIC: if (!gce->ptszNick) ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_TOPIC], si->pszModule, si->ptszName, ci.aFonts[11].color, TranslateT("The topic is '%s'"), RemoveFormatting(gce->ptszText)); else ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_TOPIC], si->pszModule, si->ptszName, ci.aFonts[11].color, TranslateT("The topic is '%s' (set by %s)"), RemoveFormatting(gce->ptszText), gce->ptszNick); break; case GC_EVENT_INFORMATION: ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_INFO], si->pszModule, si->ptszName, ci.aFonts[12].color, _T("%s"), RemoveFormatting(gce->ptszText)); break; case GC_EVENT_ADDSTATUS: ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_ADDSTATUS], si->pszModule, si->ptszName, ci.aFonts[13].color, TranslateT("%s enables '%s' status for %s"), gce->ptszText, (char *)gce->ptszStatus, gce->ptszNick); break; case GC_EVENT_REMOVESTATUS: ci.ShowPopup(si->hContact, si, ci.hIcons[ICON_REMSTATUS], si->pszModule, si->ptszName, ci.aFonts[14].color, TranslateT("%s disables '%s' status for %s"), gce->ptszText, (char *)gce->ptszStatus, gce->ptszNick); break; } return TRUE; } BOOL DoSoundsFlashPopupTrayStuff(SESSION_INFO *si, GCEVENT *gce, BOOL bHighlight, int bManyFix) { if (!gce || !si || gce->bIsMe || si->iType == GCW_SERVER) return FALSE; BOOL bInactive = si->hWnd == NULL || GetForegroundWindow() != si->hWnd; int iEvent = gce->pDest->iType; if (bHighlight) { gce->pDest->iType |= GC_EVENT_HIGHLIGHT; if (bInactive || !g_Settings->bSoundsFocus) SkinPlaySound("ChatHighlight"); if (db_get_b(si->hContact, "CList", "Hidden", 0) != 0) db_unset(si->hContact, "CList", "Hidden"); if (bInactive) ci.DoTrayIcon(si, gce); if (bInactive || !g_Settings->bPopupInactiveOnly) ci.DoPopup(si, gce); if (ci.OnFlashHighlight) ci.OnFlashHighlight(si, bInactive); return TRUE; } // do blinking icons in tray if (bInactive || !g_Settings->bTrayIconInactiveOnly) ci.DoTrayIcon(si, gce); // stupid thing to not create multiple popups for a QUIT event for instance if (bManyFix == 0) { // do popups if (bInactive || !g_Settings->bPopupInactiveOnly) ci.DoPopup(si, gce); // do sounds and flashing switch (iEvent) { case GC_EVENT_JOIN: if (bInactive || !g_Settings->bSoundsFocus) SkinPlaySound("ChatJoin"); break; case GC_EVENT_PART: if (bInactive || !g_Settings->bSoundsFocus) SkinPlaySound("ChatPart"); break; case GC_EVENT_QUIT: if (bInactive || !g_Settings->bSoundsFocus) SkinPlaySound("ChatQuit"); break; case GC_EVENT_ADDSTATUS: case GC_EVENT_REMOVESTATUS: if (bInactive || !g_Settings->bSoundsFocus) SkinPlaySound("ChatMode"); break; case GC_EVENT_KICK: if (bInactive || !g_Settings->bSoundsFocus) SkinPlaySound("ChatKick"); break; case GC_EVENT_MESSAGE: if (bInactive || !g_Settings->bSoundsFocus) SkinPlaySound("ChatMessage"); if (bInactive && !(si->wState & STATE_TALK)) { si->wState |= STATE_TALK; db_set_w(si->hContact, si->pszModule, "ApparentMode", (LPARAM)(WORD)40071); } if (ci.OnFlashWindow) ci.OnFlashWindow(si, bInactive); break; case GC_EVENT_ACTION: if (bInactive || !g_Settings->bSoundsFocus) SkinPlaySound("ChatAction"); break; case GC_EVENT_NICK: if (bInactive || !g_Settings->bSoundsFocus) SkinPlaySound("ChatNick"); break; case GC_EVENT_NOTICE: if (bInactive || !g_Settings->bSoundsFocus) SkinPlaySound("ChatNotice"); break; case GC_EVENT_TOPIC: if (bInactive || !g_Settings->bSoundsFocus) SkinPlaySound("ChatTopic"); break; } } return TRUE; } int GetColorIndex(const char *pszModule, COLORREF cr) { MODULEINFO *pMod = ci.MM_FindModule(pszModule); int i = 0; if (!pMod || pMod->nColorCount == 0) return -1; for (i = 0; i < pMod->nColorCount; i++) if (pMod->crColors[i] == cr) return i; return -1; } // obscure function that is used to make sure that any of the colors // passed by the protocol is used as fore- or background color // in the messagebox. THis is to vvercome limitations in the richedit // that I do not know currently how to fix void CheckColorsInModule(const char *pszModule) { MODULEINFO *pMod = ci.MM_FindModule(pszModule); int i = 0; COLORREF crFG; COLORREF crBG = (COLORREF)db_get_dw(NULL, CHAT_MODULE, "ColorMessageBG", GetSysColor(COLOR_WINDOW)); LoadMsgDlgFont(17, NULL, &crFG); if (!pMod) return; for (i = 0; i < pMod->nColorCount; i++) { if (pMod->crColors[i] == crFG || pMod->crColors[i] == crBG) { if (pMod->crColors[i] == RGB(255, 255, 255)) pMod->crColors[i]--; else pMod->crColors[i]++; } } } const TCHAR* my_strstri(const TCHAR* s1, const TCHAR* s2) { int i, j, k; for (i = 0; s1[i]; i++) for (j = i, k = 0; _totlower(s1[j]) == _totlower(s2[k]); j++, k++) if (!s2[k + 1]) return s1 + i; return NULL; } BOOL IsHighlighted(SESSION_INFO *si, GCEVENT *gce) { if (!g_Settings->bHighlightEnabled || !g_Settings->pszHighlightWords || !gce || !si || !si->pMe) return FALSE; TCHAR *p1 = g_Settings->pszHighlightWords; TCHAR *p2 = NULL; const TCHAR* p3 = gce->ptszText; if (p3 == NULL) return FALSE; static TCHAR szWord1[1000]; static TCHAR szWord2[1000]; static TCHAR szTrimString[] = _T(":,.!?;\'>)"); // compare word for word while (*p1 != '\0') { // find the next/first word in the highlight word string // skip 'spaces' be4 the word while (*p1 == ' ' && *p1 != '\0') p1 += 1; //find the end of the word p2 = _tcschr(p1, ' '); if (!p2) p2 = _tcschr(p1, '\0'); if (p1 == p2) return FALSE; // copy the word into szWord1 lstrcpyn(szWord1, p1, p2 - p1 > 998 ? 999 : p2 - p1 + 1); p1 = p2; // replace %m with the users nickname p2 = _tcschr(szWord1, '%'); if (p2 && p2[1] == 'm') { TCHAR szTemp[50]; p2[1] = 's'; lstrcpyn(szTemp, szWord1, 999); mir_sntprintf(szWord1, SIZEOF(szWord1), szTemp, si->pMe->pszNick); } // time to get the next/first word in the incoming text string while (*p3 != '\0') { // skip 'spaces' be4 the word while (*p3 == ' ' && *p3 != '\0') p3 += 1; //find the end of the word p2 = (TCHAR *)_tcschr(p3, ' '); if (!p2) p2 = (TCHAR *)_tcschr(p3, '\0'); if (p3 != p2) { // eliminate ending character if needed if (p2 - p3 > 1 && _tcschr(szTrimString, p2[-1])) p2 -= 1; // copy the word into szWord2 and remove formatting lstrcpyn(szWord2, p3, p2 - p3 > 998 ? 999 : p2 - p3 + 1); // reset the pointer if it was touched because of an ending character if (*p2 != '\0' && *p2 != ' ') p2 += 1; p3 = p2; // compare the words, using wildcards if (wildcmpit(RemoveFormatting(szWord2), szWord1)) return TRUE; } } p3 = gce->ptszText; } return FALSE; } BOOL LogToFile(SESSION_INFO *si, GCEVENT *gce) { TCHAR szBuffer[4096]; TCHAR szLine[4096]; TCHAR p = '\0'; szBuffer[0] = '\0'; GetChatLogsFilename(si, gce->time); BOOL bFileJustCreated = !PathFileExists(si->pszLogFileName); TCHAR tszFolder[MAX_PATH]; _tcscpy(tszFolder, si->pszLogFileName); PathRemoveFileSpec(tszFolder); if (!PathIsDirectory(tszFolder)) CreateDirectoryTreeT(tszFolder); TCHAR szTime[100]; lstrcpyn(szTime, ci.MakeTimeStamp(g_Settings->pszTimeStampLog, gce->time), 99); FILE *hFile = _tfopen(si->pszLogFileName, _T("ab+")); if (hFile == NULL) return FALSE; TCHAR szTemp[512], szTemp2[512]; TCHAR* pszNick = NULL; if (bFileJustCreated) fputws((const wchar_t*)"\377\376", hFile); //UTF-16 LE BOM == FF FE if (gce->ptszNick) { if (g_Settings->bLogLimitNames && lstrlen(gce->ptszNick) > 20) { lstrcpyn(szTemp2, gce->ptszNick, 20); lstrcpyn(szTemp2 + 20, _T("..."), 4); } else lstrcpyn(szTemp2, gce->ptszNick, 511); if (gce->ptszUserInfo) mir_sntprintf(szTemp, SIZEOF(szTemp), _T("%s (%s)"), szTemp2, gce->ptszUserInfo); else mir_sntprintf(szTemp, SIZEOF(szTemp), _T("%s"), szTemp2); pszNick = szTemp; } switch (gce->pDest->iType) { case GC_EVENT_MESSAGE: case GC_EVENT_MESSAGE | GC_EVENT_HIGHLIGHT: p = '*'; mir_sntprintf(szBuffer, SIZEOF(szBuffer), _T("%s: %s"), gce->ptszNick, ci.RemoveFormatting(gce->ptszText)); break; case GC_EVENT_ACTION: case GC_EVENT_ACTION | GC_EVENT_HIGHLIGHT: p = '*'; mir_sntprintf(szBuffer, SIZEOF(szBuffer), _T("%s %s"), gce->ptszNick, ci.RemoveFormatting(gce->ptszText)); break; case GC_EVENT_JOIN: p = '>'; mir_sntprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s has joined"), pszNick); break; case GC_EVENT_PART: p = '<'; if (!gce->ptszText) mir_sntprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s has left"), pszNick); else mir_sntprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s has left (%s)"), pszNick, ci.RemoveFormatting(gce->ptszText)); break; case GC_EVENT_QUIT: p = '<'; if (!gce->ptszText) mir_sntprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s has disconnected"), pszNick); else mir_sntprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s has disconnected (%s)"), pszNick, ci.RemoveFormatting(gce->ptszText)); break; case GC_EVENT_NICK: p = '^'; mir_sntprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s is now known as %s"), gce->ptszNick, gce->ptszText); break; case GC_EVENT_KICK: p = '~'; if (!gce->ptszText) mir_sntprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s kicked %s"), gce->ptszStatus, gce->ptszNick); else mir_sntprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s kicked %s (%s)"), gce->ptszStatus, gce->ptszNick, ci.RemoveFormatting(gce->ptszText)); break; case GC_EVENT_NOTICE: p = 'o'; mir_sntprintf(szBuffer, SIZEOF(szBuffer), TranslateT("Notice from %s: %s"), gce->ptszNick, ci.RemoveFormatting(gce->ptszText)); break; case GC_EVENT_TOPIC: p = '#'; if (!gce->ptszNick) mir_sntprintf(szBuffer, SIZEOF(szBuffer), TranslateT("The topic is '%s'"), ci.RemoveFormatting(gce->ptszText)); else mir_sntprintf(szBuffer, SIZEOF(szBuffer), TranslateT("The topic is '%s' (set by %s)"), ci.RemoveFormatting(gce->ptszText), gce->ptszNick); break; case GC_EVENT_INFORMATION: p = '!'; mir_sntprintf(szBuffer, SIZEOF(szBuffer), _T("%s"), ci.RemoveFormatting(gce->ptszText)); break; case GC_EVENT_ADDSTATUS: p = '+'; mir_sntprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s enables '%s' status for %s"), gce->ptszText, gce->ptszStatus, gce->ptszNick); break; case GC_EVENT_REMOVESTATUS: p = '-'; mir_sntprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s disables '%s' status for %s"), gce->ptszText, gce->ptszStatus, gce->ptszNick); break; } // formatting strings don't need to be translatable - changing them via language pack would // only screw up the log format. if (p) mir_sntprintf(szLine, SIZEOF(szLine), _T("%s %c %s\r\n"), szTime, p, szBuffer); else mir_sntprintf(szLine, SIZEOF(szLine), _T("%s %s\r\n"), szTime, szBuffer); if (szLine[0]) { _fputts(szLine, hFile); if (g_Settings->LoggingLimit > 0) { fseek(hFile, 0, SEEK_END); long dwSize = ftell(hFile); rewind(hFile); long trimlimit = g_Settings->LoggingLimit * 1024; if (dwSize > trimlimit) { time_t now = time(0); TCHAR tszTimestamp[20]; _tcsftime(tszTimestamp, 20, _T("%Y%m%d-%H%M%S"), _localtime32((__time32_t *)&now)); tszTimestamp[19] = 0; // max size reached, rotate the log // move old logs to /archived sub folder just inside the log root folder. // add a time stamp to the file name. TCHAR tszDrive[_MAX_DRIVE], tszDir[_MAX_DIR], tszName[_MAX_FNAME], tszExt[_MAX_EXT]; _tsplitpath(si->pszLogFileName, tszDrive, tszDir, tszName, tszExt); TCHAR tszNewPath[_MAX_DRIVE + _MAX_DIR + _MAX_FNAME + _MAX_EXT + 20]; mir_sntprintf(tszNewPath, _MAX_DRIVE + _MAX_DIR + _MAX_FNAME + _MAX_EXT + 20, _T("%s%sarchived\\"), tszDrive, tszDir); CreateDirectoryTreeT(tszNewPath); TCHAR tszNewName[_MAX_DRIVE + _MAX_DIR + _MAX_FNAME + _MAX_EXT + 20]; mir_sntprintf(tszNewName, _MAX_DRIVE + _MAX_DIR + _MAX_FNAME + _MAX_EXT + 20, _T("%s%s-%s%s"), tszNewPath, tszName, tszTimestamp, tszExt); fclose(hFile); hFile = 0; if (!PathFileExists(tszNewName)) CopyFile(si->pszLogFileName, tszNewName, TRUE); DeleteFile(si->pszLogFileName); } } } if (hFile) fclose(hFile); return TRUE; } BOOL DoEventHookAsync(HWND hwnd, const TCHAR *pszID, const char *pszModule, int iType, TCHAR* pszUID, TCHAR* pszText, INT_PTR dwItem) { SESSION_INFO *si = ci.SM_FindSession(pszID, pszModule); if (si == NULL) return FALSE; GCDEST *gcd = (GCDEST*)mir_calloc(sizeof(GCDEST)); gcd->pszModule = mir_strdup(pszModule); gcd->ptszID = mir_tstrdup(pszID); gcd->iType = iType; GCHOOK *gch = (GCHOOK*)mir_calloc(sizeof(GCHOOK)); gch->ptszUID = mir_tstrdup(pszUID); gch->ptszText = mir_tstrdup(pszText); gch->dwData = dwItem; gch->pDest = gcd; PostMessage(hwnd, GC_FIREHOOK, 0, (LPARAM)gch); return TRUE; } BOOL DoEventHook(const TCHAR *pszID, const char *pszModule, int iType, const TCHAR *pszUID, const TCHAR* pszText, INT_PTR dwItem) { SESSION_INFO *si = ci.SM_FindSession(pszID, pszModule); if (si == NULL) return FALSE; GCDEST gcd = { (char*)pszModule, pszID, iType }; GCHOOK gch = { 0 }; gch.ptszUID = (LPTSTR)pszUID; gch.ptszText = (LPTSTR)pszText; gch.dwData = dwItem; gch.pDest = &gcd; NotifyEventHooks(ci.hSendEvent, 0, (WPARAM)&gch); return TRUE; } BOOL IsEventSupported(int eventType) { // Supported events switch (eventType) { case GC_EVENT_JOIN: case GC_EVENT_PART: case GC_EVENT_QUIT: case GC_EVENT_KICK: case GC_EVENT_NICK: case GC_EVENT_NOTICE: case GC_EVENT_MESSAGE: case GC_EVENT_TOPIC: case GC_EVENT_INFORMATION: case GC_EVENT_ACTION: case GC_EVENT_ADDSTATUS: case GC_EVENT_REMOVESTATUS: case GC_EVENT_CHUID: case GC_EVENT_CHANGESESSIONAME: case GC_EVENT_ADDGROUP: case GC_EVENT_SETITEMDATA: case GC_EVENT_GETITEMDATA: case GC_EVENT_SETSBTEXT: case GC_EVENT_ACK: case GC_EVENT_SENDMESSAGE: case GC_EVENT_SETSTATUSEX: case GC_EVENT_CONTROL: case GC_EVENT_SETCONTACTSTATUS: return TRUE; } // Other events return FALSE; } void ValidateFilename(TCHAR *filename) { TCHAR *p1 = filename; TCHAR szForbidden[] = _T("\\/:*?\"<>|"); while (*p1 != '\0') { if (_tcschr(szForbidden, *p1)) *p1 = '_'; p1 += 1; } } static TCHAR tszOldTimeStamp[30]; TCHAR* GetChatLogsFilename(SESSION_INFO *si, time_t tTime) { if (!tTime) time(&tTime); // check whether relevant parts of the timestamp have changed and // we have to reparse the filename TCHAR *tszNow = ci.MakeTimeStamp(_T("%a%d%m%Y"), tTime); // once a day if (_tcscmp(tszOldTimeStamp, tszNow)) { _tcsncpy_s(tszOldTimeStamp, tszNow, _TRUNCATE); *si->pszLogFileName = 0; } if (si->pszLogFileName[0] == 0) { REPLACEVARSARRAY rva[11]; rva[0].lptzKey = _T("d"); rva[0].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%#d"), tTime)); // day 01-31 rva[1].lptzKey = _T("dd"); rva[1].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%d"), tTime)); // month 1-12 rva[2].lptzKey = _T("m"); rva[2].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%#m"), tTime)); // month 01-12 rva[3].lptzKey = _T("mm"); rva[3].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%m"), tTime)); // month text short rva[4].lptzKey = _T("mon"); rva[4].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%b"), tTime)); // month text rva[5].lptzKey = _T("month"); rva[5].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%B"), tTime)); // year 01-99 rva[6].lptzKey = _T("yy"); rva[6].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%y"), tTime)); // year 1901-9999 rva[7].lptzKey = _T("yyyy"); rva[7].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%Y"), tTime)); // weekday short rva[8].lptzKey = _T("wday"); rva[8].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%a"), tTime)); // weekday rva[9].lptzKey = _T("weekday"); rva[9].lptzValue = mir_tstrdup(ci.MakeTimeStamp(_T("%A"), tTime)); // end of array rva[10].lptzKey = NULL; rva[10].lptzValue = NULL; TCHAR tszTemp[MAX_PATH], *ptszVarPath; if (g_Settings->pszLogDir[lstrlen(g_Settings->pszLogDir) - 1] == '\\') { mir_sntprintf(tszTemp, SIZEOF(tszTemp), _T("%s%s"), g_Settings->pszLogDir, _T("%userid%.log")); ptszVarPath = tszTemp; } else ptszVarPath = g_Settings->pszLogDir; REPLACEVARSDATA dat = { sizeof(dat) }; dat.dwFlags = RVF_TCHAR; dat.hContact = si->hContact; dat.variables = rva; TCHAR *tszParsedName = (TCHAR*)CallService(MS_UTILS_REPLACEVARS, (WPARAM)ptszVarPath, (LPARAM)&dat); if (ci.OnGetLogName) ci.OnGetLogName(si, tszParsedName); else PathToAbsoluteT(tszParsedName, si->pszLogFileName); mir_free(tszParsedName); for (int i = 0; i < SIZEOF(rva); i++) mir_free(rva[i].lptzValue); for (TCHAR *p = si->pszLogFileName + 2; *p; ++p) if (*p == ':' || *p == '*' || *p == '?' || *p == '"' || *p == '<' || *p == '>' || *p == '|') *p = _T('_'); } return si->pszLogFileName; }