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

3362 lines
96 KiB
Plaintext

{
This file is part of R&Q.
Under same license
}
unit utilLib;
{$I RnQConfig.inc}
interface
uses
Winapi.Windows, System.SysUtils, Vcl.Graphics, System.Classes, System.Generics.Collections,
System.SyncObjs, Vcl.Forms, Vcl.ExtCtrls, System.Types, System.JSON, System.DateUtils, System.StrUtils,
globalLib, outboxLib, RQUtil, RnQNet, RnQZip, RDGlobal, events, ICQCommon, ICQConsts, ICQContacts, ICQSession,
Murmur2, Xml.VerySimple;
{$I PubRTTI.inc}
type
TParams = TArray;
TAlphaWindows = (AW_CL, AW_CHAT, AW_BOTH);
TUpdateInfo = record
hasNew, beta: Boolean;
error, text, changelog, distrib: String;
end;
{$I NoRTTI.inc}
TActionKind = (AK_PROCESSOUTBOX, AK_PROCESSINFORETRIEVE, AK_PROCESSAVATARDOWNLOAD,
AK_SENDSTATUS, AK_GETLASTSEEN, AK_UPDATEDB, AK_UPDATEVIEWINFO, AK_FLUSHPACKETS, AK_FLUSHEVENTS, AK_QUIT,
AK_SAVEALL, AK_SAVECONFIG, AK_SAVEDB, AK_SAVEGROUPS, AK_SAVEUINLISTS, AK_SAVEINBOX, AK_SAVEOUTBOX,
AK_SAVEXSTATUSES, AK_SAVEREACTIONS);
TActionManager = class
private
TerminateEvents: TEvent;
ActiveActions: array [TActionKind] of TAnonTask;
public
Contacts: TUIDS;
constructor Create;
destructor Destroy; override;
procedure Execute(Kind: TActionKind; Delay: Integer = 0);
procedure ProcessAction(Kind: TActionKind);
procedure AddContact(const UID: TUID);
end;
TTimerClass = class
public
procedure OnBlinkTimer(Sender: TObject);
procedure OnTimer(Sender: TObject);
end;
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;
function unexistant(const uin: TUID): Boolean;
function isAbort(const pluginReply: AnsiString): Boolean;
procedure ReloadCurrentLang;
function ShowUsers(ShowConflictMsg: Boolean = False): TUID;
function CheckAccPass(const uid: TUID; const db: String; var pPass: String): Boolean;
function getLeadingInMsg(const s: string; ofs: integer = 1): string;
{$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;
function behave(ev: Thevent; kind: integer = -1): Boolean;
procedure InitTimers;
procedure UninitTimers;
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 Check4Update;
function AutoCheckUpdates: Boolean;
function CheckUpdates(var UpdateInfo: TUpdateInfo; Auto: Boolean = False): Boolean;
function applyVars(c: TICQContact; const s: String): String;
function getXStatusMsgFor(c: TICQContact): string;
procedure toggleOnlyOnline;
procedure SendProtoMsg(var oe: Toevent);
procedure AddOutgoingMessage(Cnt: TICQContact; const Text, Binary: String; Time: TDateTime; Flags: DWord; MsgID: UInt64 = 0; const WID: String = ''; Patch: Boolean = False);
procedure SendEmail2Mail(const email: String);
function childParent(child, parent: integer): Boolean;
procedure myBeep;
procedure sortCL(cl: TRnQCList);
procedure sortCLbyGroups(cl: TRnQCList);
procedure UpdateViewInfo(c: TICQContact);
function isEmailAddress(const s: string; start: integer): integer;
procedure notAvailable;
// strings
function mb(q: extended): string;
// 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;
procedure MoveToGroup(c: TICQContact; group: Integer; const name: String);
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): String;
procedure str2beh(const b, s: String); overload;
function str2beh(s: String): Tbehaviour; overload;
function str2status(const s: String): byte;
function str2visibility(const s: String): Tvisibility;
// window management
procedure ToggleMainFormBorder(SetBorder: Boolean = False; HasBorder: Boolean = True);
procedure MainFormHandleUpdate;
procedure ShowForm(WhatForm: TWhatForm; const Page: String = ''; Mode: TFrmViewMode = vmFull);
function PrefIsVisiblePage(const pf: String): Boolean;
procedure RestoreForegroundWindow;
procedure ApplyTransparency(Window: TAlphaWindows = AW_BOTH; Forced: Integer = -1);
// file management
function delSUBtree(subPath: string): Boolean;
function deltree(path: string): Boolean;
function deleteFromTo(const fn: string; from, to_: integer): Boolean;
procedure SaveAccountAsync(What: TActionKind = AK_SAVEALL);
procedure SaveAccountSync(What: TActionKind = AK_SAVEALL);
function SaveAccount(What: TActionKind = AK_SAVEALL): Boolean;
function old_LoadDB(zp: TZipFile): Boolean;
procedure old_LoadLists(const pr: TICQSession; zp: TZipFile; const uPath: String);
procedure old_LoadExtSts(zp: TZipFile);
procedure old_LoadSpamQuests(zp: TZipFile);
procedure old_LoadProxies(zp: TZipFile);
procedure old_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;
function UnFakeUIN(uin: Int64): TUID;
function str2sortby(const s: String): TsortBy;
procedure CheckBDays;
function StringFromFile(const FileName: TFileName): RawByteString;
function parseMsgImages(const imgStr: RawByteString): TBytes;
procedure GetMsgImages(Imgs: TBytes; var ImgList: TArray);
procedure CacheType(const url, ctype: RawByteString);
function CacheImage(var mem: TMemoryStream; const url, ext: RawByteString): Boolean;
function CacheTextImage(var mem: TMemoryStream; const url, ext: RawByteString): Boolean;
function IsSVGMime(const url: RawByteString): Boolean;
function IsLottieMime(const url: RawByteString): Boolean;
function IsLottieFile(const fname: String): 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);
function AvatarUsePalette10: Boolean;
function IsTen: Boolean;
function IsEight: Boolean;
function IsEightOne: Boolean;
function IsElevated: Boolean;
function GetActiveMonitorCount: Integer;
procedure CheckAutoconnect;
procedure CheckTopMost;
function UpdateAccountEncryption(const NewPassword: String): Boolean;
// 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_BOT = 144;
DBFK_ICONCOLOR_AB = 138;
DBFK_ICONCOLOR_AT = 139;
DBFK_ICONCOLOR_PB = 140;
DBFK_ICONCOLOR_PT = 141;
var
AccountSaveCS: TCriticalSection;
ActionManager: TActionManager;
TimerClass: TTimerClass;
MainTimer: TTimer;
LastMonCnt: Integer;
SkipAutoconnect: Boolean = False;
implementation
uses
System.Math, System.UITypes,
{$IFDEF UNICODE}
System.AnsiStrings,
{$ENDIF UNICODE}
Base64, RDFileUtil, RDUtils, RnQSysUtils, RQThemes, RQLog, RnQDialogs, RnQLangs, RnQBinUtils, RnQGlobal, RnQCrypt,
RnQTrayLib, RnQTips, Hook, RnQPrefsLib, RnQMacros, RnQ_Avatars, RnQStrings, RnQGraphics32, Nodes, SQLiteDB,
SciterLib, themesLib, roasterLib, iniLib, pluginutil, pluginLib, groupsLib,
Protocols_All, Protocol_ICQ,
prefSheet, viewinfoDlg, history;
var
SaveActions: TArray = [AK_SAVEALL, AK_SAVECONFIG, AK_SAVEDB, AK_SAVEGROUPS, AK_SAVEUINLISTS, AK_SAVEINBOX, AK_SAVEOUTBOX, AK_SAVEXSTATUSES, AK_SAVEREACTIONS];
function str2sortby(const s: String): TsortBy;
begin
result := low(TsortBy);
while result <= high(TsortBy) do
if s = sortby2str[result] then
exit
else
inc(result);
result := SB_EVENT;
end;
procedure old_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;
procedure old_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 old_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 old_LoadProxies(zp: TZipFile);
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(AllProxies);
// 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(AllProxies);
SetLength(AllProxies, i + 1);
ClearProxy(AllProxies[i]);
AllProxies[i].name := UnUTF(l);
end
else if length(AllProxies) > 0 then
begin
// if h = 'proxy-ver5' then
// if yesno then
// AllProxies[i].proto := PP_SOCKS5
// else
// AllProxies[i].proto := PP_SOCKS4
// else if h='proxy' then pProxys[i].enabled:=yesno
if h = 'proxy-auth' then
AllProxies[i].auth := yesno
else if h = 'proxy-user' then
AllProxies[i].user := UnUTF(l)
else if h = 'proxy-ntlm' then
AllProxies[i].NTLM := yesno
else if h = 'proxy-pass' then
AllProxies[i].pwd := UnUTF(passDecrypt(l))
else if h = 'proxy-pass64' then
AllProxies[i].pwd := UnUTF(passDecrypt(Base64DecodeString(l)))
else if h = 'proxy-host' then
AllProxies[i].addr.host := UnUTF(l)
else if h = 'proxy-port' then
AllProxies[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;
AllProxies[i].proto := PP_NONE;
end
else
AllProxies[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 SaveAccountAsync(What: TActionKind = AK_SAVEALL);
begin
TThread.CreateAnonymousThread(procedure
begin
if not Running then
Exit;
AccountSaveCS.Acquire;
try
SaveAccount(What);
finally
AccountSaveCS.Release;
end;
end).Start;
end;
procedure SaveAccountSync(What: TActionKind = AK_SAVEALL);
begin
AccountSaveCS.WaitFor(3000);
AccountSaveCS.Acquire;
try
SaveAccount(What);
finally
AccountSaveCS.Release;
end;
end;
procedure SavePreConfig;
var
Saved: Boolean;
MemStream: TMemoryStream;
FileOld, FileNew, FileBak: String;
ZipFile: TZipFile;
procedure AddFile2Zip(const Filename: String; const Config: RawByteString);
begin
ZipFile.AddFile(Filename, 0, AccPass, Config);
end;
begin
ZipFile := TZipFile.Create;
ZipFile.ZipFileComment := 'DB file of R&Q ver.' + IntToStr(RnQBuild);
AddFile2Zip(ConfigFileName, MainPrefs.GetPreloadPrefs);
try
MemStream := TMemoryStream.Create;
ZipFile.SaveToStream(MemStream);
MemStream.SaveToFile(Account.ProtoPath + dbFileName + '5.new');
MemStream.Free;
Saved := True;
except
MsgDlg('Error on saving DB5', True, mtError);
Saved := False;
end;
ZipFile.Free;
if Saved then
try
if FileExists(Account.ProtoPath + dbFileName + '5') then
begin
FileOld := Account.ProtoPath + dbFileName + '5';
FileNew := Account.ProtoPath + dbFileName + '5.new';
FileBak := Account.ProtoPath + dbFileName + '5.bak';
if MakeBackups then
ReplaceFile(PChar(FileOld), PChar(FileNew), PChar(FileBak), REPLACEFILE_IGNORE_MERGE_ERRORS, nil, nil)
else
ReplaceFile(PChar(FileOld), PChar(FileNew), nil, REPLACEFILE_IGNORE_MERGE_ERRORS, nil, nil)
end else
// DeleteFile(uPath+dbFileName + '5');
RenameFile(Account.ProtoPath + dbFileName + '5.new', Account.ProtoPath + dbFileName + '5');
except
MsgDlg('Error saving DB', True, mtError);
end;
end;
function SaveAccount(What: TActionKind = AK_SAVEALL): Boolean;
begin
Result := False;
if FantomWork then
Exit;
if (What = AK_SAVEALL) or (What = AK_SAVECONFIG) then
begin
SavePreConfig;
SQLDB.SaveConfig;
end;
if (What = AK_SAVEALL) or (What = AK_SAVEDB) then
SQLDB.SaveContactsDB;
if (What = AK_SAVEALL) or (What = AK_SAVEGROUPS) then
SQLDB.SaveGroups;
if (What = AK_SAVEALL) or (What = AK_SAVEUINLISTS) then
SQLDB.SaveUINLists;
if (What = AK_SAVEALL) or (What = AK_SAVEINBOX) then
SQLDB.SaveInbox;
if (What = AK_SAVEALL) or (What = AK_SAVEOUTBOX) then
SQLDB.SaveOutbox;
if (What = AK_SAVEALL) or (What = AK_SAVEXSTATUSES) then
SQLDB.SaveXStatuses;
if (What = AK_SAVEALL) or (What = AK_SAVEREACTIONS) then
SQLDB.SaveReactions;
if What = AK_SAVEALL then
begin
SQLDB.SaveMacros;
SQLDB.SaveSpamQuests;
SQLDB.SaveProxies;
LastAccountSave := Now;
end;
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;
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: TArray);
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));
ImgList := ImgList + [Copy(Imgs, Pos, Size)];
Inc(Pos, Size);
except
Break;
end;
until Pos <= Length(Imgs);
end;
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;
procedure RestoreForegroundWindow;
begin
if oldForewindow = 0 then
Exit;
ForceForegroundWindow(oldForewindow);
oldForewindow := 0;
end;
procedure ApplyTransparency(Window: TAlphaWindows = AW_BOTH; Forced: Integer = -1);
var
ExStyle: Integer;
CLAlphaBlend, ChatAlphaBlend: Boolean;
AlphaValue: Integer;
begin
if not Running then
Exit;
if (Window = AW_CL) or (Window = AW_BOTH) then
begin
ExStyle := GetWindowLongPtr(UI.CL.Window, GWL_EXSTYLE);
CLAlphaBlend := transparency.forRoster or (Forced >= 0);
if CLAlphaBlend then
begin
if (ExStyle and WS_EX_LAYERED) = 0 then
SetWindowLongPtr(UI.CL.Window, GWL_EXSTYLE, ExStyle or WS_EX_LAYERED);
if Forced >= 0 then
AlphaValue := Forced
else if UI.CL.Window = GetForegroundWindow then
AlphaValue := transparency.active
else
AlphaValue := transparency.inactive;
SetLayeredWindowAttributes(UI.CL.Window, 0, AlphaValue, LWA_ALPHA);
end else if (ExStyle and WS_EX_LAYERED) <> 0 then
SetWindowLongPtr(UI.CL.Window, GWL_EXSTYLE, ExStyle and not WS_EX_LAYERED);
end;
if (Window = AW_CHAT) or (Window = AW_BOTH) then
begin
ExStyle := GetWindowLongPtr(UI.Chat.Window, GWL_EXSTYLE);
ChatAlphaBlend := transparency.forChat or (Forced >= 0);
if ChatAlphaBlend then
begin
if (ExStyle and WS_EX_LAYERED) = 0 then
SetWindowLongPtr(UI.Chat.Window, GWL_EXSTYLE, ExStyle or WS_EX_LAYERED);
if Forced >= 0 then
AlphaValue := Forced
else if UI.Chat.Window = GetForegroundWindow then
AlphaValue := transparency.active
else
AlphaValue := transparency.inactive;
SetLayeredWindowAttributes(UI.Chat.Window, 0, AlphaValue, LWA_ALPHA);
end else if (ExStyle and WS_EX_LAYERED) <> 0 then
SetWindowLongPtr(UI.Chat.Window, GWL_EXSTYLE, ExStyle and not WS_EX_LAYERED);
end;
end;
function old_str2db(const s: RawByteString; var ok: Boolean): TRnQCList;
const
ErrCorrupted = 'The contacts database is corrupted, some data is lost';
var
t, l, i: Integer;
d: RawByteString;
c: TICQContact;
vUID: TUID;
begin
ok := false;
Result := TRnQCList.Create;
c := nil;
i := 0;
while i < Length(s) do
begin
d := '';
if Length(s) - pred(i) < 8 then
begin
msgDlg(ErrCorrupted, True, mtError);
exit;
end;
try
t := dword_LEat(s, i + 1); // 1234
l := dword_LEat(s, i + 5); // 5678
if Length(s) - pred(i) < l then
begin
msgDlg(ErrCorrupted, True, mtError);
exit;
end;
d := copy(s, i + 9, l);
inc(i, 8 + l);
if (t <> DBFK_OLDUIN) and (t <> DBFK_UID) and not Assigned(c) then
Continue;
case t of
DBFK_OLDUIN:
if str2int(d) > 0 then
c := Result.add(IntToStr(str2int(d)));
DBFK_UID:
if d > '' then
begin
vUID := UnUTF(d);
c := Result.add(vUID);
end;
DBFK_GROUP:
begin
system.move(d[1], c.group, 4);
if not groups.exists(c.group) then
c.group := 0;
end;
else
c.ParseDBrow(t, d);
end;
SetLength(d, 0);
except
end;
end;
ok := True;
end;
function old_LoadDB(zp: TZipFile): Boolean;
var
s: RawByteString;
zf: TZipFile;
i: integer;
begin
Result := False;
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 := old_str2db(s, result);
TICQSession.ContactsDB.Add(Account.AccProto.MyAccNum)
end;
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;
procedure sortCL(cl: TRnQCList);
begin
cl.sort(compContacts)
end;
procedure sortCLbyGroups(cl: TRnQCList);
begin
cl.sort(compContactsByGroup)
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);
begin
case WhatForm of
WF_SHEET: UI.OpenPrefs(Page, Mode = vmFull);
WF_USERS: UI.SwitchUser;
end;
end;
function ShowUsers(ShowConflictMsg: Boolean = False): TUID;
begin
Result := UI.SwitchUser(ShowConflictMsg);
end;
function CheckAccPass(const uid: TUID; const db: String; var pPass: String): Boolean;
begin
pPass := UI.EnterPassword(GetTranslation('Account password') + ' (' + uid + ')', 16);
if not (pPass = '') then
begin
if CheckZipFilePass(db, configFileName, pPass) then
Result := True
else
begin
pPass := '';
Result := False;
TThread.CreateAnonymousThread(procedure
begin
TThread.Synchronize(nil, procedure
begin
MsgDlg('Wrong password', True, mtWarning)
end);
end).Start;
end
end
else
begin
Result := False;
//MsgDlg('Please enter password', True, mtWarning)
end;
end;
procedure UpdateViewInfo(c: TICQContact);
begin
if not UpdateViewInfoQ.Exists(c) then
begin
UpdateViewInfoQ.Add(c);
ActionManager.Execute(AK_UPDATEVIEWINFO, 500);
end;
end;
procedure old_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.RemoveExpiredEvents;
end;
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;
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;
procedure myBeep;
begin
if playSounds then
beep
end;
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;
procedure AddOutgoingMessage(Cnt: TICQContact; const Text, Binary: String; Time: TDateTime; Flags: DWord; MsgID: UInt64 = 0; const WID: String = ''; Patch: Boolean = False);
var
ev: Thevent;
begin
if Patch then
begin
ev := SQLDB.GetByMsgID(Cnt.UID, MsgID, False);
if ev = nil then
Exit;
ev.setData(UTF(Text), []);
ev.flags := ev.flags or Flags;
ev.WID := WID;
end else
ev := Thevent.new(EK_msg, Cnt, Account.AccProto.GetMyInfo, Time, UTF(Text), [], Flags, MsgID, WID);
ev.outgoing := True;
if Length(Binary) > 0 then
ev.parseData(Binary);
if Patch then
begin
WriteToHistory(ev, nil, True);
UI.Chat.UpdateEvent(Cnt, ev.clone)
end
else
begin
if logpref.writehistory and (BE_save in behaviour[ev.kind].trig) and (Flags and IF_not_save_hist = 0) then
WriteToHistory(ev);
UI.Chat.AddEvent(Cnt, ev.clone);
end;
// if oe.flags and IF_not_show_chat = 0 then
// chatFrm.addEvent_openchat(c, 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 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);
ActionManager.Execute(AK_SAVECONFIG, SaveDelay);
plugins.castEvList(PE_LIST_ADD, PL_ROSTER, c);
end;
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;
ActionManager.Execute(AK_SAVEGROUPS, SaveDelay);
Result := AddToRoster(c, IsLocal);
if not Result then
roasterLib.Update(c);
end;
procedure MoveToGroup(c: TICQContact; group: Integer; const name: String);
begin
if not Groups.Exists(group) then
begin
Groups.AddWithValues(group, name);
Groups.SetLocal(group, False);
end;
c.Group := group;
c.CntIsLocal := False;
NotInList.Remove(c);
Account.AccProto.AddContactToCL(c);
roasterlib.Update(c);
roasterLib.Focus(c);
ActionManager.Execute(AK_SAVEGROUPS, SaveDelay);
plugins.castEvList(PE_LIST_ADD, PL_ROSTER, c);
end;
function AddToNIL(c: TICQContact; IsBulk: Boolean = False): Boolean;
begin
Result := False;
Account.AccProto.RemoveContact(c);
if not NotInList.Add(c) then
Exit;
if not IsBulk then
begin
roasterLib.update(c);
ActionManager.Execute(AK_SAVECONFIG, SaveDelay);
end;
plugins.castEvList(PE_LIST_ADD, PL_NIL, c);
Result := True;
end;
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.UID);
end;
end;
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;
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;
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;
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, Point(x, y), statusPics[s, inv])
end;
function countContactsIn(proto: TICQSession; const st: byte): integer;
begin
Result := 0;
for var cnt in proto.readList(LT_ROSTER) do
if cnt.getStatus = st then
Inc(Result);
end;
procedure ToggleOnlyOnline;
begin
roasterLib.SetOnlyOnline(not showOnlyOnline);
end;
function applyVars(c: TICQContact; const s: String): 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
{c.connection.ip}
s1 := getTranslation(Str_unk);
s2 := getTranslation(Str_unk);
result := template(result, ['%you%', c.displayed, '%displayed%', 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)
else
result := applyVars(NIL, ExtStsStrings[Account.AccProto.CurXStatus].Desc)
end;
procedure Check4Update;
begin
if ConnectionAvailable then
if AutoCheckUpdates then
UI.OpenUpdater;
end;
function AutoCheckUpdates: Boolean;
var
UpdateInfo: TUpdateInfo;
begin
Result := CheckUpdates(UpdateInfo, True);
end;
function CheckUpdates(var UpdateInfo: TUpdateInfo; Auto: Boolean = False): Boolean;
var
RespStr, Tag, ReleasesPage, DistrLink, Changelog, Publish: String;
PublishedAt: TDateTime;
Version: TArray;
JSON, Assets: TJSONArray;
Release: TJSONValue;
Prerelease: Boolean;
Ver, Build: Integer;
begin
CheckUpdate.Checking := True;
Result := False;
UpdateInfo.error := '';
LoadFromURLAsString('https://code.highspec.ru/api/v1/repos/Mikanoshi/RnQ/releases', RespStr);
CheckUpdate.Checking := False;
if (Trim(RespStr) = '') or not ParseJSON(RespStr, JSON) then
begin
UpdateInfo.error := GetTranslation('Error checking for updates');
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);
Result := (Ver > RnQBuild) or (CheckUpdate.Betas and (Build > RnQBuildCustom));
if Auto then
Exit;
ReleasesPage := 'https://code.highspec.ru/Mikanoshi/RnQ/releases';
Release.GetValueSafe('prerelease', Prerelease);
Release.GetValueSafe('body', Changelog);
Release.GetValueSafe('published_at', Publish);
Assets := TJSONArray(TJSONObject(Release).GetValue('assets'));
if Assigned(Assets) and (Assets.Count > 0) then
Assets.Items[0].GetValueSafe('browser_download_url', DistrLink);
PublishedAt := ISO8601ToDate(Publish, False);
UpdateInfo.beta := Prerelease;
UpdateInfo.changelog := Changelog;
UpdateInfo.distrib := DistrLink;
if Result then
begin
UpdateInfo.text := GetTranslation('There is a new build available (%s)\nPublished at %s', [Tag, FormatDatetime(TimeFormat.info, PublishedAt)]);
Result := True;
end else
UpdateInfo.text := GetTranslation('No new version available');
UpdateInfo.hasNew := Result;
ActionManager.Execute(AK_SAVECONFIG, SaveDelay);
finally
FreeAndNil(JSON);
end;
end;
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.UID) 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;
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 UI.Chat.IsChatOpen(SpamCnt) then
UI.Chat.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)
else
msgDlg(getTranslation('Spam filtered from %s', [c.displayed + ' (' + c.uid + ')']), False, mtInformation);
exit;
end;
Result := EnableIgnoreList and IgnoreList.exists(c);
end;
function str2status(const s: String): 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;
function str2visibility(const s: String): 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;
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;
procedure FreeDB(var db: TRnQCList);
begin
if not Assigned(db) then
Exit;
ClearDB(db);
FreeAndNil(db);
end;
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 InitTimers;
begin
ActionManager := TActionManager.Create;
MainTimer.OnTimer := TimerClass.OnTimer;
MainTimer.Interval := 1000;
MainTimer.Enabled := True;
BlinkTimer.OnTimer := TimerClass.OnBlinkTimer;
BlinkTimer.Interval := blinkSpeed * 150 + 250;
BlinkTimer.Enabled := False;
end;
procedure UninitTimers;
begin
MainTimer.Enabled := False;
BlinkTimer.Enabled := False;
FreeAndNil(ActionManager);
end;
function behave(ev: Thevent; kind: integer = -1): 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;
// 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 UI.Chat.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;
//
// // SAVE
// if logpref.writehistory and (BE_save in behaviour[ev0.kind].trig) then
// WriteToHistory(ev0, ev0.who);
// forceadd := UI.Chat.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 UI.Chat.OpenChat(ev0.who);
// if not forceadd then
// UI.Chat.SetUnreadEvent(ev0.who, ev0);
// end;
// // HISTORY
// if BE_history in behaviour[ev0.kind].trig then
// if forceadd then
// UI.Chat.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
// UI.Tips.Add(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 UI.Chat.chats.idxOfUIN(spamsFilename) >= 0 then
// if UI.Chat.IsChatOpen(vProto.getContact(spamsFilename)) then
// UI.Chat.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
UI.CL.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.UID) + '.' + 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
UI.Tips.Add(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}
forceadd := UI.Chat.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 UI.Chat.OpenChat(vCnt, False, True) then
begin
forceadd := False;
UI.Chat.SetUnreadEvent(vCnt, ev);
if not BossMode.isBossKeyOn and (BE_flashchat in behaviour[ev.kind].trig) then
if not (ev.kind = EK_BUZZ) then
UI.Chat.Flash;
end;
// HISTORY
if BE_history in behaviour[ev.kind].trig then
if forceadd then
if UI.Chat.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
UI.Chat.Flash;
// POP UP
if not BossMode.isBossKeyOn and (BE_popup in behaviour[ev.kind].trig) and not QuietEvent then
if not UI.Chat.IsVisible then
if not vProto.getStatusDisable.OpenChat then
if ev.flags and IF_no_matter = 0 then
UI.Chat.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 UI.Chat.Visible then
UI.Chat.Shake;
// 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
begin
// if ev.flags and IF_no_matter = 0 then
eventQ.Add(ev.clone);
if Assigned(UI) and Assigned(UI.Chat) then
with UI.Chat do
begin
if AutoConsumeEvents and IsVisible then
SawAllHere;
RefreshTaskbarButtons;
end;
end;
end;
function beh2str(kind: integer): String;
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;
procedure str2beh(const b, s: String);
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: String): Tbehaviour;
const
tipstr = 'tip';
function extractPar(const lab: String): String;
var
i, j: integer;
begin
result := '';
i := Pos(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;
var
tS: String;
begin
result.trig := [];
result.tiptime := 0;
result.tiptimes := False;
result.tiptimeplus := 0;
s := LowerCase(s);
result.tiptimes := ContainsText(s, 'times');
try
tS := extractPar('times');
if tS <> '' then
result.tiptimeplus := strToInt(tS)
except
end;
if ContainsText(s, tipstr) then
include(result.trig, BE_tip);
try
tS := extractPar(tipstr);
if tS <> '' then
result.tiptime := strToInt(tS)
except
end;
if ContainsText(s, 'tray') then
include(result.trig, BE_tray);
if ContainsText(s, 'openchat') then
include(result.trig, BE_openchat);
if ContainsText(s, 'save') then
include(result.trig, BE_save);
if ContainsText(s, 'sound') then
include(result.trig, BE_sound);
if ContainsText(s, 'history') then
include(result.trig, BE_history);
if ContainsText(s, 'popup') then
include(result.trig, BE_popup);
if ContainsText(s, 'flashchat') then
include(result.trig, BE_flashchat);
if ContainsText(s, 'balloon') then
include(result.trig, be_BALLOON);
end;
function RegisterHK(id: Integer; hk: Word): Boolean;
var
m: Integer;
begin
m := 0;
if hk and scCommand <> 0 then
Inc(m, MOD_WIN);
if hk and scShift <> 0 then
Inc(m, MOD_SHIFT);
if hk and scCtrl <> 0 then
Inc(m, MOD_CONTROL);
if hk and scAlt <> 0 then
Inc(m, MOD_ALT);
Result := RegisterHotKey(Application.Handle, id, m, LOBYTE(hk));
end;
function UpdateSWHotkeys: Boolean;
var
I: Integer;
begin
Result := False;
if (UI = nil) or (UI.CL = 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;
procedure RemoveSWHotkeys;
var
I: Integer;
begin
for I := 0 to 200 do
UnregisterHotKey(Application.Handle, I);
end;
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);
ActionManager.Execute(AK_SAVECONFIG, SaveDelay);
end;
function AddToQuietlist(c: TICQContact): Boolean;
begin
Result := False;
if (c = nil) or quietList.exists(c) then
Exit;
quietList.Add(c);
ActionManager.Execute(AK_SAVECONFIG, SaveDelay);
Result := True;
end;
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));
}
ActionManager.Execute(AK_SAVECONFIG, SaveDelay);
end;
procedure removeFromQuietlist(c: TICQContact);
begin
if (c = nil) or not quietList.exists(c) then
Exit;
quietList.remove(c);
ActionManager.Execute(AK_SAVECONFIG, SaveDelay);
end;
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.UID);
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;
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
vCnt: TICQContact;
begin
if not Assigned(ev) then
Exit;
if Assigned(ev.otherpeer) then
vCnt := ev.otherpeer
else
vCnt := ev.who;
roasterLib.UpdateInPlace(vCnt);
UI.Tips.Remove(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 UI.Chat do
begin
OpenOn(vCnt);
SetUnreadEvent(vCnt, ev);
MoveToEvent(vCnt, ev);
if not HasEvent(vCnt, ev) then
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);
except end;
end;
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);
ActionManager.Execute(AK_SAVEINBOX, SaveDelay);
end;
procedure TrayAction;
begin
if not ChopAndRealizeEvent then
if UseSingleClickTray or not UI.CL.Visible then
UI.CL.ToggleVisible
else if not DoConnect then
UI.CL.ToggleVisible;
end;
function ints2cl(proto: TICQSession; a: 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;
function DoLock: 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 UI.CL.Visible then
UI.CL.ToggleVisible;
UI.Chat.Hide;
Locked := True;
Result := True;
if not StartingLock then
ActionManager.Execute(AK_SAVECONFIG, SaveDelay);
UI.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;
procedure DoUnlock;
begin
Locked := False;
if not StartingLock then
ActionManager.Execute(AK_SAVECONFIG, SaveDelay);
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;
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;
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;
procedure assignImgIco(img: TImage; ico: Ticon);
begin
img.Picture.Icon.Assign(ico);
img.Width := ico.Width * 2;
img.Height := ico.Height * 2;
end;
procedure MainFormHandleUpdate;
begin
if Assigned(StatusIcon) then
StatusIcon.handleChanged(UI.CL.Window);
UpdateSWHotkeys;
end;
procedure ReloadCurrentLang;
begin
ClearLanguage;
LoadSomeLanguage;
UI.UpdateTranslations;
end;
procedure ToggleMainFormBorder(SetBorder: Boolean = False; HasBorder: Boolean = True);
begin
if SetBorder then
UI.CL.SetBorder(HasBorder)
else
begin
ShowMainBorder := not ShowMainBorder;
UI.CL.SetBorder(ShowMainBorder);
end;
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;
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;
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;
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;
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;
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;
procedure ProcessOevent(oe: Toevent);
begin
case oe.kind of
OE_msg:
SendProtoMsg(oe);
OE_AUTH:
oe.whom.auth;
OE_AUTHDENIED:
oe.whom.AuthDenied(oe.info);
// OE_ADDEDYOU:
// SendICQAddedYou(oe.whom);
// OE_CONTACTS:
// SendICQContacts(oe.whom, oe.flags, oe.cl);
end;
end;
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}
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);
MainPrefs.addPrefBool(PrefIsShowBDFirst, bPrefIsShowBDFirst);
MainPrefs.addPrefBool(PrefIsShowBDBefore, bPrefIsShowBDBefore);
MainPrefs.addPrefInt(PrefShowBDFirst, iPrefShowBDFirst);
MainPrefs.addPrefInt(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
UI.Tips.Add(nil, nil, c);
if k = 0 then // Play sound
begin
played := False;
if UseContactThemes and Assigned(ContactsTheme) then
begin
ss := TPicName(c.UID) + '.' + 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 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;
function IsSVGMime(const url: RawByteString): Boolean;
begin
Result := ImgCacheInfo.ReadString(url, 'mime', '') = 'image/svg+xml';
end;
function IsLottieMime(const url: RawByteString): Boolean;
begin
Result := ImgCacheInfo.ReadString(url, 'mime', '') = 'fake/lottie';
end;
function IsLottieFile(const fname: String): Boolean;
begin
Result := fname.StartsWith('lottie-sticker-from-') or (fname = 'lottie-sticker');
end;
function CacheTextImage(var mem: TMemoryStream; const url, ext: RawByteString): Boolean;
var
imgcache, fn, JSONStr: String;
hash: LongWord;
Xml: TXmlVerySimple;
JSON: TJSONValue;
Width, Height: Integer;
ViewBox: TArray;
begin
Result := False;
mem.Seek(0, 0);
if mem.Size = 0 then
Exit;
imgcache := myPath + 'Cache\Images\';
if not DirectoryExists(imgcache) then
ForceDirectories(imgcache);
hash := CalcMurmur2(BytesOf(url));
fn := imgcache + IntToStr(hash) + '.' + ext;
mem.SaveToFile(fn);
Width := 256;
Height := 256;
Xml := nil;
JSON := nil;
if ext = 'svg' then
try
Xml := TXmlVerySimple.Create;
Xml.LoadFromStream(mem);
if Xml.DocumentElement.HasAttribute('width') and Xml.DocumentElement.HasAttribute('height') then
begin
Width := Round(StrToFloat(Xml.DocumentElement.Attributes['width']));
Height := Round(StrToFloat(Xml.DocumentElement.Attributes['height']));
end else if Xml.DocumentElement.HasAttribute('viewBox') then
begin
ViewBox := Xml.DocumentElement.Attributes['viewBox'].Split([' ']);
if Length(ViewBox) >= 4 then
begin
Width := Round(StrToFloat(ViewBox[2]));
Height := Round(StrToFloat(ViewBox[3]));
end;
end;
finally
Xml.Free;
end else if ext = 'json' then
try
SetString(JSONStr, PChar(mem.Memory), mem.Size div SizeOf(Char));
JSON := TJSONObject.ParseJSONValue(mem.Memory, 0, mem.Size, [TJSONValue.TJSONParseOption.IsUTF8]);
if Assigned(JSON) and (JSON is TJSONObject) then
begin
if not (JSON as TJSONObject).TryGetValue('w', Width) or
not (JSON as TJSONObject).TryGetValue('h', Height) then
begin
Width := 256;
Height := 256;
end;
end;
finally
JSON.Free;
end;
ImgCacheInfo.WriteString(url, 'ext', ext);
ImgCacheInfo.WriteString(url, 'hash', IntToStr(hash));
ImgCacheInfo.WriteInteger(url, 'width', Width);
ImgCacheInfo.WriteInteger(url, 'height', Height);
ImgCacheInfo.UpdateFile;
Result := True;
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(UI.Prefs) then
UI.Prefs.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
UI.Chat.OpenOn(Account.AccProto.GetContact(DataArr[1]))
else if (DataArr[0] = 's') or (DataArr[0] = 'sticker') or (DataArr[0] = 'stickers') then
begin
UI.Chat.Call('openStickersManager', []);
UI.Chat.Call('startStickersSearch', ['storeid:' + DataArr[1]]);
end else
UI.CL.Call('openAddContactDialog', [DataArr[0]]);
end;
function IsTen: Boolean;
begin
Result := TOSVersion.Check(10)
end;
function IsEight: Boolean;
begin
Result := TOSVersion.Check(8, 0)
end;
function IsEightOne: Boolean;
begin
Result := TOSVersion.Check(8, 1)
end;
function AvatarUsePalette10: Boolean;
begin
Result := AvatarUsePalette and IsTen;
end;
function IsElevated: Boolean;
begin
Result := RnQSysUtils.IsElevated
end;
function GetActiveMonitorCount: Integer;
var
lpDisplayDevice: TDisplayDevice;
dwFlags: DWORD;
cc: DWORD;
begin
lpDisplayDevice.cb := sizeOf(lpDisplayDevice);
dwFlags := 0;
cc := 0;
Result := 0;
while EnumDisplayDevices(nil, cc, lpDisplayDevice, dwFlags) do
begin
inc(cc);
if lpDisplayDevice.StateFlags and $01 {AttachedToDesktop} <> 0 then
inc(Result)
end;
end;
procedure CheckAutoconnect;
begin
if SkipAutoconnect then
begin
SkipAutoconnect := False;
Exit;
end;
if not ConnectionAvailable then
Exit;
if StayConnected and Account.AccProto.IsOffline then
Account.AccProto.SetStatus(LastStatus, True);
if ConnectOnConnection and Account.AccProto.IsOffline and not enteringProtoPWD and (LastStatusUserSet <> Byte(SC_OFFLINE)) then
Account.AccProto.SetStatus(LastStatusUserSet, True);
end;
procedure CheckTopMost;
begin
if Assigned(UI) and Assigned(UI.CL) and UI.CL.Visible and not (alwaysOnTop = IsTopMostWindow(UI.CL.Window)) then
begin
SetTopMostWindow(Application.Handle, alwaysOnTop);
SetTopMostWindow(UI.CL.Window, alwaysOnTop);
end;
end;
function UpdateAccountEncryption(const NewPassword: String): Boolean;
var
DBEncrypted: Boolean;
begin
Result := False;
DBEncrypted := SQLDB.ManageSecurity(SA_CHECK);
if DBEncrypted then
begin
if NewPassword = '' then
begin
if SQLDB.ManageSecurity(SA_DECRYPT, AccPass) then
begin
MessageDlg(GetTranslation('Account was successfully decrypted!'), mtInformation, [mbOK]);
AccPass := NewPassword;
Result := True;
end else
MessageDlg(GetTranslation('Account was not decrypted!'), mtError, [mbOK]);
end
else
begin
if SQLDB.ManageSecurity(SA_CHANGEPASS, NewPassword) then
begin
MessageDlg(GetTranslation('Account password was modified!'), mtInformation, [mbOK]);
AccPass := NewPassword;
Result := True;
end else
MessageDlg(GetTranslation('Account password wasn''t modified!'), mtError, [mbOK]);
end;
end
else
begin
if NewPassword = '' then
begin
AccPass := '';
SQLDB.Disconnect;
SQLDB.Connect(AccPass);
Result := True;
end else if SQLDB.ManageSecurity(SA_ENCRYPT, NewPassword) then
begin
MessageDlg(GetTranslation('Account was successfully encrypted!'), mtInformation, [mbOK]);
AccPass := NewPassword;
Result := True;
end else
MessageDlg(GetTranslation('Account was not encrypted!'), mtError, [mbOK]);
end;
if Assigned(UI.Prefs) then
UI.Prefs.ResetSecurity;
end;
constructor TActionManager.Create;
begin
TerminateEvents := TEvent.Create(nil, True, False, '');
TerminateEvents.ResetEvent;
for var Kind := Low(TActionKind) to High(TActionKind) do
ActiveActions[Kind] := nil;
end;
destructor TActionManager.Destroy;
begin
for var Kind := Low(TActionKind) to High(TActionKind) do
if Assigned(ActiveActions[Kind]) then
FreeAndNil(ActiveActions[Kind]);
TerminateEvents.SetEvent;
FreeAndNil(TerminateEvents);
SetLength(Contacts, 0);
inherited;
end;
procedure TActionManager.Execute(Kind: TActionKind; Delay: Integer = 0);
begin
if not Running or not Assigned(Self) then
Exit;
if Assigned(ActiveActions[Kind]) then
if ActiveActions[Kind].Finished then
FreeAndNil(ActiveActions[Kind])
else
Exit;
if Delay = 0 then
ProcessAction(Kind)
else
begin
ActiveActions[Kind] := TAnonTask.Create(procedure
procedure MarkAsFinished;
begin
TThread.Queue(nil, procedure
begin
if Running and Assigned(ActiveActions[Kind]) then
ActiveActions[Kind].Finish;
end);
end;
var
Res: TWaitResult;
begin
if not Assigned(TerminateEvents) then
begin
MarkAsFinished;
Exit;
end;
Res := TerminateEvents.WaitFor(Delay);
MarkAsFinished;
if (Res = wrTimeout) and not TThread.Current.CheckTerminated then
begin
TThread.Queue(nil, procedure
begin
if Running then
ProcessAction(Kind);
end);
end;
end);
ActiveActions[Kind].Start;
end;
end;
procedure TActionManager.ProcessAction(Kind: TActionKind);
var
OE: TOEvent;
ReExecuteIn, Index: Integer;
SkipAction: Boolean;
begin
ReExecuteIn := 0;
SkipAction := False;
if TArray.BinarySearch(SaveActions, Kind, Index) then
if FantomWork or not Running or not SQLDB.Connected then
SkipAction := True;
if not SkipAction then
case Kind of
AK_PROCESSOUTBOX:
if Assigned(Account.AccProto) and Account.AccProto.IsOnline and outboxprocessChk then
begin
OE := Account.outbox.Pop;
if Assigned(OE) then
begin
UI.UpdateOutbox;
ProcessOevent(OE);
OE.Free;
if Account.outbox.Count > 0 then
ReExecuteIn := TimeBetweenMsgs;
end;
end;
AK_PROCESSINFORETRIEVE: // Unused
if Assigned(retrieveQ) and Assigned(Account.AccProto) and (Account.AccProto.IsOnline) and not retrieveQ.Empty then
begin
Account.AccProto.GetProfile(retrieveQ.GetAt(0).UID);
retrieveQ.Delete(0);
Execute(AK_SAVEDB, SaveDelay);
if retrieveQ.Count > 0 then
ReExecuteIn := 1000;
end;
AK_PROCESSAVATARDOWNLOAD:
if Assigned(reqAvatarsQ) and Assigned(Account.AccProto) and Account.AccProto.AvatarsSupport and not reqAvatarsQ.Empty then
begin
DownloadAvatar(reqAvatarsQ.GetAt(0));
reqAvatarsQ.Delete(0);
if reqAvatarsQ.Count > 0 then
ReExecuteIn := 1000;
end;
AK_SENDSTATUS:
if Assigned(Account.AccProto) and Account.AccProto.IsReady then
Account.AccProto.SendPresenceState;
AK_GETLASTSEEN:
if Assigned(Account.AccProto) and Account.AccProto.IsReady then
if Account.AccProto.GetLastSeen(Contacts) then
SetLength(Contacts, 0)
else
ReExecuteIn := RandomRange(20000, 30000);
AK_UPDATEDB:
if Assigned(UI) then
UI.UpdateDB;
AK_UPDATEVIEWINFO:
with UpdateViewInfoQ do
begin
ResetEnumeration;
if Assigned(UI) then
while HasMore do
viewinfoDlg.UpdateViewInfo(GetNext);
Clear;
end;
AK_FLUSHPACKETS:
FlushLogPktFile;
AK_FLUSHEVENTS:
FlushLogEvFile;
AK_QUIT:
Quit;
AK_SAVEALL, AK_SAVECONFIG, AK_SAVEDB, AK_SAVEGROUPS, AK_SAVEUINLISTS, AK_SAVEINBOX, AK_SAVEOUTBOX, AK_SAVEXSTATUSES, AK_SAVEREACTIONS:
begin
if (Kind = AK_SAVEALL) or (Kind = AK_SAVECONFIG) then
UpdateProperties;
SaveAccountAsync(Kind);
end;
end;
if ReExecuteIn > 0 then
begin
if Assigned(ActiveActions[Kind]) then
FreeAndNil(ActiveActions[Kind]);
Execute(Kind, ReExecuteIn);
end;
end;
procedure TActionManager.AddContact(const UID: TUID);
begin
if not MatchText(UID, Contacts) then
Contacts := Contacts + [UID];
end;
procedure TTimerClass.OnTimer(Sender: TObject);
var
IsSSRuning: BOOL;
Minutes: Integer;
ContactArr: TArray;
begin
if not Running or not Assigned(Account.AccProto) or (UserStartTime = 0) then
Exit;
if EventQ.Count > 0 then
EventQ.RemoveExpiredEvents;
if NoIncomingCounter > 0 then
Dec(NoIncomingCounter);
if ShowBalloonTime > 0 then
begin
Dec(ShowBalloonTime);
if ShowBalloonTime <= 0 then
StatusIcon.HideBalloon;
end;
Inc(InactiveTime, 10);
{ autohide triggers if
{ - it is enabled
{ - time set has passed
{ - the windows is visible
{ - the mouse is not over the window }
if InactiveHide and (InactiveTime >= InactiveHideTime) and UI.CL.Visible and not into(MousePos, UI.CL.GetBounds) then
UI.CL.ToggleVisible;
if InactiveTrim and (InactiveTime mod 36000 = 0) then
TrimWorkingSet;
// auto-away (isHooked is needed for keyboard handling)
if IsHooked and Account.AccProto.IsOnline then
begin
// SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, @IsSSActive, 0);
SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, @IsSSRuning, 0);
Inc(autoaway.time, 5); // we are in delay-block then 0.5s
if IsMoved and not (autoaway.ss and (IsSSRuning or IsLocked)) and not (autoaway.boss and BossMode.isBossKeyOn) then
begin
autoaway.time := 0;
if (autoaway.autoexit) and (autoaway.triggered <> TR_NONE) then
ExitFromAutoaway;
end else if (autoaway.triggered = TR_NONE) and not (Account.AccProto.GetStatus in [Byte(SC_AWAY), Byte(SC_NA)]) or
(autoaway.triggered <> TR_NONE) then
begin
if autoaway.away and (autoaway.time >= autoaway.awayTime) and (autoaway.triggered = TR_NONE) then
begin
if autoaway.setxstatus then
begin
autoaway.bakxstatus := Account.AccProto.GetXStatus;
Account.AccProto.CurXStatus := autoaway.xstatus;
end;
autoaway.bakstatus := Account.AccProto.SetStatus(Byte(SC_AWAY), True);
autoaway.triggered := TR_AWAY; // has to be set AFTER setstatus
end;
if (autoaway.na and (autoaway.time >= autoaway.naTime) and (autoaway.triggered <> TR_NA)) or
(autoaway.ss and (isSSRuning or isLocked)) or (autoaway.boss and BossMode.isBossKeyOn) then
begin
if autoaway.triggered = TR_NONE then
begin
if autoaway.setxstatus then
begin
autoaway.bakxstatus := Account.AccProto.GetXStatus;
Account.AccProto.CurXStatus := autoaway.xstatus;
end;
autoaway.bakstatus := Account.AccProto.SetStatus(Byte(SC_NA), True);
end else
Account.AccProto.SetStatus(Byte(SC_NA));
autoaway.triggered := TR_NA; // has to be set AFTER setstatus
end;
end;
end;
CheckTopMost;
if Assigned(UI) and Assigned(UI.Chat) then
UI.Chat.Chats.CheckTypingTimeAll;
// Every minute
if SecondsCount >= 60 then
begin
SecondsCount := 0;
CheckAutoconnect;
if Trunc(Now) - Trunc(LastMinuteCheck) = 1 then // Once after each midnight
begin
if Assigned(Account.AccProto) then
Account.AccProto.ApplyBalloon;
CheckBDays;
end;
LastMinuteCheck := Now;
//if Assigned(StatusIcon) and Assigned(StatusIcon.TrayIcon) then
// StatusIcon.TrayIcon.Update;
if EnableRecentlyOffline and Assigned(UI) and Assigned(UI.CL) then
begin
ContactArr := UI.CL.GetContacts(d_recent);
if Length(ContactArr) > 0 then
for var Contact in ContactArr do
if Assigned(Contact) then
if not Contact.IsRecent then
roasterLib.Update(Contact);
SetLength(ContactArr, 0);
end;
// Check for updates every 24 hours
if CheckUpdate.Enabled and (Now - CheckUpdate.Last > CheckUpdate.Every) and not CheckUpdate.Checking and not StartingLock then
Check4Update;
// Resubscribe to xstatus changes
// Actively update only for the first hour when idle
if InactiveTime < 36000 then
Account.AccProto.CheckEventSubscribe;
// Full account save every 15 minutes
Minutes := MinutesBetween(Now, LastAccountSave);
if Minutes > 15 then
begin
SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, @IsSSRuning, 0);
if (IsSSRuning or IsLocked) or not IsMoved(4*10*60) or BossMode.isBossKeyOn then
begin
// Extend to 1 hour during idle
if Minutes > 60 then
SaveAccountAsync;
end else
SaveAccountAsync;
end;
// Every 15 minutes
if SecondsCount15 >= 900 then
begin
SecondsCount15 := 0;
Account.AccProto.GetAllLastSeen;
end;
end;
Inc(SecondsCount);
Inc(SecondsCount15);
end;
procedure TTimerClass.OnBlinkTimer(Sender: TObject);
begin
if not Running or not Assigned(Account.AccProto) or (UserStartTime = 0) then
Exit;
Blinking := not Blinking;
if Assigned(StatusIcon) then
StatusIcon.Update;
end;
initialization
AccountSaveCS := TCriticalSection.Create;
TimerClass := TTimerClass.Create;
MainTimer := TTimer.Create(nil);
finalization
FreeAndNil(MainTimer);
FreeAndNil(TimerClass);
FreeAndNil(AccountSaveCS);
end.