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

3471 lines
98 KiB
Plaintext

{
This file is part of R&Q.
Under same license
}
unit utilLib;
{$I RnQConfig.inc}
interface
uses
Windows, SysUtils, Graphics, Dialogs, Classes, System.Generics.Defaults, System.Generics.Collections,
SyncObjs, Forms, Controls, StdCtrls, ExtCtrls, ComCtrls, Menus, Messages, Types, JSON, DateUtils,
StrUtils, globalLib, outboxLib, RQUtil, RnQNet, RnQZip, RDGlobal, events, ICQCommon, ICQConsts, ICQContacts, ICQSession, Murmur2, GR32;
{$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_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
constructor Create;
destructor Destroy; override;
procedure Execute(Kind: TActionKind; Delay: Integer = 0);
procedure ProcessAction(Kind: TActionKind);
end;
TTimerClass = class
public
procedure OnBlinkTimer(Sender: TObject);
procedure OnTimer(Sender: TObject);
end;
TPageControl = class(comctrls.TPageControl)
protected
procedure CNDrawitem(var Message: TWMDrawItem); message CN_DRAWITEM;
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;
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;
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; fromAM: Boolean = False): String;
function getXStatusMsgFor(c: TICQContact): string;
procedure toggleOnlyOnline;
procedure OpenURL(const pURL: String); OverLoad;
function enterUinDlg(const proto: TICQSession; var uin: TUID; const title: string = ''): Boolean;
procedure SendProtoMsg(var oe: Toevent);
procedure AddOutgoingMessage(Cnt: TICQContact; const Text, Binary: String; Time: TDateTime; Flags: DWord; MsgID: UInt64 = 0; 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;
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;
procedure MoveToGroup(c: TICQContact; group: Integer; 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 HideForm(Frm: Tform);
procedure ShowForm(WhatForm: TWhatForm; const Page: String = ''; Mode: TFrmViewMode = vmFull; Who: TICQContact = nil);
function PrefIsVisiblePage(const pf: String): Boolean;
procedure RestoreForegroundWindow;
procedure ApplyTransparency(Window: TAlphaWindows = AW_BOTH; Forced: Integer = -1);
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 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 GetWidth(chk: TCheckBox): integer;
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 CacheLottie(var mem: TMemoryStream; const url, ext: 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 CloseAllChildWindows;
procedure CheckAutoconnect;
procedure CheckTopMost;
function UpdateAccountEncryption(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;
implementation
uses
Math, UITypes, SciterLib,
{$IFDEF UNICODE}
AnsiStrings, Character,
{$ENDIF UNICODE}
Base64, RDFileUtil, RDUtils, RnQSysUtils,
RQThemes, RQLog, RnQdbDlg, RnQDialogs,
RnQLangs, RnQBinUtils, RnQGlobal, RnQCrypt, RnQPics,
RnQTrayLib, RnQTips, Hook, RnQPrefsLib, prefSheet,
mainDlg, roasterLib, iniLib, pluginutil,
selectContactsDlg, incapsulate, pluginLib,
groupsLib, outboxDlg, viewinfoDlg, history,
RnQMacros, RnQ_Avatars, RnQStrings, ThemesLib, usersDlg,
Protocols_All, Protocol_ICQ,
RnQGraphics32, Stickers, Nodes, SQLiteDB, HiddenForm, HistAllSearch;
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 OpenURL(const pURL: String);
begin
RnQSysUtils.OpenURL(pURL, UseDefaultBrowser, BrowserCmdLine);
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;
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;
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: 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(pProto: TICQSession; 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
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(Account.AccProto, 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;
procedure HideForm(Frm: Tform);
begin
if Frm = nil then
Exit;
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);
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;
MsgDlg('Wrong password', True, mtWarning)
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;
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;
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; 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 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;
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; 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(TICQContact(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, Types.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; 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, '%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, True)
else
result := applyVars(NIL, ExtStsStrings[Account.AccProto.CurXStatus].Desc, True)
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, 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;
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(Hidden.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(Hidden.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: 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;
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;
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;
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(LowerCase(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;
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 CacheLottie(var mem: TMemoryStream; const url, ext: RawByteString): Boolean;
var
imgcache, fn: String;
hash: LongWord;
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);
ImgCacheInfo.WriteString(url, 'ext', ext);
ImgCacheInfo.WriteString(url, 'hash', IntToStr(hash));
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
if OnlFeature(Account.AccProto) then
begin
UI.Chat.Call('openStickersManager', []);
UI.Chat.Call('startStickersSearch', ['storeid:' + DataArr[1]]);
end;
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 CloseAllChildWindows;
var
i: Integer;
c: Tcomponent;
begin
UI.CloseAllChildWindows;
i := childWindows.Count - 1;
while i >= 0 do
begin
c := childWindows.Items[i];
if c is TForm then
with c as TForm do
if visible then
begin
// childWindows.Items[i] := NIL;
close;
end;
dec(i);
end;
end;
procedure CheckAutoconnect;
begin
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
SetTopMost(Hidden, alwaysOnTop);
SetTopMostWindow(UI.CL.Window, alwaysOnTop);
end;
end;
function UpdateAccountEncryption(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;
var
AK: TActionKind;
begin
TerminateEvents := TEvent.Create(nil, True, False, 'AMTerminateEvents');
TerminateEvents.ResetEvent;
for AK := Low(TActionKind) to High(TActionKind) do
ActiveActions[AK] := nil;
end;
destructor TActionManager.Destroy;
var
AK: TActionKind;
begin
for AK := Low(TActionKind) to High(TActionKind) do
if Assigned(ActiveActions[AK]) then
FreeAndNil(ActiveActions[AK]);
TerminateEvents.SetEvent;
FreeAndNil(TerminateEvents);
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].Terminated then
FreeAndNil(ActiveActions[Kind])
else
Exit;
if Delay = 0 then
ProcessAction(Kind)
else
begin
ActiveActions[Kind] := TAnonTask.Create(procedure
var
Res: TWaitResult;
begin
Res := TerminateEvents.WaitFor(Delay);
if Running and (Res = wrTimeout) and not TThread.Current.CheckTerminated then
TThread.Queue(nil, procedure
begin
if Running then
ProcessAction(Kind);
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:
begin
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;
end;
AK_PROCESSINFORETRIEVE: // Unused
if Assigned(retrieveQ) 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 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_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
Execute(Kind, ReExecuteIn);
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;
end;
Inc(SecondsCount);
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.