You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
RnQ/RnQ/utilLib.pas

3818 lines
109 KiB
Plaintext

{
This file is part of R&Q.
Under same license
}
unit utilLib;
{$I RnQConfig.inc}
interface
uses
Windows, SysUtils, Graphics, Dialogs, Classes, ExtCtrls, SyncObjs, Threading,
Forms, StdCtrls, Controls, Menus, ComCtrls, Messages, Types, JSON, DateUtils,
VirtualTrees, StrUtils, globalLib, outboxLib, RnQNet, RDGlobal, RnQZip,
events, ICQCommon, ICQConsts, ICQContacts, ICQSession, Murmur2, GR32;
{$I NoRTTI.inc}
type
TPageControl = class(comctrls.TPageControl)
protected
procedure CNDrawitem(var Message: TWMDrawItem); message CN_DRAWITEM;
end;
TImgBytes = array of TBytes;
TJSONHelper = class helper for TJSONValue
public
function GetValueSafe(const Key: String; out Data: T): Boolean;
end;
function OnlFeature(const pr: TICQSession; check: Boolean = True): Boolean;
procedure UnsupportedFeature;
procedure ProcessOevent(oe: Toevent);
function GetShiftState: Integer;
function ExitFromAutoaway: Boolean;
function binToStatus(const bin: TBytes): byte;
function binToXStatus(const bin: TBytes): byte;
procedure drawHint(cnv: Tcanvas; kind: integer; groupid: integer; c: TICQContact; var r: Trect; calcOnly: Boolean = False);
function unexistant(const uin: TUID): Boolean;
function isAbort(const pluginReply: AnsiString): Boolean;
procedure reloadCurrentLang();
function ShowUsers: TUID;
function CheckAccPass(const uid: TUID; const db: String; var pPass: String): Boolean;
function CheckHistPass: Boolean;
function getLeadingInMsg(const s: string; ofs: integer = 1): string;
procedure applyCommonSettings(c: Tcomponent);
procedure applyUserCharset(f: Tfont);
{$IFDEF Use_Baloons}
procedure ShowBalloonEv(ev: Thevent);
{$ENDIF Use_Baloons}
function AddToNIL(c: TICQContact; IsBulk: Boolean = False): Boolean;
procedure NILifNIL(c: TICQContact; IsBulk: Boolean = False);
function behactionName(a: Tbehaction): string;
function DoLock: Boolean;
procedure DoUnlock;
procedure trayAction;
function chopAndRealizeEvent: Boolean;
procedure realizeEvents(const kind_: integer; c: TICQContact);
procedure realizeEvent(ev: Thevent);
procedure removeSWhotkeys;
function updateSWhotkeys: Boolean;
procedure hideTaskButtonIfUhave2;
function behave(ev: Thevent; kind: integer = -1 { ; const info: Ansistring='' } ): Boolean;
procedure startTimer;
procedure stopMainTimer;
procedure contactCreation(c: TICQContact);
procedure contactDestroying(c: TICQContact);
procedure clearDB(db: TRnQCList);
procedure freeDB(var db: TRnQCList);
function IsSpam(var Wrd: String; c: TICQContact; const Msg: String = ''; Flags: DWord = 0): Boolean;
function FilterRefuse(c: TICQContact; const Msg: String = ''; Flags: DWord = 0; ev: THevent = nil): Boolean;
function rosterImgNameFor(c: TICQContact): TPicName;
procedure setAppBarSize;
procedure Check4Update;
procedure CheckUpdates;
function applyVars(c: TICQContact; const s: String; fromAM: Boolean = False): String;
function getXStatusMsgFor(c: TICQContact): string;
procedure toggleOnlyOnline;
procedure OpenURL(const pURL: String); OverLoad;
procedure OpenICQURL(const pURL: String);
function enterUinDlg(const proto: TICQSession; var uin: TUID; const title: string = ''): Boolean;
procedure SendProtoMsg(var oe: Toevent);
procedure AddOutgoingMessage(Cnt: TICQContact; const Text: String; const Binary: RawByteString; Flags: DWord);
procedure SendEmail2Mail(const email: String);
function childParent(child, parent: integer): Boolean;
procedure myBeep;
function findViewInfo(c: TICQContact): TRnQViewInfoForm;
procedure sortCL(cl: TRnQCList);
procedure sortCLbyGroups(cl: TRnQCList);
procedure UpdateViewInfo(c: TICQContact);
procedure openSendContacts(dest: TICQContact);
function isEmailAddress(const s: string; start: integer): integer;
procedure notAvailable;
// strings
function mb(q: extended): string;
procedure onlyDigits(obj: Tobject); overload;
// icq communication
function AddToIgnoreList(c: TICQContact; const LocalOnly: Boolean = False): Boolean;
procedure RemoveFromIgnorelist(c: TICQContact);
function addToQuietlist(c: TICQContact): Boolean;
procedure removeFromQuietlist(c: TICQContact);
procedure RemoveFromRoster(c: TICQContact; const WithHistory: Boolean = False);
function AddToRoster(c: TICQContact; IsLocal: Boolean = False): boolean; overload;
function AddToRoster(c: TICQContact; group: Integer; IsLocal: Boolean = True): Boolean; overload;
function DoConnect: Boolean;
// convert
function ints2cl(proto: TICQSession; a: TintegerDynArray): TRnQCList;
function statusDrawExt(const DC: HDC; const x, y: integer; const s: byte; const inv: Boolean = False): TSize;
function beh2str(kind: integer): RawByteString;
procedure str2beh(const b, s: RawByteString); overload;
function str2beh(s: AnsiString): Tbehaviour; overload;
function str2status(const s: AnsiString): byte;
function str2visibility(const s: AnsiString): Tvisibility;
function db2strU(db: TRnQCList): RawByteString;
// window management
procedure ToggleMainFormBorder(SetBrdr: Boolean = False; IsBrdr: Boolean = True);
procedure applySnap();
procedure MainFormHandleUpdate;
procedure dockSet(var r: Trect); overload;
procedure dockSet; overload;
procedure fixWindowPos(frm: Tform);
procedure showAuthreq(c: TICQContact; msg: string);
function viewTextWindow(const title, body: string): Tform;
procedure HideForm(Frm: Tform);
procedure ShowForm(WhatForm: TWhatForm; const Page: String = ''; Mode: TfrmViewMode = vmFull; who: TICQContact = nil); overload;
function PrefIsVisiblePage(const pf: String): Boolean;
procedure restoreForeWindow;
procedure applyTransparency(forced: integer = -1);
//procedure applyDocking(Undock: Boolean = False);
function whatStatusPanel(statusbar: Tstatusbar; x: integer): integer;
// file management
function delSUBtree(subPath: string): Boolean;
function deltree(path: string): Boolean;
function deleteFromTo(const fn: string; from, to_: integer): Boolean;
procedure saveAllListsSync(const uPath: String; const pr: TICQSession; pProxys: Tarrproxy);
procedure saveAllListsAsync(const uPath: String; const pr: TICQSession; pProxys: Tarrproxy);
function saveAllLists(const uPath: String; const pr: TICQSession; pProxys: Tarrproxy): Boolean;
function loadDB(zp: TZipFile; pCheckGroups: Boolean): Boolean;
procedure loadLists(const pr: TICQSession; zp: TZipFile; const uPath: String);
procedure LoadExtSts(zp: TZipFile);
procedure loadSpamQuests(zp: TZipFile);
procedure LoadProxies(zp: TZipFile; var pProxys: Tarrproxy);
procedure loadOutInBox(zp: TZipFile);
function openSaveDlg(parent: Tform; const Cptn: String; IsOpen: Boolean; const ext: string = ''; const extCptn: String = '';
const defFile: String = ''; MultiSelect: Boolean = False): string;
procedure incDBTimer;
function UnFakeUIN(uin: Int64): TUID;
function str2sortby(const s: AnsiString): TsortBy;
procedure CheckBDays;
function GetWidth(chk: TCheckBox): integer;
function StringFromFile(const FileName: TFileName): RawByteString;
function parseMsgImages(const imgStr: RawByteString): TBytes;
procedure getMsgImages(imgs: TBytes; var imgList: TImgBytes);
procedure CacheType(const url, ctype: RawByteString);
function CacheImage(var mem: TMemoryStream; const url, ext: RawByteString): Boolean;
procedure UpdatePrefsFrm;
function ParseJSON(const RespStr: String; out JSON: TJSONObject): Boolean; overload;
function ParseJSON(const RespStr: String; out JSON: TJSONArray): Boolean; overload;
procedure ProcessICQLink(Data: String);
// costants for files
const
DBFK_OLDUIN = 00;
DBFK_NICK = 01;
DBFK_FIRST = 02;
DBFK_LAST = 03;
DBFK_EMAIL = 04;
DBFK_CITY = 05;
DBFK_STATE = 06;
DBFK_ABOUT = 07;
DBFK_DISPLAY = 08;
DBFK_QUERY = 09;
DBFK_ZIP = 10;
// DBFK_COUNTRY = 11;
DBFK_BIRTH = 12;
// DBFK_LANG = 13;
DBFK_HOMEPAGE = 14;
DBFK_CELLULAR = 15;
DBFK_IP = 16;
DBFK_AGE = 17;
DBFK_GMT = 18;
DBFK_GENDER = 19;
DBFK_GROUP = 20;
DBFK_LASTUPDATE = 21;
DBFK_LASTONLINE = 22;
// DBFK_LASTMSG = 23; DON'T USE, it was badly updated
DBFK_LASTMSG = 24;
DBFK_NOTES = 25;
DBFK_DONTDELETE = 26;
DBFK_ASKEDAUTH = 27;
DBFK_MEMBERSINCE = 28;
DBFK_ONLINESINCE = 29;
DBFK_SMSABLE = 30;
DBFK_NODB = 31;
DBFK_SENDTRANSL = 32;
DBFK_INTERESTS = 33;
DBFK_WORKPAGE = 34;
DBFK_WORKSTNT = 35; // <20><>
DBFK_WORKDEPT = 36; // <20><>
DBFK_WORKCOMPANY = 37; // <20><>
DBFK_WORKCOUNTRY = 38;
DBFK_WORKZIP = 39;
DBFK_WORKADDRESS = 40;
DBFK_WORKPHONE = 41;
DBFK_WORKSTATE = 42;
DBFK_WORKCITY = 43;
DBFK_SMSMOBILE = 44;
DBFK_UTYPE = 110;
DBFK_UID = 111;
DBFK_BIRTHL = 112;
DBFK_SSIID = 113;
DBFK_Authorized = 114;
DBFK_ssNoteStr = 115;
DBFK_ICONSHOW = 116;
DBFK_ICONMD5 = 117;
DBFK_ssNickname = 118;
DBFK_ssCell = 119;
DBFK_MARSTATUS = 120;
DBFK_lclNoteStr = 121;
DBFK_ZODIAC = 122;
DBFK_qippwd = 123;
DBFK_LASTBDINFORM = 124;
DBFK_LASTINFOCHG = 125;
DBFK_ADDRESS = 126;
// DBFK_BIRTHCOUNTRY = 127;
DBFK_BIRTHSTATE = 128;
DBFK_BIRTHCITY = 129;
DBFK_REGULAR = 130;
DBFK_ssCell2 = 131;
DBFK_ssCell3 = 132;
DBFK_ssCell4 = 142;
// New types
DBFK_COUNTRY = 45;
DBFK_LANG1 = 46;
DBFK_LANG2 = 47;
DBFK_LANG3 = 48;
DBFK_BIRTHCOUNTRY = 133;
DBFK_BIRTHADDRESS = 134;
DBFK_BIRTHZIP = 135;
DBFK_ISLOCAL = 136;
DBFK_OFFICIAL = 137;
DBFK_DELETED = 143;
DBFK_ICONCOLOR_AB = 138;
DBFK_ICONCOLOR_AT = 139;
DBFK_ICONCOLOR_PB = 140;
DBFK_ICONCOLOR_PT = 141;
var
ListsCS: TCriticalSection;
implementation
uses
ShellAPI, Themes, DwmApi, Math, UITypes,
{$IFDEF UNICODE}
AnsiStrings, Character,
{$ENDIF UNICODE}
Base64, RQUtil, RDFileUtil, RDUtils, RnQSysUtils,
RQMenuItem, RQThemes, RQLog, RnQDialogs,
RnQLangs, RnQButtons, RnQBinUtils, RnQGlobal, RnQCrypt, RnQPics,
RnQTrayLib, RnQTips, Hook,
prefSheet, RnQPrefsLib,
mainDlg, roasterLib, iniLib, pluginutil,
chatDlg, selectContactsDlg, incapsulate,
pluginLib, authreqDlg,
langLib, groupsLib, outboxDlg, // msgsDlg,
history,
wpDlg, RnQMacros,
usersDlg, ThemesLib, RnQStrings,
Protocols_All, Protocol_ICQ, // ICQClients,
RnQGraphics32, Stickers, Nodes,
// AsyncCalls,
menusUnit, HistAllSearch, SynCrypto;
function str2sortby(const s: AnsiString): TsortBy;
begin
result := low(TsortBy);
while result <= high(TsortBy) do
if s = sortby2str[result] then
exit
else
inc(result);
result := SB_EVENT;
end; // str2sortby
procedure OpenURL(const pURL: String);
begin
RnQSysUtils.OpenURL(pURL, UseDefaultBrowser, BrowserCmdLine);
end;
procedure OpenICQURL(const pURL: String);
begin
Account.AccProto.OpenICQURL(pURL);
end;
procedure onlyDigits(obj: Tobject);
var
i: integer;
begin
if obj is Tcustomedit then
with (obj as Tcustomedit) do
begin
i := selstart;
text := onlyDigits(text);
if i > length(text) then
i := length(text);
selstart := i;
end;
end; // onlyDigits
procedure loadLists(const pr: TICQSession; zp: TZipFile; const uPath: String);
var
zipLists: Boolean;
function LoadZorF(const fn: String): RawByteString;
var
i: integer;
begin
if zipLists then
begin
i := zp.IndexOf(fn);
if i >= 0 then
result := zp.data[i]
else
result := '';
end else
result := loadFileA(uPath + fn);
end;
var
s: RawByteString;
i: integer;
begin
// backward compatibility
renamefile(uPath + 'uin.list', uPath + uinlistFilename);
i := -1;
zipLists := False;
if Assigned(zp) then
try
i := zp.IndexOf(rosterFileName1);
if i >= 0 then
s := zp.data[i];
except
i := -1;
s := '';
end;
if i >= 0 then
begin
zipLists := True;
end else
s := loadFileA(uPath + rosterFileName1);
pr.readList(LT_ROSTER).fromString(s, pr.contactsDB);
notInlist.fromString(LoadZorF(nilFilename1), TICQSession.ContactsDB);
notInlist.remove(pr.readList(LT_ROSTER));
IgnoreList.fromString(LoadZorF(ignoreFilename1), TICQSession.ContactsDB);
quietList.fromString(LoadZorF(quietFileName1), TICQSession.ContactsDB);
uinlists.fromString(LoadZorF(uinlistFilename));
retrieveQ.fromString(LoadZorF(retrieveFileName1), TICQSession.ContactsDB);
end; // loadLists
procedure LoadExtSts(zp: TZipFile);
var
k, line, s: RawByteString;
i: integer;
begin
// clear;
// s := loadFile(userPath + extstatusesFilename);
s := loadFromZipOrFile(zp, Account.ProtoPath, extstatusesFilename);
i := 0;
while s > '' do
begin
line := chopLine(s);
k := chop(AnsiString('='), line);
k := trim(k);
line := trim(line);
if isOnlyDigits(k) then
try
i := strToInt(k);
if i < length(ExtStsStrings) then
begin
ExtStsStrings[i].Cap := '';
ExtStsStrings[i].Desc := '';
end;
except
i := -1;
// setlength(a,length(a)-1);
end
else
// if (i >= Low(XStatus6))and(i <= High(XStatus6)) then
if (i >= Low(ExtStsStrings)) and (i <= High(ExtStsStrings)) then
if k = 'caption' then
try
ExtStsStrings[i].Cap := Copy(UnUTF(line), 1, MaxXStatusLen);
except
end
else if k = 'desc' then
try
ExtStsStrings[i].Desc := StringReplace(Copy(UnUTF(line), 1, MaxXStatusDescLen), AnsiString('\n'), CRLF,
[rfReplaceAll]);
except
end;
end;
end;
procedure loadSpamQuests(zp: TZipFile);
var
k, line, s: RawByteString;
i, j: integer;
begin
// clear;
s := loadFromZipOrFile(zp, Account.ProtoPath, SpamQuestsFilename);
i := -1;
// i := 0;
while s > '' do
begin
line := chopLine(s);
k := trim(chop('=', line));
line := trim(line);
{ if isOnlyDigits(k) then
try
i := strToInt(k);
except
// setlength(a,length(a)-1);
end
else }
if k = 'question' then
try
i := length(SpamFilter.Quests);
SetLength(SpamFilter.Quests, i + 1);
SpamFilter.Quests[i].q := StringReplace(UnUTF(line), AnsiString('\n'), CRLF, [rfReplaceAll]);
except
end
else if k = 'answer' then
try
if i >= 0 then
try
j := Length(SpamFilter.Quests[i].a);
SetLength(SpamFilter.Quests[i].a, j + 1);
SpamFilter.Quests[i].a[j] := UnUTF(line);
except
end;
except
end;
end;
end;
procedure LoadProxies(zp: TZipFile; var pProxys: Tarrproxy);
var
cfg, l, h: RawByteString;
i, ppp: integer;
function yesno: Boolean;
begin
result := comparetext(l, AnsiString('yes')) = 0
end;
// var
// pp : TproxyProto;
begin
// cfg := loadfile(userPath + proxiesFileName);
cfg := loadFromZipOrFile(zp, Account.ProtoPath, proxiesFileName);
i := 0;
ClearProxyArr(pProxys);
// SetLength(pProxys, 0);
// ProxyIDBox.ItemIndex := 0;
while cfg > '' do
begin
l := chop(CRLF, cfg);
h := chop('=', l);
if h = 'proxy-name' then
begin
i := length(pProxys);
SetLength(pProxys, i + 1);
ClearProxy(pProxys[i]);
pProxys[i].name := UnUTF(l);
end
else if length(pProxys) > 0 then
begin
if h = 'proxy-ver5' then
if yesno then
pProxys[i].proto := PP_SOCKS5
else
pProxys[i].proto := PP_SOCKS4
// else if h='proxy' then pProxys[i].enabled:=yesno
else if h = 'proxy-auth' then
pProxys[i].auth := yesno
else if h = 'proxy-user' then
pProxys[i].user := UnUTF(l)
else if h = 'proxy-ntlm' then
pProxys[i].NTLM := yesno
else if h = 'proxy-pass' then
pProxys[i].pwd := UnUTF(passDecrypt(l))
else if h = 'proxy-pass64' then
pProxys[i].pwd := UnUTF(passDecrypt(Base64DecodeString(l)))
else if h = 'proxy-host' then
pProxys[i].addr.host := UnUTF(l)
else if h = 'proxy-port' then
pProxys[i].addr.port := StrToIntDef(l, 0)
else if h = 'proxy-proto' then
begin
ppp := IndexStr(l, proxyproto2str);
if ppp < 0 then
begin
// pProxys[i].enabled:=FALSE;
// pProxys[i].proto:=PP_SOCKS5;
pProxys[i].proto := PP_NONE;
end
else
pProxys[i].proto := TproxyProto(ppp);
end
{ else if Pos('proxy-', h)>0 then
for pp:=low(pp) to high(pp) do
begin
if h='proxy-'+proxyproto2str[pp]+'-host' then proxyes[i].addr[pp].host:=l;
if h='proxy-'+proxyproto2str[pp]+'-port' then proxyes[i].addr[pp].port:=l;
end; }
end
end;
end;
procedure saveAllListsSync(const uPath: String; const pr: TICQSession; pProxys: Tarrproxy);
begin
ListsCS.WaitFor(5000);
ListsCS.Acquire;
saveAllLists(uPath, pr, pProxys);
ListsCS.Release;
end;
procedure saveAllListsAsync(const uPath: String; const pr: TICQSession; pProxys: Tarrproxy);
begin
TTask.Create(procedure
begin
if not running then
Exit;
ListsCS.Acquire;
try
saveAllLists(uPath, pr, pProxys);
finally
ListsCS.Release;
end;
end).Start;
end;
function saveAllLists(const uPath: String; const pr: TICQSession; pProxys: Tarrproxy): Boolean;
const
splitMsg = 'automsg: ';
var
zf: TZipFile;
// ZIP: TZIPWriter;
procedure AddFile2Zip(const fn: String; const cfg: RawByteString);
// var
// fIDX : Integer;
begin
// if cfg > '' then
begin
// fIDX := zf.AddFile(fn);
// fIDX := zf.AddFile(fn, 0, '123');
// fIDX := zf.AddFile(fn, 0, AccPass);
// zf.Data[fIDX] := cfg;
// fIDX :=
zf.AddFile(fn, 0, AccPass, cfg);
end;
// zf.Files[fIDX].CommonFileHeader.VersionNeededToExtract
end;
var
// s : string;
cfg: RawByteString;
sA: AnsiString;
// i : Integer;
k, l: integer;
Saved: Boolean;
memStream: TMemoryStream;
lFileOld, lFileNew, lFileBak: string;
begin
Result := False;
if fantomWork then
Exit;
{
groups.save; -- OK
saveLists(MainProto); -- OK
saveInbox; -- OK
saveOutbox; -- OK
saveCFG; -- OK
saveAutoMessages; -- OK
saveMacros; -- OK
savecommonCFG; -- OK
saveDB; -- OK
saveRetrieveQ; -- OK
if reopenchats then chatFrm.savePages; -- OK
SaveExtSts; -- OK
SaveSpamQuests; -- OK
}
zf := TZipFile.Create;
// i := 0;
// zf.AddFile(dbFileName);
// zf.Data[i] := db2str(contactsDB);
zf.ZipFileComment := 'DB file of R&Q ver.' + IntToStr(RnQBuild);
cfg := db2strU(TICQSession.ContactsDB);
AddFile2Zip(dbFileName, cfg);
cfg :=
AnsiString('account-id=') + UTF(pr.getMyInfo.UID2cmp) + CRLF +
AnsiString('account-name=') + UTF(pr.getMyInfo.displayed);
AddFile2Zip(AboutFileName, cfg);
cfg := '';
AddFile2Zip(groupsFilename, groups.toString);
AddFile2Zip(rosterFileName1, pr.readList(LT_ROSTER).toString);
// msgDlg(getTranslation('Error saving contact list'),mtError);
// msgDlg(getTranslation('Error saving visible list'),mtError);
// msgDlg(getTranslation('Error saving invisible list'),mtError);
AddFile2Zip(ignoreFilename1, IgnoreList.toString);
AddFile2Zip(quietFilename1, quietList.toString);
// msgDlg(getTranslation('Error saving ignore list'),mtError);
// if NILdoWith = 2 then
begin
AddFile2Zip(nilFilename1, notInlist.toString);
// msgDlg(getTranslation('Error saving not-in-list'),mtError);
end;
AddFile2Zip(uinlistFilename, uinlists.toString);
// msgDlg(getTranslation('Error saving uinlists'), mtError);
if Assigned(eventQ) then
AddFile2Zip(inboxFilename, eventQ.toString);
if Assigned(Account.outbox) then
AddFile2Zip(outboxFilename, Account.outbox.toString);
AddFile2Zip(configFileName, getCFG);
AddFile2Zip(macrosFileName, macros2str(macros));
AddFile2Zip(ChatCSSFileName, ChatCSS);
// inc(i);
// zf.AddFile(commonFileName);
// zf.Data[i] := getCommonCFG;
if retrieveQ.empty then
// deleteFile(uPath+retrieveFileName+'.txt')
else
AddFile2Zip(retrieveFileName1, retrieveQ.toString);
if Assigned(chatFrm) then
AddFile2Zip(reopenchatsFileName, chatFrm.Pages2String);
cfg := '';
// for I := low(XStatus6) to High(XStatus6) do
for k := Low(ExtStsStrings) to High(ExtStsStrings) do
begin
cfg := cfg + format(AnsiString('%d=%s' + CRLF + 'caption=%s' + CRLF + 'desc=%s'),
[k, AnsiString(XStatusArray[k].Caption), UTF(ExtStsStrings[k].Cap),
UTF(newline2slashn(ExtStsStrings[k].Desc))]);
// f := f+format(CRLF+'ssi=%d', [a[i].ssiID]);
cfg := cfg + CRLF;
end;
AddFile2Zip(extstatusesFilename, cfg);
cfg := '';
for k := Low(SpamFilter.Quests) to High(SpamFilter.Quests) do
begin
sA := UTF(newline2slashn(SpamFilter.Quests[k].q));
if sA > '' then
begin
cfg := cfg + format(AnsiString('question=%s'), [sA]);
for l := low(SpamFilter.Quests[k].a) to High(SpamFilter.Quests[k].a) do
// cfg := cfg+format(AnsiString(CRLF + 'answer=%s'),[StrToUTF8(SpamFilter.Quests[k].ans[l])]);
cfg := cfg + CRLF + AnsiString('answer=') + UTF(SpamFilter.Quests[k].a[l]);
// f := f+format(CRLF+'ssi=%d', [a[i].ssiID]);
cfg := cfg + CRLF + '**********' + CRLF;
end;
end;
AddFile2Zip(SpamQuestsFilename, cfg);
cfg := '';
if Length(pProxys) > 0 then
for k := 0 to Length(pProxys) - 1 do
begin
if pProxys[k].name = '' then
pProxys[k].name := 'Proxy' + IntToStr(k + 1);
cfg := cfg +
'proxy-name=' + UTF(pProxys[k].name) + CRLF +
'proxy-auth=' + yesno[pProxys[k].auth] + CRLF +
'proxy-user=' + UTF(pProxys[k].user) + CRLF +
'proxy-pass64=' + Base64EncodeString(passCrypt(UTF(pProxys[k].pwd))) + CRLF +
'proxy-ntlm=' + yesno[pProxys[k].NTLM] + CRLF +
'proxy-proto=' + proxyproto2str[pProxys[k].proto] + CRLF +
'proxy-host=' + AnsiString(pProxys[k].addr.host) + CRLF +
'proxy-port=' + IntToStr(pProxys[k].addr.port) + CRLF;
cfg := cfg + '------------------' + CRLF;
end;
AddFile2Zip(proxiesFileName, cfg);
try
// zf.SaveToFile(uPath+dbFileName + '4.new');
memStream := TMemoryStream.Create;
zf.SaveToStream(memStream);
memStream.SaveToFile(uPath + dbFileName + '5.new');
memStream.Free;
Saved := True;
except
msgDlg('Error on saving DB5', True, mtError);
Saved := False;
end;
zf.Free;
if Saved then
try
if FileExists(uPath + dbFileName + '5') then
begin
lFileOld := uPath + dbFileName + '5';
lFileNew := uPath + dbFileName + '5.new';
lFileBak := uPath + dbFileName + '5.bak';
if MakeBackups then
ReplaceFile(PChar(lFileOld), PChar(lFileNew), PChar(lFileBak), REPLACEFILE_IGNORE_MERGE_ERRORS, NIL, NIL)
else
ReplaceFile(PChar(lFileOld), PChar(lFileNew), NIL, REPLACEFILE_IGNORE_MERGE_ERRORS, NIL, NIL)
end else
// DeleteFile(uPath+dbFileName + '5');
renamefile(uPath + dbFileName + '5.new', uPath + dbFileName + '5');
except
// RnQFileUtil.saveFile(userPath+dbFileName, s, True);
msgDlg('Error on saving DB', True, mtError);
end;
// if FileExists(userPath+dbFileName) then
// DeleteFile(userPath+dbFileName);
// if FileExists(userPath+dbFileName + '2') then
// DeleteFile(userPath+dbFileName + '2');
Result := True;
end;
function DoConnect: Boolean;
var
pr: TICQSession;
begin
Result := False;
if not Assigned(Account.AccProto) or not Account.AccProto.IsOffline then
Exit;
Result := True;
if not UseLastStatus then
LastStatus := RnQstartingStatus;
SetProgBar(Account.AccProto, 0.1 / progLogonTotal);
pr := Account.AccProto;
CopyProxy(pr.aProxy, MainProxy);
TICQSession(pr).Connect;
end; // doConnect
function findAuthReq(c: TICQContact): TauthreqFrm;
var
i: integer;
begin
with childWindows do
begin
i := 0;
while i < count do
begin
if Tobject(items[i]) is TauthreqFrm then
begin
result := TauthreqFrm(items[i]);
if result.contact.equals(c) then
exit;
end;
inc(i);
end;
end;
result := NIL;
end; // findAuthreq
function findViewInfo(c: TICQContact): TRnQViewInfoForm;
var
i: integer;
begin
with childWindows do
begin
i := 0;
while i < count do
begin
if Tobject(items[i]) is TRnQViewInfoForm then
begin
Result := TRnQViewInfoForm(items[i]);
if result.contact.equals(c) then
Exit;
end;
inc(i);
end;
end;
Result := nil;
end;
procedure TPageControl.CNDrawitem(var Message: TWMDrawItem);
var
Color1: TColor;
Color2: TColor;
Rect: Trect;
Rgn: HRGN;
hnd: HDC;
p: TPicName;
ss: String;
begin
hnd := Message.DrawItemStruct.HDC;
Rect := Message.DrawItemStruct.rcItem;
SelectClipRgn(hnd, 0);
inc(Rect.Left, 2);
inc(Rect.Top, 2);
inc(Rect.Bottom, 4);
inc(Rect.Right, 2);
case Message.DrawItemStruct.itemState of
1:
begin
Color1 := theme.GetColor('button.bg.selected', clWebLightgrey);
Color2 := theme.GetColor('button.frame.selected', clWebGray);;
FillRect(hnd, Rect, CreateSolidBrush(Color1));
FrameRect(hnd, Rect, CreateSolidBrush(Color2));
end;
end;
with canvas do
begin
inc(Rect.Left, 4);
dec(Rect.Right, 2);
// inc(Rect.Top, 2);
// inc(Rect.bottom, 2);
SetBKMode(hnd, TRANSPARENT);
if (Message.DrawItemStruct.itemState = 1) then
p := 'chat.tab.active'
else
p := 'chat.tab.inactive';
theme.ApplyFont(p, canvas.Font);
ss := Self.Tabs.Strings[Message.DrawItemStruct.itemID];
DrawText(hnd, PChar(ss), length(ss), Rect, DT_SINGLELINE or DT_VCENTER);
end;
Rgn := CreateRectRgn(0, 0, 0, 0);
SelectClipRgn(Message.DrawItemStruct.HDC, Rgn);
DeleteObject(Rgn);
Message.result := 1;
inherited;
end;
function parseMsgImages(const imgStr: RawByteString): TBytes;
var
pos1, pos2: integer;
image: RawByteString;
str: TMemoryStream;
procedure putToStream(const img: RawByteString);
var
OutSize: LongWord;
PIn, POut: Pointer;
Buf: TBytes;
begin
PIn := @img[1];
OutSize := CalcDecodedSize(PIn, Length(img));
str.SetSize(str.Size + SizeOf(OutSize) + OutSize);
str.Write(OutSize, SizeOf(OutSize));
SetLength(Buf, OutSize);
POut := @Buf[0];
Base64Decode(PIn^, Length(img), POut^);
str.Write(Buf, Length(Buf));
end;
begin
str := TMemoryStream.Create;
str.Position := 0;
image := imgStr;
repeat
pos1 := PosEx(RnQImageTag, image);
if (pos1 > 0) then
begin
pos2 := PosEx(RnQImageUnTag, image, pos1 + length(RnQImageTag));
putToStream(Copy(image, pos1 + length(RnQImageTag), pos2 - (pos1 + length(RnQImageTag))));
image := Copy(image, pos2 + length(RnQImageUnTag), length(image));
end else
Break;
until pos1 <= 0;
image := imgStr;
repeat
pos1 := PosEx(RnQImageExTag, image);
if (pos1 > 0) then
begin
pos2 := PosEx(RnQImageExUnTag, image, pos1 + length(RnQImageExTag));
putToStream(Copy(image, pos1 + length(RnQImageExTag), pos2 - (pos1 + length(RnQImageExTag))));
image := Copy(image, pos2 + length(RnQImageExUnTag), length(image));
end else
Break;
until pos1 <= 0;
SetLength(Result, str.Size);
str.Position := 0;
str.Read(Result, str.Size);
str.Free;
end;
procedure getMsgImages(imgs: TBytes; var imgList: TImgBytes);
var
pos: Integer;
size: LongWord;
begin
pos := 0;
if Length(imgs) > 4 then
repeat
try
size := dword_LEat(@imgs[pos]);
if size = 0 then
Break;
Inc(pos, SizeOf(LongWord));
SetLength(imgList, Length(imgList) + 1);
imgList[Length(imgList) - 1] := Copy(imgs, pos, size);
Inc(pos, size);
except
Break;
end;
until pos <= Length(imgs);
end;
function viewTextWindow(const title, body: string): Tform;
var
form: Tform;
memo: Tmemo;
pagectl: TPageControl;
pagetab: TTabSheet;
begin
form := Tform.Create(RnQmain);
result := form;
form.Caption := title;
form.position := poDefaultPosOnly;
pagectl := TPageControl.Create(form);
pagectl.parent := form;
pagectl.Align := alClient;
pagectl.TabPosition := tpTop;
pagectl.DoubleBuffered := True;
pagectl.MultiLine := True;
pagectl.Style := tsButtons;
pagectl.StyleElements := [];
pagectl.OwnerDraw := True;
pagectl.name := 'pagectl';
if (trim(body) <> '') then
begin
pagetab := TTabSheet.Create(pagectl);
pagetab.Caption := getTranslation('message');
pagetab.PageControl := pagectl;
pagetab.BorderWidth := 0;
pagetab.ControlStyle := pagetab.ControlStyle + [csOpaque];
memo := Tmemo.Create(pagetab);
memo.parent := pagetab;
memo.text := body;
memo.Align := alClient;
memo.WordWrap := bViewTextWrap;
memo.borderstyle := bsNone;
if memo.WordWrap then
memo.ScrollBars := ssVertical
else
memo.ScrollBars := ssBoth;
// form.InsertControl(memo);
memo.OnKeyDown := RnQmain.MemoKeyDown;
end;
if (pagectl.PageCount = 1) then
begin
pagectl.Pages[0].TabVisible := False;
pagectl.Pages[0].Visible := True;
end;
form.OnClose := RnQmain.onCloseSomeWindows;
with desktopworkarea(mainDlg.RnQmain.Handle) do
begin
form.Width := (Right - Left) div 3;
form.Height := (Bottom - Top) div 3 + 100;
end;
applyTaskButton(form);
form.OnKeyPress := RnQmain.previewFormKeyPress;
form.KeyPreview := True;
form.show;
end; // viewTextWindow
function openSaveDlg(parent: Tform; const Cptn: String; IsOpen: Boolean; const ext: String = ''; const extCptn: String = '';
const defFile: String = ''; MultiSelect: Boolean = False): string;
var
Filtr: String;
fn: String;
hndl: THandle;
// defDir : String;
begin
if ext > '' then
if extCptn > '' then
Filtr := getTranslation(extCptn) + '|*.' + ext + '|' + getTranslation('All files') + '|*.*'
else
Filtr := '*.' + ext + '|*.' + ext + '|' + getTranslation('All files') + '|*.*'
else
Filtr := getTranslation('All files') + '|*.*';
// if defFile = '' then
// defFile := myPath;
// dlg.options:=[ofFileMustExist,ofEnableSizing];
if parent <> NIL then
hndl := parent.Handle
else
hndl := 0;
fn := ExtractFileName(defFile);
if OpenSaveFileDialog(hndl, ext, Filtr, ExtractFileDir(defFile), getTranslation(Cptn), fn, IsOpen, MultiSelect) then
result := fn
else
result := '';
end; // opendlg
procedure restoreForeWindow;
begin
if oldForewindow = 0 then
Exit;
ForceForegroundWindow(oldForewindow);
oldForewindow := 0;
end; // restoreForeWIndow
procedure applyTransparency(forced: integer = -1);
var
bak: THandle;
begin
if not running then
Exit;
bak := RnQmain.Handle;
RnQmain.alphablend := transparency.forRoster or (forced > 0);
if RnQmain.alphablend then
if forced >= 0 then RnQmain.alphablendvalue := forced else
begin
if RnQmain.Handle = GetForegroundWindow then
RnQmain.alphablendvalue := transparency.active
else
RnQmain.alphablendvalue := transparency.inactive;
end;
chatFrm.alphablend := transparency.forChat or (forced > 0);
if chatFrm.alphablend then
if forced >= 0 then
chatFrm.alphablendvalue := forced
else
if chatFrm.Handle = GetForegroundWindow then
chatFrm.alphablendvalue := transparency.active
else
chatFrm.alphablendvalue := transparency.inactive;
if bak <> RnQmain.Handle then
MainFormHandleUpdate;
end; // applyTransparency
(*
procedure applyDocking(Undock: Boolean = False);
var
r: Trect;
begin
try
if docking.Dock2Chat then
begin
if Undock then
begin
if not RnQmain.Floating then
begin
if Assigned(RnQmain.parent) then
r.TopLeft := RnQmain.parent.ClientToScreen(Types.Point(RnQmain.Left, RnQmain.Top))
else
r.TopLeft := Types.Point(RnQmain.Left, RnQmain.Top);
end;
r.Right := r.Left + RnQmain.Width;
r.Bottom := r.Top + RnQmain.Height;
RnQmain.ManualFloat(r);
end
else
begin
mainDlg.RnQmain.DragKind := dkDock;
chatFrm.CLSplitter.Visible := True;
chatFrm.CLPanel.Visible := True;
if docking.Docked2chat and RnQmain.Floating then
if chatFrm.chats.Count > 0 then
begin
chatFrm.MainFormWidth := RnQmain.Width;
RnQmain.ManualDock(chatFrm.CLPanel);
RnQmain.Visible := True;
end;
end;
end
else
begin
if not RnQmain.Floating then
try
{ if Assigned(chatFrm.DockManager) then
chatFrm.DockManager.RemoveControl(RnQmain)
else
if Assigned(chatFrm.CLPanel.DockManager) then
chatFrm.CLPanel.DockManager.RemoveControl(RnQmain); }
if Assigned(RnQmain.parent) then
r.TopLeft := RnQmain.parent.ClientToScreen(Types.Point(RnQmain.Left, RnQmain.Top))
else
r.TopLeft := Types.Point(RnQmain.Left, RnQmain.Top);
r.Right := r.Left + RnQmain.Width;
r.Bottom := r.Top + RnQmain.Height;
RnQmain.ManualFloat(r);
except
end;
docking.Docked2chat := False;
mainDlg.RnQmain.DragKind := dkDrag;
chatFrm.CLPanel.Visible := False;
chatFrm.CLSplitter.Visible := False;
end;
finally
mainfrmHandleUpdate;
end;
end;
*)
function loadDB(zp: TZipFile; pCheckGroups: Boolean): Boolean;
var
s: RawByteString;
zf: TZipFile;
i: integer;
begin
freeDB(TICQSession.ContactsDB);
s := '';
if Assigned(zp) then
begin
// zf := TZipFile.Create;
try
i := zp.IndexOf(dbFileName);
if i >= 0 then
s := zp.data[i];
except
s := '';
end
end;
if s = '' then
if FileExists(Account.ProtoPath + dbFileName) then
s := loadFileA(Account.ProtoPath + dbFileName)
else
begin
zf := TZipFile.Create;
try
// if FileExists(userPath+dbFileName + '4') then
// begin
// zf.LoadFromFile(userPath+dbFileName + '4');
// i := zf.IndexOf(dbFileName);
// if i >=0 then
// s := zf.Uncompressed[i];
// end
// else
if FileExists(Account.ProtoPath + dbFileName + '3') then
begin
zf.LoadFromFile(Account.ProtoPath + dbFileName + '3');
i := zf.IndexOf(dbFileName);
if i >= 0 then
s := zf.data[i];
end
except
s := '';
end;
zf.Free;
end;
// if FileExists(userPath+dbFileName + '2') then
// s := ZDecompressStrEx(loadFile(userPath+dbFileName + '2'))
// else
// contactsDB:=str2db(Account.AccProto.getContactClass, s, result)
TICQSession.ContactsDB := str2db(Account.AccProto, s, result, pCheckGroups);
TICQSession.ContactsDB.Add(Account.AccProto.MyAccNum)
end; // loadDB
function compContacts(Item1, Item2: Pointer): integer;
begin
result := comparetext(TICQContact(Item1).Displayed, TICQContact(Item2).Displayed)
end;
function compContactsByGroup(Item1, Item2: Pointer): integer;
var
c1, c2: TICQContact;
begin
c1 := TICQContact(Item1);
c2 := TICQContact(Item2);
if c1.group < c2.group then
result := -1
else if c1.group > c2.group then
result := +1
else
result := comparetext(c1.Displayed, c2.Displayed);
end; // compContacts
procedure sortCL(cl: TRnQCList);
begin
cl.sort(compContacts)
end;
procedure sortCLbyGroups(cl: TRnQCList);
begin
cl.sort(compContactsByGroup)
end;
procedure HideForm(Frm: Tform);
begin
if Frm = nil then
Exit;
if Frm = RnQmain then
begin
if FormVisible(RnQmain) then
RnQmain.ToggleVisible;
Exit;
end;
Frm.Hide;
ShowWindow(Application.Handle, SW_HIDE)
end;
function PrefIsVisiblePage(const pf: String): Boolean;
begin
Result := True;
if pf = 'ICQ' then
if not Assigned(Account.AccProto) then
Result := False;
end;
procedure ShowForm(whatForm: TWhatForm; const Page: String = ''; Mode: TfrmViewMode = vmFull; who: TICQContact = nil);
var
frm: ^Tform;
frmclass: TcomponentClass;
i // , actPage
: byte;
arr: array of TPrefPage;
cr: Boolean;
begin
case WhatForm of
WF_SHEET:
begin
frmclass := TPrefSheet;
frm := @PrefSheetFrm
end;
WF_USERS:
begin
CommonMethods.SwitchUser;
Exit;
end;
WF_WP:
begin
frmclass := TwpFrm;
frm := @wpFrm
end;
WF_SEARCH:
begin
frmclass := TAllHistSrchForm;
frm := @AllHistSrchForm;
end;
else
exit;
end;
if frm^ = nil then
begin
cr := True;
application.createForm(frmclass, frm^);
applyCommonSettings(frm^);
translateWindow(frm^);
end else
cr := False;
// actPage := 0;
if WhatForm = WF_SHEET then
begin
SetLength(arr, 0);
if not (Page = '') then
if cr and (Mode <> vmFull) then
for i := 0 to Length(prefPages) - 1 do
if prefPages[i].name = Page then
begin
SetLength(arr, 1);
arr[0] := prefPages[i];
Break;
end;
if cr then
begin
PrefSheetFrm.SetViewMode(arr);
if Mode = vmFull then
PrefSheetFrm.SetActivePage(Page);
end else if not (Page = '') then
PrefSheetFrm.SetActivePage(Page);
SetLength(arr, 0);
end;
if (WhatForm = WF_SEARCH) then
with TAllHistSrchForm(frm^) do
begin
if Assigned(who) then
begin
contact := who;
all := Mode = vmFull;
end
else
begin
contact := nil;
all := True;
end;
if Visible then
DoAfterShow
else
Show;
end;
showForm(frm^);
end; // showPref
function ShowUsers: TUID;
begin
Result := CommonMethods.SwitchUser;
end;
function CheckAccPass(const uid: TUID; const db: String; var pPass: String): Boolean;
begin
Result := False;
pPass := CommonMethods.EnterPassword(GetTranslation('Account password') + ' (' + uid + ')', 16);
if not (pPass = '') then
begin
if CheckZipFilePass(db, dbFileName, pPass) then
Result := True
else
begin
pPass := '';
Result := False;
MsgDlg('Wrong password', True, mtWarning)
end
end
else
begin
Result := False;
//MsgDlg('Please enter password', True, mtWarning)
end;
end;
function CheckHistPass: Boolean;
begin
Result := False;
if histcrypt.enabled and (histcrypt.pwd = '') then
begin
histcrypt.pwd := CommonMethods.EnterPassword(GetTranslation('History password'));
if histcrypt.pwd = '' then
Exit;
end;
Result := True;
end;
procedure UpdateViewInfo(c: TICQContact);
begin
if not UpdateViewInfoQ.exists(c) then
UpdateViewInfoQ.Add(c);
end;
procedure loadOutInBox(zp: TZipFile);
var
s: RawByteString;
i: integer;
zipPref: Boolean;
begin
i := -1;
zipPref := False;
if Assigned(zp) then
try
i := zp.IndexOf(outboxFilename);
if i >= 0 then
s := zp.data[i];
except
i := -1;
s := '';
end;
if i < 0 then
s := loadFileA(Account.ProtoPath + outboxFilename)
else
zipPref := True;
Account.outbox.fromString(s);
if zipPref then
begin
i := zp.IndexOf(inboxFilename);
if i >= 0 then
s := zp.data[i];
end
else
s := loadFileA(Account.ProtoPath + inboxFilename);
eventQ.fromString(s);
eventQ.removeExpiringEvents;
end;
procedure openSendContacts(dest: TICQContact);
var
wnd: TselectCntsFrm;
begin
UnsupportedFeature; Exit;
if not Assigned(dest) then
Exit;
if not (dest is TICQContact) then
Exit;
wnd := TselectCntsFrm.doAll(RnQmain, getTranslation('To %s', [dest.displayed]), getTranslation('Send selected contacts'),
Account.AccProto, notInlist.clone.Add(Account.AccProto.ReadList(LT_ROSTER)), RnQmain.sendContactsAction,
[sco_multi, sco_groups, sco_predefined], @wnd, False, False);
// Theme.getIco2(PIC_CONTACTS, wnd.icon);
theme.pic2ico(RQteFormIcon, PIC_CONTACTS, wnd.Icon);
wnd.extra := Tincapsulate.aString(dest.uid);
end; // openSendContacts
function isEmailAddress(const s: string; start: integer): integer;
// const
// emailChar=['a'..'z','A'..'Z','0'..'9','-','_','.'];
var
j: integer;
existsDot: Boolean;
begin
result := -1;
if CharInSet(s[start], EMAILCHARS) then // chi comincia bene...
begin
// try to find the @
j := start + 1;
while (j < length(s)) and (CharInSet(s[j], EMAILCHARS)) do
inc(j);
if s[j] = '@' then
begin
// @ found, now skip the @ and search for .
inc(j);
existsDot := False;
while (j < length(s)) and (CharInSet(s[j + 1], EMAILCHARS)) do
begin
if s[j] = '.' then
begin
existsDot := True;
Break;
end;
inc(j);
end;
if existsDot and (CharInSet(s[j], EMAILCHARS)) then
// at least a valid char after the . must exists
begin
repeat
inc(j);
until (j > length(s)) or not (CharInSet(s[j], EMAILCHARS));
// go forth till we're out or we meet an invalid char
result := j - 1;
end;
end;
end;
end; // isEmailAddress
procedure NotAvailable;
begin
msgDlg('This feature isn''t available yet.\nCome back tomorrow...', True, mtInformation)
end;
procedure UnsupportedFeature;
begin
msgDlg(Str_Unsupported, True, mtWarning)
end;
function childParent(child, parent: integer): Boolean;
begin
result := True;
repeat
if child = parent then
exit;
child := getParent(child);
until child = 0;
result := parent = 0;
end; // childParent
procedure myBeep;
begin
if playSounds then
beep
end;
function whatStatusPanel(statusbar: Tstatusbar; x: integer): integer;
var
x1: integer;
begin
result := 0;
x1 := statusbar.panels[0].Width;
while (x > x1) and (result < statusbar.panels.count - 1) do
begin
inc(result);
inc(x1, statusbar.panels[result].Width);
end;
end; // whatStatusPanel
procedure SendProtoMsg(var oe: Toevent);
var
SendMsg: String;
i: integer;
vBin: RawByteString;
begin
oe.flags := oe.flags or IF_urgent;
if oe.flags and IF_multiple <> 0 then
oe.flags := oe.flags or IF_noblink and not IF_urgent;
vBin := plugins.castEv(PE_MSG_SENT, oe.whom.uid, oe.flags, oe.info);
if (vBin > '') then
if (byte(vBin[1]) = PM_DATA) then
begin
i := _int_at(vBin, 2);
SendMsg := UnUTF(_istring_at(vBin, 2));
if Length(vBin) >= 1 + 4 + i + 4 then
oe.info := UnUTF(_istring_at(vBin, 2 + 4 + i))
else
oe.info := SendMsg;
end else if (byte(vBin[1]) = PM_ABORT) then
Exit
else begin end
else
SendMsg := oe.info;
Account.AccProto.SendMsg(oe.whom, oe.kind, oe.flags, oe.info, SendMsg);
end; // SendProtoMsg
procedure AddOutgoingMessage(Cnt: TICQContact; const Text: String; const Binary: RawByteString; Flags: DWord);
var
ev: Thevent;
begin
ev := Thevent.new(EK_MSG, Cnt, Account.AccProto.GetMyInfo, Now, UTF(Text), [], Flags, ReqID);
ev.outgoing := True;
if Length(Binary) > 0 then
ev.parseData(Binary);
if logpref.writehistory and (BE_save in behaviour[ev.kind].trig) and (Flags and IF_not_save_hist = 0) then
WriteToHistory(ev);
// if oe.flags and IF_not_show_chat = 0 then
// chatFrm.addEvent_openchat(c, ev.clone);
chatFrm.addEvent(Cnt, ev.clone);
ev.Free;
end;
procedure SendEmail2Mail(const email: String);
begin
if email > '' then
exec('mailto:' + email);
end;
function deleteFromTo(const fn: string; from, to_: integer): Boolean;
begin
result := partDeleteFile(fn, from, to_ - from)
end;
function enterUinDlg(const proto: TICQSession; var uin: TUID; const title: string = ''): Boolean;
var
res: TUID;
ttl: String;
s: String;
// e: integer;
// fUIN : Int64;
begin
if title = '' then
ttl := 'uin'
else
ttl := title;
res := '';
repeat
result := InputQuery(GetTranslation(ttl), GetTranslation('UIN'), s);
res := s;
if result then
begin
res := trim(res);
uin := res;
if proto = nil then
begin
result := False;
if TICQSession._isProtoUid(uin) then
begin
result := True;
uin := res;
end;
end else
result := proto.ValidICQ(uin);
if result then
begin
// uin := res;
Break
end
else
msgDlg('Invalid UIN', True, mtError)
end;
until not result;
end; // enterUinDlg
function AddToRoster(c: TICQContact; IsLocal: Boolean = False): Boolean;
begin
NotInList.Remove(c);
c.CntIsLocal := IsLocal;
Result := Account.AccProto.AddContact(c, IsLocal);
if not Result then
Exit;
roasterlib.Update(c);
roasterLib.Focus(c);
SaveListsDelayed := True;
AutosizeDelayed := True;
plugins.castEvList(PE_LIST_ADD, PL_ROSTER, c);
end; // AddToRoster
function AddToRoster(c: TICQContact; group: Integer; IsLocal: Boolean = True): Boolean;
begin
Result := False;
if c = nil then
Exit;
if group = 2000 then
group := 0;
c.group := group;
if c.group = 0 then
IsLocal := True;
SaveGroupsDelayed := True;
Result := AddToRoster(c, IsLocal);
if not Result then
roasterLib.Update(c);
end; // AddToRoster
function AddToNIL(c: TICQContact; IsBulk: Boolean = False): Boolean;
begin
Result := False;
Account.AccProto.RemoveContact(TICQContact(c));
if not NotInList.Add(c) then
Exit;
if not IsBulk then
begin
roasterLib.update(c);
SaveListsDelayed := True;
end;
plugins.castEvList(PE_LIST_ADD, PL_NIL, c);
Result := True;
end; // addToNIL
procedure NILifNIL(c: TICQContact; IsBulk: Boolean = False);
begin
if Assigned(c) and not c.IsInRoster then
begin
AddToNIL(c, IsBulk);
if not IsBulk and (c is TICQContact) then
if c.InfoUpdatedTo = 0 then
Account.AccProto.GetProfile(c.UID2cmp);
end;
end; // eventFrom
function deltree(path: string): Boolean;
var
sr: TsearchRec;
begin
result := False;
if (path = '') or not directoryExists(path) then
exit;
path := includeTrailingPathDelimiter(path);
if findFirst(path + '*.*', faAnyFile, sr) = 0 then
repeat
if (sr.name <> '.') and (sr.name <> '..') then
if sr.Attr and faDirectory > 0 then
deltree(path + sr.name)
else
deleteFile(path + sr.name);
until findNext(sr) <> 0;
findClose(sr);
// path:=ExcludeTrailingPathDelimiter(path);
result := RemoveDir(path);
end; // deltree
function delSUBtree(subPath: string): Boolean;
var
sr: TsearchRec;
path: String;
begin
result := False;
path := myPath + subPath;
if (subPath = '') or (subPath = PathDelim) or (path = '') or not directoryExists(path) then
exit;
subPath := includeTrailingPathDelimiter(subPath);
path := includeTrailingPathDelimiter(path);
if findFirst(path + '*.*', faAnyFile, sr) = 0 then
repeat
if (sr.name <> '.') and (sr.name <> '..') then
if sr.Attr and faDirectory > 0 then
delSUBtree(subPath + sr.name)
else
deleteFile(path + sr.name);
until findNext(sr) <> 0;
findClose(sr);
// path:=ExcludeTrailingPathDelimiter(path);
result := RemoveDir(path);
end; // deltree
function rosterImgNameFor(c: TICQContact): TPicName;
begin
if notInlist.exists(c) then
Result := Status2ImgName(byte(SC_UNK), False)
else
Result := Account.AccProto.Statuses[c.getStatus].ImageName;
end; // rosterImgNameFor
function statusDrawExt(const DC: HDC; const x, y: integer; const s: byte; const inv: Boolean = False): TSize;
begin
if statusPics[s, inv].picName = '' then
begin
statusPics[s, inv].picName := status2imgName(s, inv);
statusPics[s, inv].pEnabled := True;
statusPics[s, inv].ThemeToken := -1;
statusPics[s, inv].Element := RQteDefault;
end;
if DC = 0 then
result := theme.GetPicSize(statusPics[s, inv])
else
result := theme.drawPic(DC, Types.Point(x, y), statusPics[s, inv])
end;
procedure showAuthreq(c: TICQContact; msg: string);
var
ar: TauthreqFrm;
begin
msg := dupString(msg);
ar := findAuthReq(c);
if ar = NIL then
TauthreqFrm.doAll(RnQmain, c, msg)
else
begin
ar.msgBox.text := msg;
ar.BringToFront;
end;
end; // showAuthreq
function countContactsIn(proto: TICQSession; const st: byte): integer;
var
cnt: TICQContact;
begin
Result := 0;
for cnt in proto.readList(LT_ROSTER) do
if cnt.getStatus = st then
Inc(Result);
end; // countContactsIn
procedure ToggleOnlyOnline;
begin
roasterLib.SetOnlyOnline(not showOnlyOnline);
// design_fr.prefToggleOnlyOnline;
end;
function applyVars(c: TICQContact; const s: String; fromAM: Boolean = False): String;
var
h: Tdatetime;
s1, s2: String;
begin
if imAwaySince > 0 then
h := (Now - imAwaySince) * 24
else
h := 0;
result := template(s, ['%awaysince%', formatDatetime(timeformat.automsg, imAwaySince), '%awaysince-gmt%',
formatDatetime(timeformat.automsg, imAwaySince - GMToffset), '%elapsedhours%', IntToStr(trunc(h)), '%elapsedminutes%',
IntToStr(trunc(frac(h) * 60)), '%h%', IntToStr(hourof(Now)), '%m%', IntToStr(minuteof(Now)), '%s%', IntToStr(secondof(Now)),
'%D%', IntToStr(dayof(Now)), '%M%', IntToStr(monthof(Now)), '%Y%', IntToStr(yearof(Now)), '%hh%', IntToStr(hourof(Now), 2),
'%mm%', IntToStr(minuteof(Now), 2), '%ss%', IntToStr(secondof(Now), 2), '%DD%', IntToStr(dayof(Now), 2), '%MM%',
IntToStr(monthof(Now), 2),
'%onlinecontacts%', IntToStr(TList(Account.AccProto.readList(LT_ROSTER)).count - countContactsIn(Account.AccProto,
byte(SC_OFFLINE))), '%offlinecontacts%', IntToStr(countContactsIn(Account.AccProto, byte(SC_OFFLINE))), '%events%',
IntToStr(eventQ.count)
// '%AutoMess%', ifThen(fromAM, '', getAutomsgFor(c))
]);
if Assigned(c) then
begin
if (c is TICQContact) then
begin
if {TICQContact(c).connection.ip}0 = 0 then
s1 := getTranslation(Str_unk)
else
s1 := '';//ip2str(TICQContact(c).connection.ip);
if 0 = 0 then
s2 := getTranslation(Str_unk)
else
s2 := '0';
end
else
begin
s1 := getTranslation(Str_unk);
s2 := s1;
end;
result := template(result, ['%you%', c.displayed, '%nick%', c.nick, '%first%', c.first, '%last%', c.last, '%status%',
getTranslation(Account.AccProto.Statuses[c.getStatus].Cptn), '%ip%', s1, '%proto%', s2]);
end
else
result := template(result, ['%you%', '', '%nick%', '', '%first%', '', '%last%', '', '%ip%', getTranslation(Str_unk),
'%status%', getTranslation(Str_unk),
// statusNameExt2(byte(SC_OFFLINE)),
'%proto%', getTranslation(Str_unk)]);
end;
function getXStatusMsgFor(c: TICQContact): string;
begin
// result := applyVars(c, curXStatusDesc, True);
// result := applyVars(c, ExtStsStrings[ICQ.curXStatus][1], True);
// result := applyVars(c, ExtStsStrings[TicqSession(c.fProto).curXStatus], True);
if Assigned(c) then
result := applyVars(c, ExtStsStrings[Account.AccProto.CurXStatus].Desc, True)
else
result := applyVars(NIL, ExtStsStrings[Account.AccProto.CurXStatus].Desc, True)
end; // getAutomsg
procedure Check4Update;
begin
if ConnectionAvailable then
begin
CheckUpdates;
CheckUpdate.Checking := True;
end
end; // Check4Update
procedure CheckUpdates;
var
RespStr, Tag, ReleasesPage, DistrLink, Changelog, Publish: String;
PublishedAt: TDateTime;
Version: TArray;
JSON: TJSONArray;
Release: TJSONValue;
Prerelease: Boolean;
Ver, Build: Integer;
TaskDlg: TTaskDialog;
begin
CheckUpdate.Checking := False;
LoadFromURLAsString('https://code.highspec.ru/api/v1/repos/Mikanoshi/RnQ/releases', RespStr);
if (Trim(RespStr) = '') or not ParseJSON(RespStr, JSON) then
begin
MsgDlg('Error checking for updates', True, mtError);
Exit;
end;
CheckUpdate.Last := Now;
try
if JSON.Count > 0 then
Release := JSON.Items[0]
else
Exit;
if not Assigned(Release) or not (Release is TJSONObject) then
Exit;
Release.GetValueSafe('tag_name', Tag);
Version := Tag.Split(['v']);
if Length(Version) < 2 then
Exit;
TryStrToInt(Version[0], Ver);
TryStrToInt(Version[1], Build);
if (Ver > RnQBuild) or (CheckUpdate.Betas and (Build > RnQBuildCustom)) then
begin
ReleasesPage := 'https://code.highspec.ru/Mikanoshi/RnQ/releases';
Release.GetValueSafe('prerelease', Prerelease);
Release.GetValueSafe('body', Changelog);
Release.GetValueSafe('published_at', Publish);
TJSONArray(TJSONObject(Release).GetValue('assets')).Items[0].GetValueSafe('browser_download_url', DistrLink);
PublishedAt := ISO8601ToDate(Publish, False);
if (Win32MajorVersion >= 6) and ThemeServices.ThemesEnabled then
begin
TaskDlg := TTaskDialog.Create(nil);
TaskDlg.Caption := GetTranslation('New version');
TaskDlg.Text := GetTranslation('There is a new build available (%s).\nPublished at %s', [Tag, FormatDatetime(TimeFormat.info, PublishedAt)]);
TaskDlg.CustomMainIcon.LoadFromResourceName(hInstance, 'UPDATES');
TaskDlg.ExpandButtonCaption := GetTranslation('Changelog');
TaskDlg.ExpandedText := Changelog;
TaskDlg.Flags := [tfUseHiconMain, tfExpandFooterArea];
if Prerelease then
begin
TaskDlg.FooterText := GetTranslation('This version is marked as beta!');
TaskDlg.CustomFooterIcon.LoadFromResourceName(hInstance, 'WARN');
TaskDlg.Flags := TaskDlg.Flags + [tfUseHiconFooter];
end;
TaskDlg.CommonButtons := [];
TaskDlg.Buttons.Clear;
with TaskDlg.Buttons.Add do
begin
ModalResult := 1;
Caption := GetTranslation('Releases page');
Enabled := True;
end;
with TaskDlg.Buttons.Add do
begin
ModalResult := 2;
Caption := GetTranslation('Distribution');
Enabled := not (DistrLink = '');
end;
with TaskDlg.Buttons.Add do
begin
ModalResult := 0;
Caption := GetTranslation('Close');
Enabled := True;
Default := True;
end;
TaskDlg.Execute;
if TaskDlg.ModalResult = 1 then
OpenURL(ReleasesPage)
else if TaskDlg.ModalResult = 2 then
OpenURL(DistrLink);
FreeAndNil(TaskDlg);
end else
if MessageDlg(GetTranslation('There''s a new version available (%s)!\nDo you want to download the new version?', [Tag]), mtConfirmation, [mbYes, mbNo]) = mrYes then
OpenURL(ReleasesPage);
end else if not CheckUpdate.AutoChecking then
MsgDlg('No new version available', True, mtInformation);
SaveCfgDelayed := True;
finally
FreeAndNil(JSON);
end;
end;
procedure dockSet;
var
r: Trect;
begin
if RnQmain = NIL then
exit;
r := RnQmain.boundsrect;
dockSet(r);
RnQmain.boundsrect := r;
end; // dockSet
procedure dockSet(var r: Trect);
var
w: integer;
vOn: Boolean;
begin
if not RnQmain.Visible or not running then
exit;
vOn := docking.appBar and docking.active and not docking.tempOff;
if vOn <> docking.appBarFlag then
begin
docking.appBarFlag := vOn;
RnQSysUtils.dockSet(RnQmain.Handle, vOn, WM_DOCK);
end;
if not docking.active then
exit;
w := r.Right - r.Left;
r := desktopworkarea(mainDlg.RnQmain.Handle);
if docking.appBar then
begin
r.Left := 0;
r.Right := screen.Width;
end;
if docking.pos = DP_left then
begin
dec(r.Left, getsystemmetrics(SM_CXFRAME));
r.Right := r.Left + w;
end
else
begin
inc(r.Right, getsystemmetrics(SM_CXFRAME));
r.Left := r.Right - w;
end;
appbarResizeDelayed := True;
end; // dockSet
procedure setAppBarSize;
// var
// r : TRect;
begin
// r := RnQmain.boundsrect;
/// / r.Right := r.Right + 10;
RnQSysUtils.setAppBarSize(RnQmain.Handle, RnQmain.boundsrect, WM_DOCK, docking.pos = DP_left)
end; // setAppBarSize
procedure fixWindowPos(frm: Tform);
var
dwa: Trect;
begin
if frm = nil then
exit;
if not doFixWindows or docking.active or (frm.WindowState <> wsNormal) then
exit;
dwa := screen.DesktopRect;
// dwa:=desktopWorkArea;
if fixingWindows.lastWidth <> dwa.Right then
begin
if fixingWindows.onTheRight then
frm.Left := dwa.Right - fixingWindows.lastRightSpace;
fixingWindows.lastWidth := dwa.Right;
end;
if frm.Left < (dwa.Left - frm.Width) then
frm.Left := dwa.Left - frm.Width + 10;
if frm.Top < (dwa.Top - frm.Height) then
frm.Top := dwa.Top - frm.Height + 10;
if frm.Left > dwa.Right - 10 then
frm.Left := dwa.Right - 10;
if frm.Top > dwa.Bottom - 10 then
frm.Top := dwa.Bottom - 10;
if frm.Height > screen.Height then
frm.Height := screen.Height - 20;
if frm.Width > screen.Width then
frm.Width := screen.Width - 20;
fixingWindows.onTheRight := centerPoint(frm.boundsrect).x > (screen.Width div 2);
fixingWindows.lastRightSpace := dwa.Right - frm.Left;
end; // fixWindowPos
function IsSpam(var Wrd: String; c: TICQContact; const Msg: String = ''; Flags: DWord = 0): Boolean;
var
b, filter: Boolean;
s: String;
i: Integer;
begin
if (flags and IF_auth > 0) and SpamFilter.IgnoreAuthNIL and (notInlist.exists(c) or not c.isInRoster) then
begin
Result := True;
Exit;
end;
if SpamFilter.IgnoreNIL and (notInlist.exists(c) or not c.IsInRoster) then
begin
Result := True;
Exit;
end;
Result := False;
filter := False;
if SpamFilter.UINgt > 0 then
if TryStrToInt(c.uid, i) and (i <= SpamFilter.UINgt) then
Exit
else
filter := True;
if SpamFilter.NotNIL then
if c.isInRoster then
Exit
else
filter := True;
if SpamFilter.MultiSend then
if flags and IF_multiple = 0 then
Exit
else
filter := True;
if SpamFilter.NotEmpty then
if ExistsHistWith(c.UID2cmp) then
Exit
else
filter := True;
if SpamFilter.NoBadWords then
begin
b := False;
s := SpamFilter.BadWords;
while not b and (s > '') do
begin
Wrd := chop(';', s);
b := ansiContainsText(msg, Wrd);
end;
if b then
filter := True
else
begin
Wrd := '';
filter := False;
end;
end;
Result := filter;
end; // IsSpam
function FilterRefuse(c: TICQContact; const Msg: String = ''; Flags: DWord = 0; ev: THevent = nil): Boolean;
var
Wrd: String;
SpamCnt: TICQContact;
begin
Result := True;
Wrd := '';
if IsSpam(wrd, c, msg, flags) then
begin
if SpamFilter.AddToHist then
if (msg > '') and (Assigned(ev)) then
begin
SpamCnt := Account.AccProto.GetContact(spamsFilename);
WriteToHistory(ev, SpamCnt);
if chatFrm.IsChatOpen(SpamCnt) then
chatFrm.AddEvent(SpamCnt, ev.clone);
end;
if SpamFilter.Warn then
if wrd > '' then
msgDlg(getTranslation('Spam filtered from %s \n by word %s', [c.displayed + ' (' + c.uid + ')', wrd]), False, mtInformation, c.uid)
else
msgDlg(getTranslation('Spam filtered from %s', [c.displayed + ' (' + c.uid + ')']), False, mtInformation, c.uid);
exit;
end;
Result := EnableIgnoreList and IgnoreList.exists(c);
end; // FilterRefuse
function db2strU(db: TRnQCList): RawByteString;
var
dim: integer;
procedure addStr(const s: RawByteString);
var
i: integer;
begin
if length(s) > 0 then
begin
i := length(result);
while dim + length(s) > i do
inc(i, 10000);
if i > length(result) then
SetLength(result, i);
system.move(Pointer(s)^, result[dim + 1], length(s));
inc(dim, length(s));
end;
end; // addStr
var
cnt: TICQContact;
begin
Result := '';
dim := 0;
for cnt in db do
if Assigned(cnt) then
addStr(cnt.GetDBrow);
SetLength(Result, dim);
end; // db2str
function str2status(const s: AnsiString): byte;
var
ss: TPicName;
begin
ss := LowerCase(s);
for result := byte(low(status2img)) to byte(high(status2img)) do
// if LowerCase(status2img[TICQStatus(result)]) = s then
if status2img[result] = ss then
exit;
result := byte(SC_ONLINE); // shut up compiler warning
end; // str2status
function str2visibility(const s: AnsiString): Tvisibility;
var
ss: TPicName;
begin
ss := LowerCase(s);
for result := low(result) to high(result) do
if visib2str[result] = ss then
exit;
result := VI_normal; // shut up compiler warning
end; // str2visibility
procedure clearDB(db: TRnQCList);
var
i: integer;
begin
for i := 0 to TList(db).count - 1 do
with db.getAt(i) do
Free;
db.clear;
end; // clearDB
procedure freeDB(var db: TRnQCList);
begin
if not Assigned(db) then
exit;
clearDB(db);
db.Free;
db := NIL;
end; // freeDB
procedure contactCreation(c: TICQContact);
begin
// getMem(c.data, sizeof(TCE));
// new(TCE(c.data));
c.data := AllocMem(sizeof(TCE));
fillChar(c.data^, sizeof(TCE), 0);
// TCE(c.data^).toquery := True; // Too many queries for loading large CLs for the first time!
end;
procedure contactDestroying(c: TICQContact);
begin
if Assigned(c.data) then
begin
{ if assigned(pTCE(c.data).history0) then
begin
Thistory(pTCE(c.data).history0).Free;
pTCE(c.data).history0 := NIL;
end; }
if Assigned(pTCE(c.data).node) then
FreeAndNil(pTCE(c.data).node);
SetLength(TCE(c.data^).notes, 0);
freeMem(c.data);
end;
end;
procedure startTimer;
begin
RnQmain.timer.enabled := True
end;
procedure stopMainTimer;
begin
RnQmain.timer.Enabled := False;
end;
function behave(ev: Thevent; kind: integer = -1 { ; const info: AnsiString='' } ): Boolean;
function IsAnswer(ans: array of string; const text: String): Boolean;
var
i: integer;
begin
Result := False;
if (length(ans) = 0) then
Exit;
for i := Low(ans) to High(ans) do
if (AnsiStartsText(ans[i], text) or AnsiStartsText('' + ans[i], text)) then
begin
Result := True;
Exit;
end;
end;
function IsUnverified(const text: String): Boolean;
begin
Result := ContainsText(text, 'https://icq.com') and ContainsText(text, 'verifyphone');
end;
const
SpamBotMsgFlags = IF_not_show_chat or IF_not_save_hist or IF_Simple;
var
ok: Boolean;
wnd: TselectCntsFrm;
spmHist: Thistory;
i, j: integer;
ev0: Thevent;
s: string;
vProto: TICQSession;
vCnt: TICQContact;
tipsAllowed: Boolean;
SkipEvent: Boolean;
QuietEvent: Boolean;
picsFound: Boolean;
picsName: TPicName;
gr: TGroup;
dd: TDivisor;
events: Thevents;
forceadd: Boolean;
Activity: Boolean;
begin
Result := False;
// if info > '' then
// ev.setInfo(info);
if kind >= 0 then
ev.kind := kind;
case kind of
EK_MSG, EK_BUZZ:
ok := not FilterRefuse(ev.who, ev.getBodyText, 0, ev);
EK_AUTHREQ:
ok := (not(EnableIgnoreList and IgnoreList.exists(ev.who))) and (not FilterRefuse(ev.who, '', IF_auth));
else
ok := not FilterRefuse(ev.who);
end;
if not ok then
Exit;
tipsAllowed := IsCanShowNotifications;
if Assigned(ev.otherpeer) then
vCnt := ev.otherpeer
else
vCnt := ev.who;
vProto := Account.AccProto;
if SpamFilter.UseBot then
if not (vProto.isInList(LT_ROSTER, vCnt) or notInList.exists(vCnt) or chatFrm.IsChatOpen(vCnt)) then
begin
if kind in [EK_typingBeg .. EK_XstatusMsg, EK_incoming, EK_outgoing, EK_auth, EK_authDenied, EK_statuschange] then
exit
else if (IF_offline and ev.flags > 1) then
begin
end
else if kind in [EK_XstatusMsg] then
begin
end
else if ((kind = EK_MSG) and (Length(vCnt.antispam.lastQuests) > 0)
and (IsAnswer(vCnt.antispam.lastQuests, ev.getBodyText)) or IsUnverified(ev.getBodyText)) then
begin
vCnt.antispam.tries := 0;
SetLength(vCnt.antispam.lastQuests, 0);
try
spmHist := Thistory.Create(spamsFilename);
events := spmHist.getBySender(ev.who.UID);
if Length(events) > 0 then
for i := 0 to Length(events) - 1 do
begin
ev0 := events[i];
if ev0 = nil then
Continue;
ev0.expires := -1;
// SAVE
if logpref.writehistory and (BE_save in behaviour[ev0.kind].trig) then
WriteToHistory(ev0, ev0.who);
forceadd := chatFrm.IsChatOpen(ev0.who);
// OPENCHAT
if // not BossMode.isBossKeyOn and
(BE_openchat in behaviour[ev0.kind].trig) and not vProto.getStatusDisable.OpenChat then
begin
forceadd := not chatFrm.OpenChat(ev0.who);
if not forceadd then
chatFrm.SetUnreadEvent(ev0.who, ev0);
end;
// HISTORY
if BE_history in behaviour[ev0.kind].trig then
if forceadd then
chatFrm.addEvent(ev0.who, ev0.clone);
// TRAY
if (ev0.kind = EK_CONTACTS) and chatFrm.isVisible and (ev0.who = chatFrm.thisChat.who) then
TselectCntsFrm.doAll(RnQmain, getTranslation('from %s', [ev0.who.displayed]),
GetTranslation('Add selected contacts'), vProto, ev0.cl.clone, RnQmain.AddContactsAction, [sco_multi], @wnd, False, False)
else if BE_tray in behaviour[ev0.kind].trig then
eventQ.Add(ev0.clone);
// TIP
if tipsAllowed and not BossMode.isBossKeyOn and (BE_tip in behaviour[ev0.kind].trig) and
(ev0.flags and IF_offline = 0) and not vProto.getStatusDisable.tips then
try
// TipAdd(ev0);
TipAdd3(ev0);
except end;
FreeAndNil(ev0);
end;
SetLength(events, 0);
spmHist.deleteBySender(ev.who.UID);
spmHist.Free;
except end;
if not IsUnverified(ev.getBodyText) then
OutboxAdd(OE_msg, vCnt, SpamBotMsgFlags, getTranslation(AntiSpamMsgs[2]))
end
else
begin
SetLength(ev.who.antispam.lastQuests, 0);
if logpref.writehistory and (BE_save in behaviour[ev.kind].trig) then
WriteToHistory(ev, vProto.getContact(spamsFilename));
if (BE_history in behaviour[ev.kind].trig) then
// if chatFrm.chats.idxOfUIN(spamsFilename) >= 0 then
if chatFrm.IsChatOpen(vProto.getContact(spamsFilename)) then
chatFrm.addEvent(vProto.getContact(spamsFilename), ev.clone);
if ev.who.antispam.tries = SpamFilter.BotTriesCount then
begin
inc(ev.who.antispam.tries);
OutboxAdd(OE_msg, vCnt, SpamBotMsgFlags, AnsiReplaceStr(getTranslation(AntiSpamMsgs[3]), '%uin%', ev.who.uid));
exit;
end
else if vCnt.antispam.tries > SpamFilter.BotTriesCount then
exit
else
begin
Randomize;
if SpamFilter.UseBotFromFile and (Length(SpamFilter.Quests) > 0) then
begin
i := RandomRange(0, length(SpamFilter.Quests));
// if i >0 then
begin
with SpamFilter.Quests[i] do
begin
SetLength(vCnt.antispam.lastQuests, length(a));
for j := 0 to length(a) - 1 do
vCnt.antispam.lastQuests[j] := a[j];
s := q;
end;
end
{ else
begin
s := '';
ev.who.antispam.lastQuest := '';
end; }
end
else
begin
i := RandomRange(100, 999);
SetLength(vCnt.antispam.lastQuests, 1);
vCnt.antispam.lastQuests[0] := IntToStr(i);
s := TxtFromInt(i)
end;
if length(vCnt.antispam.lastQuests) > 0 then
begin
inc(vCnt.antispam.tries);
if SpamFilter.UseBotFromFile and (length(SpamFilter.Quests) > 0) then
OutboxAdd(OE_msg, vCnt, SpamBotMsgFlags, AnsiReplaceStr(getTranslation(AntiSpamMsgs[5]), '%attempt%',
IntToStr(SpamFilter.BotTriesCount + 1 - ev.who.antispam.tries)) + CRLF + getTranslation(AntiSpamMsgs[6])
+ CRLF + s)
else
OutboxAdd(OE_msg, vCnt, SpamBotMsgFlags, AnsiReplaceStr(getTranslation(AntiSpamMsgs[5]), '%attempt%',
IntToStr(SpamFilter.BotTriesCount + 1 - ev.who.antispam.tries)) + CRLF + getTranslation(AntiSpamMsgs[4]) +
CRLF + s);
exit;
end;
end;
end;
end;
// prevent annoying fast incoming/outgoing sequences
if minOnOff then
if (ev.kind = EK_incoming) and (Now - vCnt.LastTimeSeenOnline < minOnOffTime * DTseconds) or
(ev.kind = EK_outgoing) and (Now - TCE(vCnt.data^).lastIncoming < minOnOffTime * DTseconds) then
Exit;
Result := True;
Activity := False;
if ev.kind in [EK_MSG .. EK_statuschange] then
begin
TCE(vCnt.data^).lastEventTime := Now;
Activity := True;
end;
if ev.kind in [EK_MSG, EK_BUZZ, EK_CONTACTS, EK_auth, EK_authDenied, EK_AUTHREQ] then
begin
TCE(vCnt.data^).lastMsgTime := ev.when;
Activity := True;
end;
if Activity then
RnQmain.CLBox.UpdateContact(vCnt);
// SAVE
if logpref.writehistory and (BE_save in behaviour[ev.kind].trig) then
WriteToHistory(ev);
SkipEvent := False;
if DsblEvnt4ClsdGrp and (ev.kind in [EK_incoming, EK_outgoing, EK_statuschange, EK_typingBeg,
EK_typingFin, EK_XstatusMsg]) then
begin
// gr := vCnt.group;
gr := groups.Get(vCnt.group);
if OnlOfflInOne then
dd := d_contacts
else
dd := d_online;
SkipEvent := not gr.expanded[dd];
end;
QuietEvent := False;
if EnableQuietList and quietList.exists(ev.who) then
if ev.kind in [EK_AddedYou, EK_incoming, EK_outgoing, EK_statuschange, EK_typingBeg, EK_typingFin, EK_XstatusMsg, EK_buzz] then
begin
SkipEvent := True;
QuietEvent := True;
end;
// SOUND
if not BossMode.isBossKeyOn and (BE_sound in behaviour[ev.kind].trig) and not vProto.getStatusDisable.sounds and not SkipEvent
then
if ev.flags and IF_no_matter = 0 then
begin
picsFound := False;
if UseContactThemes and Assigned(ContactsTheme) then
begin
picsName := TPicName(vCnt.UID2cmp) + '.' + event2str[ev.kind];
picsFound := (ContactsTheme.GetSound(picsName) > '');
if picsFound then
ContactsTheme.PlaySound(picsName)
else
begin
picsName := TPicName('group.') + TPicName(AnsiLowerCase(groups.id2name(vCnt.group))) + '.' +
TPicName(event2str[ev.kind]);
picsFound := (ContactsTheme.GetSound(picsName) > '');
if picsFound then
ContactsTheme.PlaySound(picsName);
end;
end;
if not picsFound then
theme.PlaySound(event2str[ev.kind]);
end;
// TIP
if tipsAllowed and not BossMode.isBossKeyOn and (BE_tip in behaviour[ev.kind].trig) and (ev.flags and IF_offline = 0) and
not vProto.getStatusDisable.tips and not SkipEvent then
if ev.flags and IF_no_matter = 0 then
try
// TipAdd(ev);
TipAdd3(ev);
except
end;
{$IFDEF Use_Baloons}
if not BossMode.isBossKeyOn and (be_BALLOON in behaviour[ev.kind].trig) and (ev.flags and IF_offline = 0) and
not vProto.getStatusDisable.tips and not SkipEvent then
if ev.flags and IF_no_matter = 0 then
ShowBalloonEv(ev);
{$ENDIF Use_Baloons}
// TRAY
if (ev.kind = EK_CONTACTS) and chatFrm.isVisible and (ev.who = chatFrm.thisChat.who) then
TselectCntsFrm.doAll(RnQmain, getTranslation('from %s', [ev.who.displayed]), getTranslation('Add selected contacts'), vProto,
ev.cl.clone, RnQmain.AddContactsAction, [sco_multi], @wnd, False, False)
else if (BE_tray in behaviour[ev.kind].trig) and not SkipEvent then
// if ev.flags and IF_no_matter = 0 then
eventQ.Add(ev.clone);
forceadd := chatFrm.IsChatOpen(vCnt);
// OPENCHAT
if (BE_openchat in behaviour[ev.kind].trig) and not vProto.getStatusDisable.OpenChat and not QuietEvent then
if ev.flags and IF_no_matter = 0 then
if chatFrm.OpenChat(vCnt, False, True) then
begin
forceadd := False;
chatFrm.SetUnreadEvent(vCnt, ev);
if not BossMode.isBossKeyOn and (BE_flashchat in behaviour[ev.kind].trig) then
if not (ev.kind = EK_BUZZ) then
chatFrm.flash;
end;
// HISTORY
if BE_history in behaviour[ev.kind].trig then
if forceadd then
if chatFrm.addEvent(vCnt, ev.clone) then
if ev.flags and IF_no_matter = 0 then
if not vProto.getStatusDisable.OpenChat then
if not BossMode.isBossKeyOn and (BE_flashchat in behaviour[ev.kind].trig) then
if not (ev.kind = EK_BUZZ) and not QuietEvent then
chatFrm.flash;
// POP UP
if not BossMode.isBossKeyOn and (BE_popup in behaviour[ev.kind].trig) and not QuietEvent then
if not chatFrm.isVisible then
if not vProto.getStatusDisable.OpenChat then
if ev.flags and IF_no_matter = 0 then
chatFrm.openOn(vCnt, focusOnChatPopup);
// SHAKE IT BABY!
if (ev.kind = EK_BUZZ) and not QuietEvent then
if not BossMode.isBossKeyOn and (BE_flashchat in behaviour[ev.kind].trig) then
if chatFrm.Visible then
chatFrm.shake;
end; // behave
function beh2str(kind: integer): RawByteString;
var
s: RawByteString;
begin
s := '';
if behaviour[kind].tiptimes then
s := s + 'times(' + IntToStr(behaviour[kind].tiptimeplus) + ')+';
if BE_tip in behaviour[kind].trig then
s := s + 'tip(' + IntToStr(behaviour[kind].tiptime) + ')+';
if BE_tray in behaviour[kind].trig then
s := s + 'tray+';
if BE_openchat in behaviour[kind].trig then
s := s + 'openchat+';
if BE_save in behaviour[kind].trig then
s := s + 'save+';
if BE_sound in behaviour[kind].trig then
s := s + 'sound+';
if BE_history in behaviour[kind].trig then
s := s + 'history+';
if BE_popup in behaviour[kind].trig then
s := s + 'popup+';
if BE_flashchat in behaviour[kind].trig then
s := s + 'flashchat+';
if be_BALLOON in behaviour[kind].trig then
s := s + 'balloon+';
Delete(s, length(s), 1);
result := s;
end; // beh2str
procedure str2beh(const b, s: RawByteString);
var
i: byte;
begin
// for e:=EK_last downto 1 do
// for i:=0 to EK_last-1 do
for i := 1 to EK_last do
if b = event2str[i] + '-behaviour' then
behaviour[i] := str2beh(s)
end;
function str2beh(s: AnsiString): Tbehaviour;
const
tipstr = AnsiString('tip');
function extractPar(const lab: AnsiString): AnsiString;
var
i, j: integer;
begin
result := '';
i := AnsiPos(lab + '(', s);
if i > 0 then
begin
inc(i, length(lab) + 1);
j := PosEx(')', s, i);
// j:=i;
// while (length(s) > j) and (s[j]<>')') do
// inc(j);
if j > 0 then
result := Copy(s, i, j - i)
else
result := ''
end;
end; // extractPar
var
tS: AnsiString;
begin
result.trig := [];
result.tiptime := 0;
result.tiptimes := False;
result.tiptimeplus := 0;
s := LowerCase(s);
result.tiptimes := ansiContainsText(s, AnsiString('times'));
try
tS := extractPar(AnsiString('times'));
if tS <> '' then
result.tiptimeplus := strToInt(tS)
except
end;
if ansiContainsText(s, tipstr) then
include(result.trig, BE_tip);
try
tS := extractPar(tipstr);
if tS <> '' then
result.tiptime := strToInt(tS)
except
end;
if ansiContainsText(s, AnsiString('tray')) then
include(result.trig, BE_tray);
if ansiContainsText(s, AnsiString('openchat')) then
include(result.trig, BE_openchat);
if ansiContainsText(s, AnsiString('save')) then
include(result.trig, BE_save);
if ansiContainsText(s, AnsiString('sound')) then
include(result.trig, BE_sound);
if ansiContainsText(s, AnsiString('history')) then
include(result.trig, BE_history);
if ansiContainsText(s, AnsiString('popup')) then
include(result.trig, BE_popup);
if ansiContainsText(s, AnsiString('flashchat')) then
include(result.trig, BE_flashchat);
if ansiContainsText(s, AnsiString('balloon')) then
include(result.trig, be_BALLOON);
end; // str2beh
procedure hideTaskButtonIfUhave2;
begin
if not menuViaMacro then
ShowWindow(application.Handle, SW_HIDE)
end;
function registerHK(id: integer; hk: word): Boolean;
var
m: integer;
begin
m := 0;
if hk and $1000 > 0 then
inc(m, MOD_WIN);
if hk and $2000 > 0 then
inc(m, MOD_SHIFT);
if hk and $4000 > 0 then
inc(m, MOD_CONTROL);
if hk and $8000 > 0 then
inc(m, MOD_ALT);
result := RegisterHotKey(RnQmain.Handle, id, m, LOBYTE(hk));
end; // registerHK
function updateSWhotkeys: Boolean;
var
i: integer;
begin
result := False;
if RnQmain = nil then
exit;
removeSWhotkeys;
result := True;
for i := 0 to length(macros) - 1 do
if macros[i].sw then
result := registerHK(i, macros[i].hk) and result;
end; // updateSWhotkeys
procedure removeSWhotkeys;
var
i: integer;
begin
for i := 0 to 200 do
unregisterHotKey(RnQmain.Handle, i);
end; // removeSWhotkeys
function AddToIgnoreList(c: TICQContact; const LocalOnly: Boolean = False): Boolean;
begin
Result := False;
if (c = nil) or IgnoreList.exists(c) then
Exit;
IgnoreList.Add(c);
Result := True;
if not LocalOnly then
Account.AccProto.AddToList(LT_SPAM, c);
SaveListsDelayed := True;
end; // AddToIgnorelist
function addToQuietlist(c: TICQContact): Boolean;
begin
Result := False;
if (c = nil) or quietList.exists(c) then
Exit;
quietList.Add(c);
saveListsDelayed := True;
Result := True;
end; // addToQuietlist
procedure RemoveFromIgnorelist(c: TICQContact);
// var
// i : Byte;
begin
if (c = NIL) or not IgnoreList.exists(c) then
exit;
IgnoreList.remove(c);
// if ICQ.readList(LT_SPAM).exists(c) then
Account.AccProto.RemFromList(LT_SPAM, c);
// ICQ.SSI_DelVisItem(c.UID, FEEDBAG_CLASS_ID_IGNORE_LIST);
{ for i := Low(prefPages) to High(prefPages) do
if prefPages[i].Name = 'Ignore list' then
if Assigned(prefPages[i].frame) then
with TignoreFr(prefPages[i].frame).ignoreBox do
items.delete(items.indexOfObject(c));
}
saveListsDelayed := True;
end; // RemoveFromIgnorelist
procedure removeFromQuietlist(c: TICQContact);
begin
if (c = nil) or not quietList.exists(c) then
Exit;
quietList.remove(c);
saveListsDelayed := True;
end; // removeFromQuietlist
procedure RemoveFromRoster(c: TICQContact; const WithHistory: Boolean = False);
var
grp: integer;
begin
if c = nil then
Exit;
if c.IsInRoster then
plugins.castEvList(PE_LIST_REMOVE, PL_ROSTER, c);
grp := c.group;
roasterLib.Remove(c);
// c.iProto.removeContact(c);
if withHistory then
DelHistWith(c.UID2cmp);
if (grp > 0) and (TRnQCList(Account.AccProto.ReadList(LT_ROSTER)).getCount(grp) = 0) then
if MessageDlg(GetTranslation('This group (%s) is empty! Do you want to delete it?', [groups.id2name(grp)]), mtConfirmation,
[mbYes, mbNo]) = mrYes then
roasterLib.removeGroup(grp);
c.group := 0;
end; // RemoveFromRoster
procedure realizeEvents(const kind_: integer; c: TICQContact);
var
k: integer;
ev0: Thevent;
begin
k := -1;
repeat
k := eventQ.getNextEventFor(c, k);
if (k >= 0) and (k < eventQ.count) then
begin
ev0 := Thevent(eventQ.items[k]);
if (kind_ < 0) or (ev0.kind = kind_) then
begin
eventQ.removeAt(k);
realizeEvent(ev0);
end;
end;
until (k < 0);
end;
procedure realizeEvent(ev: Thevent);
var
wnd: TselectCntsFrm;
vCnt: TICQContact;
begin
if not Assigned(ev) then
Exit;
if Assigned(ev.otherpeer) then
vCnt := ev.otherpeer
else
vCnt := ev.who;
roasterLib.UpdateInPlace(vCnt);
TipRemove(ev);
if ev.kind in [EK_ADDEDYOU, EK_AUTHREQ, EK_MSG, EK_CONTACTS] then
NILifNIL(vCnt);
case ev.kind of
EK_ADDEDYOU:
if ev.who.isInList(LT_ROSTER) then
MsgDlg(GetTranslation('%s added you to his/her contact list.', [vCnt.displayed]), False, mtInformation)
else if MessageDlg(GetTranslation('%s added you to his/her contact list.\nDo you want to add him/her to your contact list?',
[vCnt.displayed]), mtConfirmation, [mbYes, mbNo]) = mrYes then
AddToRoster(vCnt, 0);
EK_AUTHREQ:
showAuthreq(vCnt, ev.getBodyText);
EK_incoming:
if showIncomingDlg then
msgDlg(getTranslation('%s is online', [vCnt.displayed]), False, mtInformation);
EK_MSG:
with chatFrm do
begin
OpenOn(vCnt);
chatFrm.SetUnreadEvent(vCnt, ev);
chatFrm.MoveToEvent(vCnt, ev);
if not chatFrm.HasEvent(vCnt, ev) then
chatFrm.addEvent(vCnt, ev.clone)
end;
EK_CONTACTS:
TselectCntsFrm.doAll(RnQmain, getTranslation('from %s', [vCnt.displayed]), getTranslation('Add selected contacts'),
Account.AccProto, ev.cl.clone, RnQmain.AddContactsAction, [sco_multi, sco_selected], @wnd, False, False)
end;
try
// FreeAndNil(ev);
ev.Free;
except
end;
end; // realizeEvent
function chopAndRealizeEvent: Boolean;
var
ev: Thevent;
begin
Result := False;
if eventQ = nil then
Exit;
ev := eventQ.pop;
if not Assigned(ev) then
Exit;
Result := True;
realizeEvent(ev);
saveInboxDelayed := True;
end; // chopAndRealizeEvent
procedure trayAction;
begin
if not chopAndRealizeEvent then
if useSingleClickTray or (not RnQmain.Visible) then
RnQmain.toggleVisible
else if not DoConnect then
RnQmain.toggleVisible;
// doConnect;
end; // trayAction
function ints2cl(proto: TICQSession; a: types.TintegerDynArray): TRnQCList;
var
i: integer;
begin
result := TRnQCList.Create;
for i := 0 to Length(a) - 1 do
// result.add(contactsDB.get(TICQContact, IntToStr(a[i])));
// result.add(TICQSession.ContactsDB.get(TICQContact, a[i]));
result.add(proto.GetContact(IntToStr(a[i])));
end; // ints2cl
function DoLock: Boolean;
var
Pwd: String;
Res: Boolean;
begin
if (AccPass = '') and (Account.AccProto.pwd = '') then
begin
MsgDlg('Impossible to activate lock as no account or login password is defined', True, mtInformation);
Result := True;
end
else
begin
if FormVisible(RnQmain) then
RnQmain.ToggleVisible;
chatFrm.Close;
locked := True;
Result := True;
if not startingLock then
saveCfgDelayed := True;
CommonMethods.EnterLockPassword(
GetTranslation('R&Q LOCKED'),
ReplaceText(GetTranslation('R&Q has been locked.\nYou need to type in your account or login password to unlock it.'), #10, '
')
);
end;
end; // DoLock
procedure DoUnlock;
begin
locked := False;
if not startingLock then
saveCfgDelayed := True;
startingLock := False;
end;
function behactionName(a: Tbehaction): string;
begin
result := getTranslation(behactions2str[a])
end;
function mb(q: extended): string;
begin
result := floatToStrF(q / (1024 * 1024), ffFixed, 20, 1) + getTranslation('Mb')
end;
procedure applyUserCharset(f: Tfont);
begin
if userCharset >= 0 then
f.charset := userCharset
end;
function getLeadingInMsg(const s: string; ofs: integer = 1): string;
var
i: integer;
begin
i := 0;
while (i < length(s)) and (CharInSet(s[i + ofs], ['>', ' '])) do
inc(i);
result := Copy(s, ofs, i);
end; // getLeadingInMsg
procedure applyCommonSettings(c: Tcomponent);
var
i: integer;
begin
if not Assigned(c) then
exit;
for i := c.componentCount - 1 downto 0 do
applyCommonSettings(c.components[i]);
if c is Tcontrol then
ApplyThemeComponent(Tcontrol(c));
end;
procedure assignImgBmp(img: TImage; bmp: TBitmap);
begin
img.Picture.Bitmap.Destroy;
img.Picture.Bitmap.Assign(bmp);
// img.Picture.Bitmap.FreeImage;
img.TRANSPARENT := bmp.TRANSPARENT;
img.Height := bmp.Height;
img.Width := bmp.Width;
end; // assignImgBmp
procedure assignImgIco(img: TImage; ico: Ticon);
begin
img.Picture.Icon.Assign(ico);
img.Width := ico.Width * 2;
img.Height := ico.Height * 2;
end; // assignImgIco
procedure MainFormHandleUpdate;
//var
// b: Boolean;
begin
// b := StyleServices.enabled and DwmCompositionEnabled and (not docking.Docked2chat and RnQmain.Floating);
if RnQmain.Handle = RnQmain.oldHandle then
Exit;
DragAcceptFiles(RnQmain.oldHandle, False);
RnQmain.oldHandle := RnQmain.Handle;
// DragAcceptFiles(RnQmain.roster.handle, FALSE);
DragAcceptFiles(RnQmain.Handle, True);
if Assigned(StatusIcon) then
StatusIcon.handleChanged(RnQmain.Handle);
updateSWhotkeys;
end; // mainfrmhandleupdate
procedure reloadCurrentLang();
begin
ClearLanguage;
LoadSomeLanguage;
TranslateWindows;
end; // reloadCurrentLang
procedure ToggleMainFormBorder(SetBrdr: Boolean = False; IsBrdr: Boolean = True);
begin
with RnQmain do
if not (SetBrdr and ((IsBrdr and (BorderStyle <> bsNone) or (not IsBrdr and (BorderStyle = bsNone))))) then
if BorderStyle = bsNone then
begin
BorderStyle := bsSizeToolWin;
ShowMainBorder := True;
end
else
begin
BorderStyle := bsNone;
ShowMainBorder := False;
end;
MainFormHandleUpdate;
end; // toggleMainfrmBorder
procedure applySnap();
begin
if Assigned(RnQmain) then
RnQmain.ScreenSnap := snapToScreenEdges;
if Assigned(chatFrm) then
chatFrm.ScreenSnap := snapToScreenEdges;
end;
function unexistant(const uin: TUID): Boolean;
begin
result := not Account.AccProto.getMyInfo.equals(uin) and not Account.AccProto.readList(LT_ROSTER).exists(uin)
and not notInlist.exists(uin)
end; // unexistant
function isAbort(const pluginReply: AnsiString): Boolean;
begin
result := (pluginReply > '') and (byte(pluginReply[1]) = PM_ABORT)
end;
procedure unroundWindow(hnd: THandle); inline;
begin
SetWindowRgn(hnd, 0, True)
end;
procedure drawHint(cnv: Tcanvas; kind: integer; groupid: integer; c: TICQContact; var r: Trect; calcOnly: Boolean = False);
const
border = 5;
roundsize = 16;
maxWidth = 300;
var
// n:Tnode;
maxX, x, y, dy, xdy: integer;
procedure textout(s: string); overload;
var
rr: Trect;
begin
if s = '' then
begin
xdy := 0;
exit;
end;
// textOut(cnv.handle, x,y, , j);
// drawText(cnv.handle, PChar(s), -1, R, DT_CALCRECT or DT_SINGLELINE or DT_VCENTER or DT_CENTER);
// cnv.TextRect(150);
// rr := r;
rr.Left := x;
rr.Top := y;
rr.Right := maxWidth;
rr.Bottom := y; // + 100;
s := dupAmpersand(s);
// rr.Right := r.Left + 10;
cnv.TextRect(rr, s, [tfCalcRect, tfBottom, tfLeft, tfWordBreak, tfEndEllipsis, tfEditControl]);
xdy := rr.Bottom - rr.Top;
// if rr.Right > maxWidth then
begin
// rr.Left := x;
// rr.Top := y;
// rr.Right := maxWidth;
// rr.Bottom := y + 100;
inc(rr.Right, 2);
if calcOnly then
// cnv.TextRect(rr, s, [tfBottom, tfLeft, tfWordBreak, tfEndEllipsis, tfEditControl, tfCalcRect])
else
cnv.TextRect(rr, s, [tfBottom, tfLeft, tfWordBreak, tfEndEllipsis, tfEditControl]);
// xdy := rr.Bottom - rr.Top;
x := rr.Right;
end;
{ else
begin
cnv.TextOut(x,y, s);
x:=cnv.penpos.x;
end; }
if x > maxX then
maxX := x;
end; // textout
procedure textout(const s: string; a: TFontStyles); overload;
begin
cnv.Font.Style := a;
textout(s);
end; // textout
procedure fieldOut(const fn, fc: string; needTranslateFC: Boolean = False);
begin
textout(fn, []);
if fc = '' then
textout(getTranslation(Str_unk), [fsItalic])
else if needTranslateFC then
textout(getTranslation(fc), [fsBold])
else
textout(fc, [fsBold]);
x := border;
// inc(y, dy+2);
inc(y, xdy + 2);
end; // fieldout
procedure fieldOutDP(const fn, fc: string; needTranslateFC: Boolean = False);
begin
textout(getTranslation(fn) + ': ', []);
if fc = '' then
textout(getTranslation(Str_unk), [fsItalic])
else if needTranslateFC then
textout(getTranslation(fc), [fsBold])
else
textout(fc, [fsBold]);
x := border;
// inc(y, dy+2);
inc(y, xdy + 2);
end; // fieldout
procedure lineOut(clr: TColor);
begin
cnv.Pen.Color := clr;
cnv.moveTo(r.Left + 5, y);
cnv.LineTo(r.Right - 5, y);
end; // lineout
procedure rulerOut();
begin
inc(y, dy div 2);
if not calcOnly then
lineOut(cnv.Pen.Color);
inc(y, 2);
inc(y, dy div 2);
end; // rulerOut
// procedure picOut(picName:String);
// begin
// end; // picOut
//
var
{i, }a, a2, a3: integer;
cl: TRnQCList;
cnt1: TICQContact;
ty: integer;
pic: TPicName;
sr: TSearchRec;
// gr : TGPGraphics;
// region:HRGN;
tS: String;
begin
if (kind = NODE_CONTACT) and (c = nil) then
Exit;
if (kind = NODE_GROUP) and (groupid < 0) then
Exit;
if cnv = nil then
Exit;
if not calcOnly then
begin
cnv.Font.Color := clInfoText;
cnv.Pen.Color := theme.GetColor('roaster.hint.border', clInfoText);
cnv.Brush.Color := theme.GetColor('roaster.hint', clInfoBk);
// cnv.RoundRect(r.Left,r.top,r.Right,r.bottom, roundsize+1,roundsize+1);
// cnv.FillRect(r);
cnv.Rectangle(r);
end;
theme.ApplyFont('roaster.hint', cnv.Font);
dy := cnv.TextHeight('I');
maxX := 0;
x := border;
y := roundsize div 2;
case kind of
NODE_CONTACT:
begin
if calcOnly then
with theme.GetPicSize(RQteDefault, rosterImgNameFor(c)) do
begin
inc(x, cx + 3);
ty := cy;
pic := Protocols_All.GetXStsPic(c, False);
with theme.GetPicSize(RQteDefault, pic) do
begin
inc(x, cx + 3);
ty := max(cy, ty);
end;
if (c is TICQContact) and (TICQContact(c).birthFlag) then
with theme.GetPicSize(RQteDefault, PIC_BIRTH) do
begin
inc(x, cx + 3);
ty := max(cy, ty);
end;
end
else
with theme.drawPic(cnv.Handle, x, y, rosterImgNameFor(c)) do
begin
inc(x, cx + 3);
ty := cy;
if TICQCOntact(c).Official then
theme.drawPic(cnv.Handle, x - 15, ty - 5, PIC_OFFICIAL);
pic := Protocols_All.GetXStsPic(c, False);
with theme.drawPic(cnv.Handle, x, y, pic) do
begin
inc(x, cx + 3);
ty := max(cy, ty);
end;
if (c is TICQContact) and (TICQContact(c).birthFlag) then
with theme.drawPic(cnv.Handle, x, y, PIC_BIRTH) do
begin
inc(x, cx + 3);
ty := max(cy, ty);
end;
end;
ty := max(ty, 16);
inc(y, ty - dy);
// i := y;
fieldOut(getTranslation('UIN') + '# ', c.uin2Show);
// if y < i+ty then y := i+ty;
// if n.contact.xStatusStr > '' then
if Assigned(Account.AccProto) and Account.AccProto.IsOnline then
fieldOutDP('Status', c.getStatusName);
// if (not XStatusAsMain) and (cnt.xStatus > 0) then
if Assigned(c) then // ICQ
begin
if c.xStatusStr > '' then
begin
if c.xStatusDesc > '' then
fieldOutDP(Str_message, c.xStatusDesc)
// else if cnt.ICQ6Status > '' then
// fieldOutDP(Str_message, cnt.ICQ6Status)
end
else if c.xStatusDesc > '' then
begin
// if c.isOffline then
fieldOutDP(Str_message, c.xStatusDesc);
// if cnt.ICQ6Status > '' then
// fieldOutDP(Str_message, cnt.ICQ6Status);
end;
if c.IdleTime > 0 then
fieldOutDP('Idle time', getTranslation('%d:%.2d', [c.IdleTime div 60, c.IdleTime mod 60]));
end;
rulerOut();
tS := getTranslation('Important') + ': ';
if Assigned(c) then
if c.ssImportant > '' then
begin
fieldOut(tS, c.ssImportant);
tS := '';
end;
if c.lclImportant > '' then
fieldOut(tS, c.lclImportant);
if TICQContact(c).Official then
begin
textout(GetTranslation('Official contact'));
x := border;
inc(y, xdy + 2);
end;
fieldOutDP('Nick', c.nick);
fieldOutDP('First name', c.first);
fieldOutDP('Last name', c.last);
if c.birthL <> 0 then
fieldOutDP('Birthday', DateToStr(c.birthL))
else if c.birth > 0 then
fieldOutDP('Birthday', DateToStr(c.birth));
fieldOutDP('Group', groups.id2name(c.group));
tS := '';
if not (c.GetContactIP = 0) then
if Account.AccProto.GetMyInfo <> nil then
if c.GetContactIP = Account.AccProto.GetMyInfo.GetContactIP then
tS := ''//ip2str(c.GetContactIntIP)
else
tS := '';//ip2str(c.GetContactIP);
if tS > '' then
fieldOutDP('IP address', tS);
if Assigned(c) and c.noClient then
fieldOutDP('Client was closed', TimeToString(c.clientClosed));
if c.isOnline then
begin
// fieldOutDP('Client', getClientFor(c));
fieldOutDP('Client', c.ClientDesc);
if Assigned(c) then
fieldOutDP('Online since', TimeToString(c.onlinesince));
end
else
fieldOutDP('Last time seen online', TimeToString(c.lastTimeSeenOnline));
if c.isInList(LT_SPAM) or IgnoreList.exists(c) then
fieldOut('', 'Being ignored', True);
if not c.CntIsLocal and not c.Authorized then
fieldOut('', 'Authorization required', True);
if Account.AccProto.AvatarsSupport and avatarShowInHint then
if Assigned(c.Icon.bmp) then
begin
maxX := max(maxX, c.Icon.bmp.GetWidth + 15);
if not calcOnly then
DrawRbmp(cnv.Handle, c.Icon.bmp, 5, y + 5);
inc(y, c.Icon.bmp.GetHeight + 5);
end else if Assigned(c) then
if FindFirst(IncludeTrailingPathDelimiter(AccPath + avtPath) + c.UID2cmp + '.avatar.*', faAnyFile, sr) = 0 then
fieldOut('', 'Has avatar', True);
end;
NODE_GROUP:
begin
if calcOnly then
with theme.GetPicSize(RQteDefault, PIC_CLOSE_GROUP) do
begin
inc(x, cx + 3);
inc(y, cy - dy);
end
else
with theme.drawPic(cnv.Handle, x, y, PIC_CLOSE_GROUP) do
begin
inc(x, cx + 3);
inc(y, cy - dy);
end;
cl := Account.AccProto.readList(LT_ROSTER);
fieldOutDP('Total', IntToStr(cl.getCount(groupid)));
if Account.AccProto.isOnline then
begin
a := 0;
a2 := 0;
a3 := 0;
for cnt1 in cl do
if cnt1.group = groupid then
if cnt1.isOffline then
inc(a)
else
if cnt1.isOnline then
inc(a2)
else
inc(a3);
fieldOutDP('Online', IntToStr(a2));
fieldOutDP('Offline', IntToStr(a));
fieldOutDP('Unknown', IntToStr(a3));
end;
end;
else // Unknown type
begin
r := Rect(0, 0, 0, 0);
exit;
end;
end;
// r:=rect(0,0,maxX+ShadowSize+roundsize,y+ShadowSize+roundsize);
r := Rect(0, 0, maxX + ShadowSize + 5, y + ShadowSize);
// cnv.Rectangle(r);
// SetWindowRgn(cnv.Handle, region, TRUE);
// r:=rect(0,0,100,400);
end; // drawHint
function binToStatus(const bin: TBytes): byte;
begin
if length(bin) < 4 then
result := byte(SC_UNK)
else
result := str2int(bin);
if not(result in [byte(SC_ONLINE) .. byte(SC_Last)]) then
result := byte(SC_UNK);
// if (resultSC_UNK) then result:=SC_UNK;
end; // infoToStatus
function binToXStatus(const bin: TBytes): byte;
begin
if length(bin) < 6 then
result := 0
else
result := bin[5];
if result > High(XStatusArray) then
result := 0;
end; // binToXStatus
function ExitFromAutoaway: Boolean;
begin
Result := False;
if autoaway.triggered = TR_none then
Exit;
if autoaway.setxstatus then
Account.AccProto.CurXStatus := autoaway.bakxstatus;
Account.AccProto.SetStatus(autoaway.bakstatus);
Result := True;
end; // ExitFromAutoaway
function GetShiftState: Integer;
var
keys: TkeyboardState;
begin
result := 0;
if not GetKeyboardState(keys) then
exit;
if keys[VK_SHIFT] >= $80 then
inc(result, 1);
if keys[VK_CONTROL] >= $80 then
inc(result, 2);
if keys[VK_MENU] >= $80 then
inc(result, 4);
end; // getShiftState
procedure ProcessOevent(oe: Toevent);
begin
case oe.kind of
OE_msg:
SendProtoMsg(oe);
OE_CONTACTS:
SendICQContacts(oe.whom, oe.flags, oe.cl);
OE_AUTH:
oe.whom.auth;
OE_AUTHDENIED:
oe.whom.AuthDenied(oe.info);
// OE_ADDEDYOU:
// SendICQAddedYou(oe.whom);
end;
end; // ProcessOevent
function OnlFeature(const pr: TICQSession; check: Boolean = True): Boolean;
// True if online
begin
if check and (pr <> nil) then
result := pr.isOnline
else
result := False;
if not result then
MsgDlg('You must be online in order to use this feature', True, mtWarning)
end;
{$IFDEF Use_Baloons}
procedure ShowBalloonEv(ev: Thevent);
var
counter: Int64;
s: String;
begin
// str1:=ev.decrittedInfoOrg;
// if pos(#13,str1)<>0 then str1:=copy(str1,1,pos(#13,str1)-1);
counter := behaviour[ev.kind].tiptime;
// s := copy(ev.decrittedInfo,1,255);
s := Copy(ev.getBodyText, 1, 255);
if behaviour[ev.kind].tiptimes then
counter := counter * length(s) + behaviour[ev.kind].tiptimeplus * 100;
if counter < 100 then
counter := 100;
if (be_BALLOON in behaviour[ev.kind].trig) and (ev.flags and IF_offline = 0) and not Account.AccProto.getStatusDisable.tips then
case ev.kind of
EK_MSG, EK_AUTHREQ:
statusIcon.showballoon(counter, s, ev.who.displayed + ' ' + getTranslation(tipevent2str[ev.kind]), bitInfo{, event2imgName(ev.kind)});
else
statusIcon.showballoon(counter, ev.who.displayed, getTranslation(tipevent2str[ev.kind]), bitInfo{, event2imgName(ev.kind)});
end;
end;
{$ENDIF Use_Baloons}
function CheckAntispam(c: TICQContact): Boolean;
begin
result := False;
// if not (rosterLib.exists(c) or notInList.exists(c)) then
// if spam then
end;
procedure CheckBDays;
const
bds: TPicName = 'birthday';
PrefIsShowBDFirst = 'is-show-bd-first';
PrefShowBDFirst = 'show-bd-first';
PrefIsShowBDBefore = 'is-show-bd-before';
PrefShowBDBefore = 'show-bd-before';
var
bPrefIsShowBDFirst, bPrefIsShowBDBefore: Boolean;
iPrefShowBDFirst, iPrefShowBDBefore: integer;
cl: TRnQCList;
c: TICQContact;
k, l: integer;
ss: TPicName;
played, showInform: Boolean;
begin
// if not Assigned(Account.AccProto) then Exit;
iPrefShowBDFirst := 7;
iPrefShowBDBefore := 3;
bPrefIsShowBDFirst := MainPrefs.getPrefBoolDef(PrefIsShowBDFirst, True);
bPrefIsShowBDBefore := MainPrefs.getPrefBoolDef(PrefIsShowBDBefore, True);
if bPrefIsShowBDFirst then
MainPrefs.getPrefInt(PrefShowBDFirst, iPrefShowBDFirst);
if bPrefIsShowBDBefore then
MainPrefs.getPrefInt(PrefShowBDBefore, iPrefShowBDBefore);
if not bPrefIsShowBDFirst or not bPrefIsShowBDBefore then
exit;
cl := Account.AccProto.readList(LT_ROSTER).clone;
try
if Assigned(notInlist) then
cl.Add(notInlist);
cl.resetEnumeration;
while cl.hasMore do
begin
c := cl.getNext;
if c.uid = '' then
Continue;
k := c.Days2Bd;
if (k >= iPrefShowBDFirst) and (k >= iPrefShowBDBefore) then
Continue;
showInform := False;
if bPrefIsShowBDBefore and (k < iPrefShowBDBefore) then
showInform := True;
if bPrefIsShowBDFirst and not showInform then
begin
l := -1;
if trunc(c.LastBDInform) < trunc(Now) then
begin
l := trunc(Now) - trunc(c.LastBDInform);
end;
if l > iPrefShowBDFirst then
if k < iPrefShowBDFirst then
begin
showInform := True;
c.LastBDInform := Now;
end;
end;
if showInform then
begin
TipAdd3(NIL, NIL, c);
if k = 0 then // Play sound
begin
played := False;
if UseContactThemes and Assigned(ContactsTheme) then
begin
ss := TPicName(c.UID2cmp) + '.' + bds;
if (ContactsTheme.GetSound(ss) > '') then
begin
played := True;
ContactsTheme.PlaySound(ss)
end
else
begin
ss := TPicName('group.') + TPicName(AnsiLowerCase(groups.id2name(c.group))) + '.' + bds;
if (ContactsTheme.GetSound(ss) > '') then
begin
played := True;
ContactsTheme.PlaySound(ss)
end;
end;
end;
if not played then
theme.PlaySound(bds);
end;
end;
{ if not BossMode.isBossKeyOn and (BE_tip in behaviour[ev.kind].trig) and (ev.flags and IF_offline=0)
and not proto.getStatusDisable.tips then
if ev.flags and IF_no_matter = 0 then
try
TipAdd(ev);
except
end; }
end;
finally
cl.Free;
end;
end;
procedure ClearSpamFilter;
begin
SpamFilter.BadWords := '';
end;
function GetWidth(chk: TCheckBox): integer;
var
c: TBitmap;
begin
c := TBitmap.Create;
try
c.canvas.Font.Assign(chk.Font);
result := c.canvas.TextWidth(chk.Caption) + 16;
finally
c.Free;
end;
end;
function StringFromFile(const FileName: TFileName): RawByteString;
var
f: THandle;
Size: integer;
begin
result := '';
if FileName = '' then
exit;
f := FileOpen(FileName, fmOpenRead or fmShareDenyNone);
if PtrInt(f) >= 0 then
begin
Size := GetFileSize(f, nil);
SetLength(result, Size);
if FileRead(f, Pointer(result)^, Size) <> Size then
result := '';
FileClose(f);
end;
end;
procedure CacheType(const url, ctype: RawByteString);
begin
try
if not (ctype = '') then
begin
imgCacheInfo.WriteString(url, 'mime', ctype);
imgCacheInfo.UpdateFile;
end;
except end;
end;
function CacheImage(var mem: TMemoryStream; const url, ext: RawByteString): Boolean;
var
imgcache, fn: String;
hash: LongWord;
winimg: TWICImage;
begin
Result := False;
winimg := TWICImage.Create;
mem.Seek(0, 0);
try
winimg.LoadFromStream(mem);
except
if Assigned(winimg) then
winimg.Free;
Exit;
end;
if winimg.Empty then
begin
winimg.Free;
Exit;
end;
imgcache := myPath + 'Cache\Images\';
if not DirectoryExists(imgcache) then
ForceDirectories(imgcache);
hash := CalcMurmur2(BytesOf(url));
fn := imgcache + IntToStr(hash) + '.' + ext;
winimg.SaveToFile(fn);
try
imgCacheInfo.WriteString(url, 'ext', ext);
imgCacheInfo.WriteString(url, 'hash', IntToStr(hash));
imgCacheInfo.WriteInteger(url, 'width', winimg.Width);
imgCacheInfo.WriteInteger(url, 'height', winimg.Height);
imgCacheInfo.UpdateFile;
finally
winimg.Free;
end;
Result := True;
end;
procedure incDBTimer;
var
isSSRuning: BOOL;
begin
if saveDBtimer2 = 0 then
// Increase saveDBtimer to maximum. If ScreenSaver is running than it's 10 min
begin
SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, @isSSRuning, 0);
if (isSSRuning or isLocked) or not isMoved(4*(10*60)) or BossMode.isBossKeyOn then
saveDBtimer2 := max(saveDBtimer2, 600)
else
saveDBtimer2 := max(saveDBtimer2, 240);
end else
inc(saveDBtimer2, saveDBdelay);
end;
function UnFakeUIN(uin: Int64): TUID;
var
x: int64;
begin
// x := MaxLongint;
x := UIN;
while x > 4294967296 do
x := x - 4294967296;
result := IntToStr(x);
end;
procedure UpdatePrefsFrm;
begin
if Assigned(PrefSheetFrm) then PrefSheetFrm.Reset;
end;
function ParseJSON(const RespStr: String; out JSON: TJSONObject): Boolean;
var
TmpJSON: TJSONValue;
begin
Result := False;
JSON := nil;
TmpJSON := TJSONObject.ParseJSONValue(TEncoding.UTF8.GetBytes(RespStr), 0);
if not Assigned(TmpJSON) then
Exit;
if TmpJSON is TJSONObject then
begin
JSON := TmpJSON as TJSONObject;
Result := True;
end else
FreeAndNil(TmpJSON);
end;
function ParseJSON(const RespStr: String; out JSON: TJSONArray): Boolean;
var
TmpJSON: TJSONValue;
begin
Result := False;
JSON := nil;
TmpJSON := TJSONObject.ParseJSONValue(TEncoding.UTF8.GetBytes(RespStr), 0);
if not Assigned(TmpJSON) then
Exit;
if TmpJSON is TJSONArray then
begin
JSON := TmpJSON as TJSONArray;
Result := True;
end else
FreeAndNil(TmpJSON);
end;
function TJSONHelper.GetValueSafe(const Key: String; out Data: T): Boolean;
var
ValVal: TJSONValue;
sTmp: String;
rTmp: RawByteString;
iTmp: Integer;
cTmp: Cardinal;
uTmp: UInt64;
bTmp: Boolean;
begin
Result := False;
if not (Self is TJSONObject) then
Exit;
ValVal := TJSONObject(Self).GetValue(Key);
if not Assigned(ValVal) then
Exit;
if TypeInfo(T) = TypeInfo(String) then
begin
// Decode UTF8
Result := ValVal.TryGetValue(sTmp);
PString(@Data)^ := UnUTF(sTmp);
end else if TypeInfo(T) = TypeInfo(RawByteString) then
begin
// Keep UTF8
Result := ValVal.TryGetValue(rTmp);
PRawByteString(@Data)^ := rTmp;
end else if TypeInfo(T) = TypeInfo(Integer) then
begin
Result := ValVal.TryGetValue(iTmp);
PInteger(@Data)^ := iTmp;
end else if TypeInfo(T) = TypeInfo(Cardinal) then
begin
Result := ValVal.TryGetValue(cTmp);
PCardinal(@Data)^ := cTmp;
end else if TypeInfo(T) = TypeInfo(UInt64) then
begin
Result := ValVal.TryGetValue(uTmp);
PUInt64(@Data)^ := uTmp;
end else if TypeInfo(T) = TypeInfo(Boolean) then
begin
Result := ValVal.TryGetValue(bTmp);
PBoolean(@Data)^ := bTmp;
end
// else raise Exception.Create('Unknown data type: ' + DataType.ToString);
end;
procedure ProcessICQLink(Data: String);
var
Contact: TICQContact;
DataArr: TArray;
begin
Data := Data.Replace('icq://', '').Replace('icq:', '');
DataArr := Data.Split(['/']);
if (DataArr[0] = 'people') or (DataArr[0] = 'info') or (DataArr[0] = 'profile') then
begin
Contact := Account.AccProto.getContact(DataArr[1]);
if Assigned(Contact) then
Contact.ViewInfo;
end else if DataArr[0] = 'chat' then
chatFrm.OpenOn(Account.AccProto.GetContact(DataArr[1]))
else if (DataArr[0] = 's') or (DataArr[0] = 'sticker') or (DataArr[0] = 'stickers') then
begin
if OnlFeature(Account.AccProto) then
begin
chatFrm.ChatBox.Call('openStickersManager', []);
chatFrm.ChatBox.Call('startStickersSearch', ['storeid:' + DataArr[1]]);
end;
end;
end;
initialization
g_hLib_User32 := LoadLibrary('user32.dll');
if g_hLib_User32 = 0 then
raise Exception.Create('LoadLibrary(user32.dll) failed');
@g_pUpdateLayeredWindow := GetProcAddress(g_hLib_User32, 'UpdateLayeredWindow');
ListsCS := TCriticalSection.Create;
finalization
g_pUpdateLayeredWindow := NIL;
if g_hLib_User32 <> 0 then
FreeLibrary(g_hLib_User32);
g_hLib_User32 := 0;
FreeAndNil(ListsCS);
end.