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/ICQ/ICQSession.pas

6186 lines
188 KiB
Plaintext

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

{
This file is part of R&Q.
Under same license
}
unit ICQSession;
{$I RnQConfig.inc}
interface
uses
Windows, SysUtils, Classes, Types, JSON, Generics.Collections, Threading, ExtCtrls,
RnQGlobal, RnQNet, RDGlobal, RQUtil, RnQPrefsLib, RnQBinUtils,
ICQCommon, ICQContacts, StrUtils, ICQConsts, OverbyteIcsHttpProt,
Math, SynEcc, Stickers, SQLiteDB;
{$I NoRTTI.inc}
type
TWPResult = packed record
nick, first, last, email: String;
StsMSG: String;
BDay: TDateTime;
uin: TUID;
authRequired: Boolean;
gender: Byte;
status: Word; // 0=offline 1=online 2=don't know
age: Word;
BaseID: Word;
end; // TWPResult
TWPSearch = packed record
nick, first, last, email, city, state, keyword: String;
uin: TUID;
Token: RawByteString;
gender: Byte;
lang: AnsiString;
onlineOnly: Boolean;
country: Word;
wInterest: Word;
age: Integer;
end; // TWPSearch
TICQEvent = (
IE_error = Byte(ICQConsts.IE_error),
IE_online,
IE_offline,
IE_incoming,
IE_outgoing,
IE_msg,
IE_buzz,
IE_userinfo = Byte(High(ICQConsts.TProtoEvent)) + 20,
IE_userinfoCP,
IE_contacts,
IE_numOfContactsChanged,
IE_wpEnd,
IE_userSimpleInfo,
IE_statusChanged,
IE_authReq,
IE_auth,
IE_authDenied,
IE_wpResult,
IE_addedYou,
IE_visibilityChanged,
IE_toofast,
IE_connecting,
IE_connected,
IE_loggin,
IE_redirecting,
IE_redirected,
IE_almostOnline,
IE_serverSent,
IE_serverGot,
IE_MyInfoAck,
IE_pause,
IE_resume,
IE_ack,
IE_serverAck,
IE_msgError,
IE_Missed_MSG,
IE_sendingXStatus,
IE_ackXStatus,
IE_contactupdate,
IE_contactSelfDeleted,
IE_contactupdateInPlace,
IE_typing,
IE_avatar_changed,
IE_srvSomeInfo,
IE_MultiChat,
IE_serverHistoryReady,
IE_stickersupdate,
IE_stickersearchupdate
);
TICQPhase = (
null_, // offline
connecting_, // trying to reach the login server
login_, // performing login on login server
reconnecting_, // trying to reach the service server
relogin_, // performing login on service server
settingup_, // setting up things
online_
);
TICQSession = class;
TProtoNotify = procedure (Sender: TICQSession; event: Integer);
TICQNotify = procedure(Sender: TICQSession; event: TicqEvent) of object;
TErrorProc = reference to procedure(Resp: TPair);
THandlerProc = reference to procedure(RespStr: String);
TReturnData = (RT_None, RT_JSON);
TICQSession = class
protected
MyAccount: TUID;
// event managing
Listener: TProtoNotify;
procedure NotifyListeners(ev: TICQEvent);
public
progLogon: Double;
aProxy: Tproxy;
SupportTypingNotif,
IsSendTypingNotif: Boolean;
class var ContactsDB: TRnQCList;
function ValidICQ(const UID: TUID): Boolean; inline;
function ValidUID(const UID: TUID): Boolean; inline;
function ValidPhone(const Phone: TUID): Boolean; inline;
function ValidMail(const Mail: TUID): Boolean;
function ContactExists(const UID: TUID): Boolean;
function GetShowStr: String;
property MyAccNum: TUID read MyAccount;
private
Phase: TICQPhase;
WasUINwp: Boolean; // trigger a last result at first result
PreviousInvisible : Boolean;
// P_WebAware: Boolean;
// P_AuthNeeded: Boolean;
// P_ShowInfo: Byte;
StartingVisibility: TVisibility;
StartingStatus: TICQstatus;
CurStatus: TICQstatus;
fVisibility: TVisibility;
SNACref: TmsgID;
Cookie: RawByteString;
Refs: array [1..MaxRefs] of record
Kind: TRefKind;
UID: TUID;
end;
SavingMyInfo: record
Running: Boolean;
ACKcount: Integer;
c: TICQContact;
end;
fRoster: TRnQCList;
SpamList: TRnQCList;
fPwd: String;
fSessionSecret: String;
fSessionKey: RawByteString;
fAuthToken: String;
fAuthTokenTime: Integer;
fAuthTokenExpIn: Integer;
fHostOffset: Integer;
fAimSid: String;
fDevId: String;
fFetchBaseURL: String;
fRESTToken: String;
fRESTClientId: String;
LastFetchBaseURL: String;
FatalErrorCount: Integer;
BuzzedLastTime: TDateTime;
public
fECCKeys: record
Generated: Boolean;
PubEccKey: TECCPublicKey;
PrivKey: TECCPrivateKey;
end;
// listener: TicqNotify;
// MyInfo0: TICQcontact;
BirthdayFlag: Boolean;
CurXStatusVal: Byte;
CurXStatusStr: TXStatStr;
// used to pass valors to listeners
eventError : TicqError;
eventOldStatus: TICQstatus;
eventOldInvisible: Boolean;
eventUrgent: Boolean;
eventContact: TICQContact;
eventContacts: TRnQCList;
eventWP: TwpResult;
eventMsgA: RawByteString;
eventAddress: String;
eventNameA: AnsiString;
eventData: String;
eventBinData: TBytes;
// eventFilename: String;
eventInt: Integer; // multi-purpose
eventFlags: Dword;
// eventFileSize: LongWord;
eventTime: TDateTime; // in local time
eventMsgID: TMsgID;
eventStream: TMemoryStream;
eventWID: RawByteString;
eventEncoding: TEncoding;
// acceptKey: String;
// ConnectSSL: Boolean;
pPublicEmail,
ShowClientID,
UseCryptMsg,
UseEccCryptMsg,
SaveToken,
AvatarsSupport,
AvatarsAutoGet: Boolean;
MyAvatarHash: String;
HttpPoll: TSslHttpCli;
Timeout: TTimer;
PollStream: TStringStream;
ExecTime: Int64;
LastSearchPacks: TStickerPacks;
class function _GetProtoName: string;
class function _IsProtoUid(const UID: TUID): Boolean;
class function _IsValidUid(const UIN: TUID): Boolean;
class function _IsValidPhone(const Phone: TUID): Boolean;
class function _IsValidMail(const Mail: TUID): Boolean;
class function _CreateProto(const UID: TUID): TICQSession;
class function _RegisterUser(var pUID: TUID; var pPWD: String): Boolean;
class function _MaxPWDLen: Integer;
function GetICQContact(const uid: TUID): TICQContact; overload;
function GetICQContact(const uin: Integer): TICQContact; overload;
function GetContact(const UID: TUID): TICQContact; overload;
function GetContact(const UIN: Integer): TICQContact; overload;
function PwdEqual(const Pass: String): Boolean;
// constructor Create; override;
// destructor Destroy; override;
class constructor InitICQProto;
class destructor UnInitICQProto;
constructor Create(const id: TUID);
destructor Destroy; override; final;
procedure ResetPrefs;
procedure GetPrefs(var pp: TRnQPref);
procedure SetPrefs(pp: TRnQPref);
procedure Clear;
function RequestPasswordIfNeeded(DoConnect: Boolean = True): Boolean;
procedure Connect;
procedure Disconnect;
function SetStatusAndVis(st, vi: Byte; IsAuto: Boolean = False): Byte;
function SetStatus(st: Byte; IsAuto: Boolean = False): Byte;
procedure SetVisibility(vi: Byte);
procedure DoSetStatus(st: Byte; vi: Byte);
function GetPwd: String;
procedure SetPwd(const Value: String);
procedure SetCurXStatus(XStatus: Byte);
function MakeParams(const Method, BaseURL: String; const Params: TDictionary; Sign: Boolean = True; DoublePercent: Boolean = False): String;
procedure OpenICQURL(const URL: String);
function ClientLogin: Boolean;
function StartSession: Boolean;
function PingSession: Boolean;
procedure AfterSessionStarted;
procedure ResetSession;
procedure EndSession(EndToken: Boolean = False);
procedure PollError(const ExtraError: String = ''; Silent: Boolean = False);
procedure StartPolling;
procedure RestartPolling(Delay: Integer = 1);
procedure AbortPolling(Sender: TObject);
procedure PollURL(const URL: String);
procedure PollRequestDone(Sender: TObject; RqType: THttpRequest; ErrCode: Word);
procedure ProcessContactList(const CL: TJSONArray; Batch: Boolean = False);
function ProcessContact(const Buddy: TJSONObject; GroupToAddTo: Integer = -1; Batch: Boolean = False): TICQContact;
procedure ProcessNewStatus(var Cnt: TICQcontact; NewStatus: TICQstatus; XStatusStrChanged: Boolean = False; NoNotify: Boolean = False);
procedure ProcessUsersAndGroups(const JSON: TJSONObject);
procedure ProcessDialogState(const Dlg: TJSONObject);
procedure ProcessIMState(const Data: TJSONObject);
procedure ProcessTyping(const Data: TJSONObject);
procedure ProcessAddedYou(const Data: TJSONObject);
procedure ProcessPermitDeny(const Data: TJSONObject);
procedure InitWebRTC;
function RequiresLogin: Boolean;
function RESTAvailable: Boolean;
function GetStatus: Byte;
function GetVisibility: Byte;
function IsInvisible: Boolean;
function IsOnline: Boolean;
function IsOffline: Boolean;
function IsReady: Boolean;
function IsConnecting: Boolean;
function IsSSCL: Boolean;
function IsMobileAccount: Boolean;
function ImVisibleTo(c: TICQContact): Boolean;
function GetStatusName(ForTray: Boolean = False): String;
function GetStatusImg(ForTray: Boolean = False): TPicName;
function GetXStatus: Byte;
// manage contact lists
function ReadList(l: TLIST_TYPES): TRnQCList;
procedure AddToList(l: TLIST_TYPES; cl: TRnQCList); overload;
// procedure RemFromList(l: TLIST_TYPES; cl: TRnQCList); overload;
// manage contacts
procedure AddToList(l: TLIST_TYPES; cnt: TICQContact); overload;
procedure RemFromList(l: TLIST_TYPES; cnt: TICQContact); overload;
function IsInList(l: TLIST_TYPES; cnt: TICQContact): Boolean;
function AddContact(c: TICQContact; IsLocal: Boolean = false): Boolean;
function RemoveContact(c: TICQContact): Boolean;
procedure RemoveContactFromServer(c: TICQContact);
function SendUpdateGroup(const Name: String; ga: TGroupAction; const Old: String = ''): Boolean;
procedure InputChangedFor(c: TICQContact; InpIsEmpty: Boolean; TimeOut: Boolean = False);
function UpdateGroupOf(c: TICQContact; grp: Integer): Boolean;
procedure GetClientPicAndDesc4(cnt: TICQContact; var pPic: TPicName; var CliDesc: String);
function MaxCharsFor(const c: TICQContact; IsBin: Boolean = False): Integer;
function CompareStatusFor(Cnt1, Cnt2: TICQContact): SmallInt;
function CanAddCntOutOfGroup: Boolean;
function CreateNewGUID: String;
procedure AddMsg(Flags: Byte; Urgent: Boolean; const Msg: RawByteString);
// ICQ Only
// property WebAware: Boolean read P_WebAware write SetWebAware;
// property AuthNeeded: Boolean read P_AuthNeeded write SetAuthNeeded;
// property ShowInfo: Byte read P_ShowInfo write P_ShowInfo;
property Pwd: String read GetPwd write SetPwd;
property Visibility: TVisibility read fVisibility write fVisibility;
property CurXStatus: Byte read CurXStatusVal write SetCurXStatus;
procedure GetServerHistory(const UID: TUID; FromMsgId: TMsgID; const PatchVer: String);
private
function GetLocalIP: Integer;
//procedure sendAddTempContact(const buinlist: RawByteString); overload; // 030F
public // ICQ Only
procedure SendAddContact(c: TICQContact);
procedure SendRemoveContact(c: TICQContact);
procedure AddContactToCL(var c: TICQContact);
procedure AddContactsToCL(cl: TRnQCList);
procedure SendWPsearch(wp: TwpSearch; idx: Integer); deprecated;
procedure SendWPsearch2(wp: TwpSearch; idx: Integer; IsWP: Boolean = True); deprecated;
procedure SendTyping(c: TICQContact; NotifType: Word);
procedure RemoveMeFromHisCL(const uin: TUID); deprecated;
procedure SendSaveMyInfo(c: TICQContact);
procedure SendContacts(Cnt: TICQContact; flags: DWord; cl: TRnQCList); deprecated;
function GetMyCaps: String;
procedure GetProfile(const UID: TUID);
procedure GetContactInfo(const UID: TUID; const IncludeField: String);
procedure GetContactAttrs(const UID: TUID);
procedure SendContactAttrs(c: TICQContact);
procedure GetCL;
procedure FindContact;
procedure ValidateSid;
procedure GetExpressions;
procedure GetAllCaps;
procedure Test;
function SendSessionRequest(IsPOST: Boolean; const BaseURL: String; Query: String;
const Header: AnsiString = ''; const ErrMsg: String = ''; const ErrProc: TErrorProc = nil): Boolean; overload;
function SendSessionRequest(IsPOST: Boolean; const BaseURL: String; Query: String; Ret: TReturnData;
var JSON: TJSONObject; const Header: AnsiString = ''; const ErrMsg: String = ''; const ErrProc: TErrorProc = nil): Boolean; overload;
function SendRequest(IsPOST: Boolean; const BaseURL, Query: String;
const Header: AnsiString = ''; const ErrMsg: String = ''; const ErrProc: TErrorProc = nil): Boolean; overload;
function SendRequest(IsPOST: Boolean; const BaseURL, Query: String; Ret: TReturnData;
var JSON: TJSONObject; const Header: AnsiString = ''; const ErrMsg: String = ''; const ErrProc: TErrorProc = nil): Boolean; overload;
procedure SendRequestAsync(IsPOST: Boolean; const BaseURL, Query: String; const Header: AnsiString = ''; HandlerProc: THandlerProc = nil);
function SendPresenceState: Boolean;
procedure SendStatusStr(const st: Byte; const StText: String = '');
protected
// send packets
// procedure sendPermissions;
// TODO:
// procedure sendRemoveVisible(cl: TRnQCList); overload;
// procedure sendRemoveInvisible(cl: TRnQCList); overload;
// procedure sendAddInvisible(cl: TRnQCList); overload;
// procedure sendAddVisible(cl: TRnQCList); overload;
private
procedure GetPermitDeny;
procedure SetPermitDenyMode(const Mode: String);
procedure AddToBlock(const c: String);
procedure RemFromBlock(const c: String);
function Add2Ignore(c: TICQContact): Boolean; //overload;
function RemFromIgnore(c: TICQContact): Boolean;
procedure ParseMsgError(const snac: RawByteString; ref: Integer); deprecated;
procedure parse1503(const snac: RawByteString; ref: Integer; flags: Word); deprecated;
procedure GoneOffline; // called going offline
procedure OnProxyError(Sender: TObject; Error: Integer; const Msg: String);
function MyUINle: RawByteString;
public // All
function CreateDataPayload(Caps: TArray; const Data: TBytes = nil; Compressed: Integer = -1; CRC: Cardinal = 0; Len: Integer = 0): String;
procedure SendMsg(Cnt: TICQContact; Kind: Integer; Flags: DWord; const HistMsg: String; const Msg: String);
function SendBuzz(Cnt: TICQContact): Boolean;
procedure SetListener(l: TProtoNotify);
procedure SetMuted(c: TICQcontact; Mute: Boolean);
procedure Authorize(c: TICQContact; Grant: Boolean = True); deprecated;
procedure AuthRequest(c: TICQContact; Reason: String); deprecated;
function AddRef(k: TRefKind; const uin: TUID): Integer;
function IsMyAcc(c: TICQContact): Boolean;
function GetMyInfo: TICQContact;
function GetStatuses: TStatusArray;
function GetVisibilities: TStatusArray;
function GetStatusMenu: TStatusMenu;
function GetVisMenu: TStatusMenu;
function GetStatusDisable: TOnStatusDisable;
procedure ApplyBalloon;
property Statuses: TStatusArray read GetStatuses;
property MyInfo: TICQContact read getMyInfo;
procedure SendSMS(const Dest, Msg: String; Ack: Boolean);
procedure SendSMS2(const Dest, Msg: String; Ack: Boolean);
procedure GetStoreStickerPacks;
procedure SearchStoreStickerPacks(const Query: String);
procedure SearchStoreStickerPack(const StoreId: String);
function GetStoreStickerPack(const Id: String; IsStoreId: Boolean): TStickerPack;
procedure BuyStickerPack(const PackId: String);
procedure RemoveStickerPack(const PackId: String);
end; // TICQSession
TICQProtoClass = class of TICQSession;
TICQAsync = class(TSslHttpCli)
public
Contact: TICQContact;
end;
var
// sendInterests,
ShowInvisSts,
AvatarsNotDnlddInform: Boolean;
ExtClientCaps: RawByteString;
AddExtCliCaps: Boolean;
SendBalloonOn: Integer;
SendBalloonOnDate: TDateTime;
ICQStatuses, ICQVis: TStatusArray;
StatMenu, ICQVisMenu: TStatusMenu;
ReqId: Integer = 1;
AttachedLoginPhone: String;
implementation
uses
Controls, DateUtils,
{$IFDEF UNICODE}
AnsiStrings,
{$ENDIF UNICODE}
RnQZip, SynCrypto,
RnQLangs, RDUtils, RnQCrypt, Base64,
globalLib, groupsLib, utilLib, outboxLib, roasterLib,
ICQClients, history, events, mainDlg,
RnQStrings, RQCodes, RnQDialogs, Protocol_ICQ;
const
AESBLKSIZE = SizeOf(TAESBlock);
function CheckResponseData(var JSON: TJSONObject; out ReqID: String): TPair;
var
Tmp: TJSONValue;
begin
Result.Key := 0;
Result.Value := '';
if Assigned(JSON) then
begin
JSON.GetValueSafe('requestId', ReqID);
Tmp := JSON.GetValue('response');
if Assigned(Tmp) and (Tmp is TJSONObject) then
begin
Tmp.Owned := False;
JSON.Free;
JSON := Tmp as TJSONObject;
JSON.GetValueSafe('statusCode', Result.Key);
JSON.GetValueSafe('statusText', Result.Value);
if Result.Key = Integer(EAC_OK) then
begin
Tmp := JSON.GetValue('data');
if Assigned(Tmp) and (Tmp is TJSONObject) then
begin
Tmp.Owned := False;
JSON.Free;
JSON := Tmp as TJSONObject;
end;
end;
end;
end;
end;
function CheckSimpleData(var JSON: TJSONObject; StatusOnly: Boolean = False): Boolean;
var
Tmp: TJSONValue;
begin
Result := True;
Tmp := JSON.GetValue('status');
if not Assigned(Tmp) or not (Tmp is TJSONNumber) or not (TJSONNumber(Tmp).AsInt = Integer(EAC_OK)) then
Exit(False);
if not StatusOnly then
begin
Tmp := JSON.GetValue('data');
if not Assigned(Tmp) or not (Tmp is TJSONObject) then
Exit(False);
JSON := TJSONObject(Tmp);
end;
end;
function TICQSession.ValidICQ(const UID: TUID): Boolean;
begin
Result := (Length(UID) > 0) and (ValidUID(UID) or ValidPhone(UID) or ValidMail(UID));
end;
function TICQSession.ValidUID(const UID: TUID): Boolean;
begin
Result := (Length(UID) > 0) and Self._IsValidUid(UID);
end;
function TICQSession.ValidPhone(const Phone: TUID): Boolean;
begin
Result := (Length(Phone) > 0) and Self._IsValidPhone(Phone);
end;
function TICQSession.ValidMail(const Mail: TUID): Boolean;
begin
Result := (Length(Mail) > 0) and Self._IsValidMail(mail);
end;
function TICQSession.ContactExists(const UID: TUID): Boolean;
begin
Result := contactsDB.exists(UID);
end;
function TICQSession.GetShowStr: String;
var
mi: TICQContact;
begin
mi := GetMyInfo;
if Assigned(mi) then
Result := GetMyInfo.Displayed
else
Result := MyAccNum;
Result := '(' + _GetProtoName + ') ' + Result;
end;
class function TICQSession._RegisterUser(var pUID: TUID; var pPWD: String): Boolean;
begin
Result := False;
openURL('https://icq.com/join/');
end;
class function TICQSession._CreateProto(const UID: TUID): TICQSession;
begin
Result := TICQSession.Create(uid);
end;
constructor TICQSession.Create(const id: TUID);
begin
ContactsDB := TRnQCList.Create;
Phase := null_;
Listener := nil;
MyAccount := IfThen(id = '', '', TICQContact.TrimUID(id));
// if (MyAccount <> '') and (Pos(AnsiChar('@'), MyAccount) > 1) then
// AttachedLoginEmail := MyAccount
// else
// AttachedLoginEmail := '';
AttachedLoginPhone := '';
fPwd := '';
fHostOffset := 0;
fAuthTokenExpIn := 0; // Never
fDevId := ICQ_DEV_ID;
SNACref := 1;
CurStatus := SC_OFFLINE;
CurXStatus := 0;
StartingStatus := SC_ONLINE;
Visibility := VI_normal;
Cookie := '';
// ShowInfo := 2;
// WebAware := True;
fRoster := TRnQCList.Create;
SpamList := TRnQCList.Create;
SavingMyInfo.Running := False;
fECCKeys.Generated := ecc_make_key(fECCKeys.PubEccKey, fECCKeys.PrivKey);
PollStream := TStringStream.Create('', TEncoding.UTF8);
HttpPoll := TSslHttpCli.Create(nil);
HttpPoll.FollowRelocation := True;
HttpPoll.OnRequestDone := PollRequestDone;
HttpPoll.RcvdStream := PollStream;
HttpPoll.Connection := 'keep-alive';
Timeout := TTimer.Create(nil);
Timeout.OnTimer := AbortPolling;
Timeout.Interval := 58000;
Timeout.Enabled := False;
end; // Create
procedure TICQSession.ResetPrefs;
var
i : Integer;
begin
pwd := '';
SupportTypingNotif := True;
IsSendTypingNotif := True;
CurXStatus := 0;
// AuthNeeded := True;
pPublicEmail := False;
ShowClientID := True;
EnableRecentlyOffline := False;
RecentlyOfflineDelay := 15;
AddExtCliCaps := False;
ExtClientCaps := '';
TypingInterval := 5;
UseCryptMsg := True;
UseEccCryptMsg := True;
AvatarsSupport := True;
AvatarsAutoGet := True;
AvatarsNotDnlddInform := False;
MyAvatarHash := '';
SaveToken := True;
ShowInvisSts := True;
SendBalloonOn := BALLOON_NEVER;
OnStatusDisable[byte(SC_OCCUPIED)].Blinking := True;
OnStatusDisable[byte(SC_OCCUPIED)].Sounds := True;
for i := Low(XStatusArray) to High(XStatusArray) do
begin
ExtStsStrings[i].Cap := GetTranslation(XStatusArray[i].Caption);
ExtStsStrings[i].Desc := '';
end;
end;
procedure TICQSession.GetPrefs(var pp: TRnQPref);
var
i: Integer;
s: String;
sR: RawByteString;
begin
if (MyAccount <> '') and (Pos(AnsiChar('@'), MyAccount) <= 0) then
pp.addPrefStr('oscar-uid', MyAccount);
pp.addPrefBool('add-client-caps', AddExtCliCaps);
pp.addPrefStr('add-client-caps-str', String2Hex(ExtClientCaps));
pp.addPrefInt('send-balloon-on', SendBalloonOn);
pp.addPrefDate('send-balloon-on-date', SendBalloonOnDate);
try
pp.addPrefBool('public-email', pPublicEmail);
pp.addPrefBool('save-token', SaveToken);
except
// MsgDlg('Какая-то глупая ошибка :(((', mtError);
end;
pp.addPrefInt('typing-notify-interval', TypingInterval);
pp.addPrefBool('use-crypt-msg', UseCryptMsg);
pp.addPrefBool('use-ecc-crypt-msg', UseEccCryptMsg);
pp.addPrefBool('avatars-flag', AvatarsSupport);
pp.addPrefBool('avatars-auto-load-flag', AvatarsAutoGet);
pp.addPrefBool('avatars-not-downloaded-inform-flag', AvatarsNotDnlddInform);
pp.addPrefStr('avatar-my', MyAvatarHash);
pp.addPrefBool('recently-offline-enable', EnableRecentlyOffline);
pp.addPrefInt('recently-offline-delay', RecentlyOfflineDelay);
pp.addPrefBool('show-invis-status', ShowInvisSts);
for i in Self.GetStatusMenu do
if i <> Byte(SC_OFFLINE) then
begin
s := Status2Img[i] + '-disable-';
pp.addPrefBool(s + 'blinking', OnStatusDisable[i].Blinking);
pp.addPrefBool(s + 'tips', OnStatusDisable[i].Tips);
pp.addPrefBool(s + 'sounds', OnStatusDisable[i].Sounds);
pp.addPrefBool(s + 'openchat', OnStatusDisable[i].OpenChat);
end;
// pp.addPrefBool('auth-needed', Self.AuthNeeded);
// pp.addPrefBool('webaware', Self.WebAware);
pp.addPrefBool('show-client-id', ShowClientID);
pp.addPrefInt('xstatus', Self.CurXStatus);
// pp.addPrefInt('icq-showinfo', Self.ShowInfo);
if not (RnQstartingStatus in [Low(Status2Img)..High(Status2Img)]) then
pp.addPrefStr('starting-status', 'last_used')
else
pp.addPrefStr('starting-status', Status2Img[RnQstartingStatus]);
pp.addPrefStr('starting-visibility', Visib2Str[TVisibility(RnQStartingVisibility)]);
pp.addPrefStr('last-set-status', Status2Img[LastStatusUserSet]);
pp.addPrefBool('typing-notify-flag', SupportTypingNotif);
pp.addPrefBool('show-typing', isSendTypingNotif);
if not dontSavePwd //and not locked
then
pp.addPrefBlob64('crypted-password64', passCrypt(UTF(pwd)))
else
pp.DeletePref('crypted-password64');
pp.DeletePref('crypted-password');
if not dontSavePwd //and not locked
then
begin
sR := UTF(fPwd);
// pp.addPrefBlob('crypted-password', passCrypt(sR));
pp.addPrefBlob64('crypted-password64', passCrypt(sR))
end else
pp.DeletePref('crypted-password64');
pp.DeletePref('crypted-password');
if SaveToken then
begin
pp.addPrefBlob64('crypted-session-key64', passCrypt(fSessionKey));
pp.addPrefBlob64('crypted-auth-token64', passCrypt(fAuthToken));
pp.addPrefInt('auth-token-time', fAuthTokenTime);
pp.addPrefInt('auth-token-expiresin', fAuthTokenExpIn);
end;
pp.addPrefInt('session-last-host-offset', fHostOffset);
end;
procedure TICQSession.SetPrefs(pp: TRnQPref);
var
i: Integer;
sU, sU2: String;
st: Byte;
l: RawByteString;
begin
if pp.prefExists('crypted-password64') then
pwd := UnUTF(passDecrypt(pp.getPrefBlob64Def('crypted-password64')))
else
pwd := passDecrypt(pp.getPrefBlobDef('crypted-password'));
pp.getPrefBool('typing-notify-flag', SupportTypingNotif);
pp.getPrefBool('show-typing', isSendTypingNotif);
pp.getPrefStr('oscar-uid', sU);
if sU > '' then
MyAccount := sU;
pp.getPrefBool('public-email', pPublicEmail);
pp.getPrefBool('add-client-caps', AddExtCliCaps);
ExtClientCaps := Hex2String(pp.getPrefBlobDef('add-client-caps-str'));
// authneeded := pp.getPrefBoolDef('auth-needed', AuthNeeded);
// webaware := pp.getPrefBoolDef('webaware', WebAware);
// showInfo := pp.getPrefIntDef('icq-showinfo', ShowInfo);
i := pp.getPrefIntDef('xstatus');
if i >= 0 then
if (i in [Low(XStatusArray)..High(XStatusArray)]) then
CurXStatus := i
else
CurXStatus := 0;
pp.getPrefInt('send-balloon-on', SendBalloonOn);
pp.getPrefDate('send-balloon-on-date', SendBalloonOnDate);
for st := Byte(Low(tICQstatus)) to Byte(High(tICQstatus)) do
with OnStatusDisable[Byte(st)] do
begin
sU2 := status2Img[st] + '-disable-';
sU := sU2 + 'blinking';
pp.getPrefBool(sU, Blinking);
sU := sU2 + 'tips';
pp.getPrefBool(sU, Tips);
sU := sU2 + 'sounds';
pp.getPrefBool(sU, Sounds);
sU := sU2 +'openchat';
pp.getPrefBool(sU, OpenChat);
end;
pp.getPrefBool('recently-offline-enable', EnableRecentlyOffline);
pp.getPrefInt('recently-offline-delay', RecentlyOfflineDelay);
pp.getPrefBool('save-token', SaveToken);
if pp.prefExists('crypted-password64') then
l := passDecrypt(pp.getPrefBlob64Def('crypted-password64'))
else
l := passDecrypt(pp.getPrefBlobDef('crypted-password'));
pwd:= UnUTF(l);
l := '';
if SaveToken then
begin
fSessionKey := passDecrypt(pp.getPrefBlob64Def('crypted-session-key64'));
fAuthToken := passDecrypt(pp.getPrefBlob64Def('crypted-auth-token64'));
pp.getPrefInt('auth-token-time', fAuthTokenTime);
pp.getPrefInt('auth-token-expiresin', fAuthTokenExpIn);
end;
pp.getPrefInt('session-last-host-offset', fHostOffset);
pp.getPrefInt('typing-notify-interval', TypingInterval);
pp.getPrefBool('use-crypt-msg', UseCryptMsg);
pp.getPrefBool('use-ecc-crypt-msg', UseEccCryptMsg);
pp.getPrefBool('avatars-flag', AvatarsSupport);
pp.getPrefBool('avatars-auto-load-flag', AvatarsAutoGet);
pp.getPrefBool('avatars-not-downloaded-inform-flag', AvatarsNotDnlddInform);
pp.getPrefBool('show-invis-status', ShowInvisSts);
pp.getPrefBool('show-client-id', ShowClientID);
l := pp.getPrefBlobDef('starting-status');
if l='last_used' then
RnQstartingStatus := -1
else
RnQstartingStatus := Str2Status(l);
l := pp.getPrefBlobDef('starting-visibility');
RnQStartingVisibility := 0;//Byte(Str2Visibility(l));
l := pp.getPrefBlobDef('last-set-status');
LastStatusUserSet := Str2Status(l);
Visibility := TVisibility(RnQStartingVisibility);
MyAvatarHash := pp.getPrefBlobDef('avatar-my');
if ContactsDB.IdxOf(MyAccount) >= 0 then
with GetMyInfo do
IconID := MyAvatarHash;
ApplyBalloon();
end;
procedure TICQSession.Clear;
begin
ReadList(LT_ROSTER).Clear;
ReadList(LT_SPAM).Clear;
FreeAndNil(eventContacts);
eventContact := nil;
end;
destructor TICQSession.Destroy;
begin
fRoster.Free;
SpamList.Free;
FreeAndNil(PollStream);
Timeout.Enabled := False;
FreeAndNil(Timeout);
FreeAndNil(HttpPoll);
FreeAndNil(ContactsDB);
end; // Destroy
function TICQSession.MyUINle: RawByteString;
begin
Result := dword_LEasStr(StrToIntDef(MyAccount, 0))
end;
function TICQSession.GetMyInfo: TICQContact;
begin
Result := ContactsDB.Add(MyAccount);
end;
function TICQSession.IsMyAcc(c: TICQContact): Boolean;
begin
Result := Assigned(c) and c.equals(MyAccount)
end;
function TICQSession.CanAddCntOutOfGroup : Boolean;
begin
Result := False;
end;
function TICQSession.PwdEqual(const Pass: String) : Boolean;
begin
Result := (not (Pass = '') and (Pass = fPwd));
end;
function TICQSession.GetPwd: String;
begin
Result := fPwd;
end;
function TICQSession.RequiresLogin: Boolean;
begin
Result := (fSessionKey = '') or (fAuthToken = '') or (fAuthTokenTime = 0) or
(not (fAuthTokenExpIn = 0) and (fAuthTokenTime + fAuthTokenExpIn < DateTimeToUnix(Now, False)));
end;
function TICQSession.RESTAvailable: Boolean;
begin
Result := not (fAimSid = '') and not (fRESTToken = '') and not (fRESTClientId = '');
end;
procedure TICQSession.SetPwd(const Value: String);
begin
if (Length(Value) <= MaxPwdLength) then
if not (Value = fPwd) then
fPwd := Value;
end; // SetPwd
procedure TICQSession.SetCurXStatus(XStatus: Byte);
begin
CurXStatusVal := XStatus;
if Assigned(RnQmain.CLBox) then
RnQmain.CLBox.UpdateAdditionalImage;
end;
procedure TICQSession.NotifyListeners(ev: TICQEvent);
begin
if Assigned(Listener) then
Listener(Self, Integer(ev));
end; // NotifyListeners
function TICQSession.IsOffline: Boolean;
begin
Result := Phase = null_
end;
function TICQSession.IsOnline: Boolean;
begin
Result := Phase = online_
end;
function TICQSession.IsConnecting: Boolean;
begin
Result := (Phase <> online_) and (Phase <> null_)
end;
procedure TICQSession.GoneOffline;
begin
if Phase = null_ then
Exit;
Phase := null_;
CurStatus := SC_OFFLINE;
fRoster.ForEach(procedure(cnt: TICQContact)
begin
cnt.OfflineClear;
cnt.Status := SC_UNK;
end);
NotifyListeners(IE_offline);
end; // GoneOffline
procedure TICQSession.Disconnect;
begin
CleanDisconnect := True;
if Phase = online_ then
EndSession(not SaveToken)
else
Phase := null_;
end;
function TICQSession.IsReady: Boolean;
begin
Result := Phase in [settingup_, online_]
end;
function TICQSession.IsSSCL: Boolean;
begin
Result := True;
end;
function TICQSession.IsMobileAccount: Boolean;
begin
Result := String(MyAccount).StartsWith('+');;
end;
function TICQSession.SendSessionRequest(IsPOST: Boolean; const BaseURL: String; Query: String; const Header: AnsiString = '';
const ErrMsg: String = ''; const ErrProc: TErrorProc = nil): Boolean;
begin
if fAimSid = '' then
Exit(False);
Query := 'f=json&aimsid=' + fAimSid + '&r=' + CreateNewGUID + Query;
Result := SendRequest(IsPOST, BaseURL, Query, Header, ErrMsg, ErrProc);
end;
function TICQSession.SendSessionRequest(IsPOST: Boolean; const BaseURL: String; Query: String; Ret: TReturnData;
var JSON: TJSONObject; const Header: AnsiString = ''; const ErrMsg: String = ''; const ErrProc: TErrorProc = nil): Boolean;
begin
if fAimSid = '' then
Exit(False);
Query := 'f=json&aimsid=' + fAimSid + '&r=' + CreateNewGUID + Query;
Result := SendRequest(IsPOST, BaseURL, Query, Ret, JSON, Header, ErrMsg, ErrProc);
end;
function TICQSession.SendRequest(IsPOST: Boolean; const BaseURL, Query: String; const Header: AnsiString = '';
const ErrMsg: String = ''; const ErrProc: TErrorProc = nil): Boolean;
var
JSON: TJSONObject;
begin
JSON := nil;
Result := SendRequest(IsPOST, BaseURL, Query, RT_None, JSON, Header, ErrMsg, ErrProc);
end;
function TICQSession.SendRequest(IsPOST: Boolean; const BaseURL, Query: String; Ret: TReturnData; var JSON: TJSONObject;
const Header: AnsiString = ''; const ErrMsg: String = ''; const ErrProc: TErrorProc = nil): Boolean;
var
Prefix, RespStr, SReqID: String;
Resp: TPair;
begin
if not Running then
Exit;
Result := False;
Prefix := IfThen(IsPOST, '[POST] ', '[GET] ');
eventNameA := Prefix + Header;
eventData := BaseURL + '?' + Query;
NotifyListeners(IE_serverGot);
if IsPOST then
LoadFromURLAsString(BaseURL, RespStr, Query)
else
LoadFromURLAsString(BaseURL + '?' + Query, RespStr);
if not (Trim(RespStr) = '') then
begin
eventNameA := Prefix + Header;
eventData := RespStr;
NotifyListeners(IE_serverSent);
end;
if not ParseJSON(RespStr, JSON) then
Exit;
try
Resp := CheckResponseData(JSON, SReqID);
if Resp.Key = Integer(EAC_OK) then
Result := True
else if Assigned(ErrProc) then
ErrProc(Resp)
else if not (ErrMsg = '') then
MsgDlg(Format(GetTranslation(ErrMsg) + #13#10 + GetTranslation('Server returned error:') + #13#10 + '%s', [Resp.Value]), False, mtError)
else
begin
// Too many useless errors :)
//eventInt := Resp.Key;
//eventMsgA := Resp.Value;
//eventError := EC_other;
//NotifyListeners(IE_error);
end;
finally
if Ret = RT_None then
FreeAndNil(JSON);
end;
end;
procedure TICQSession.SendRequestAsync(IsPOST: Boolean; const BaseURL, Query: String; const Header: AnsiString = ''; HandlerProc: THandlerProc = nil);
var
Prefix: String;
begin
Prefix := IfThen(IsPOST, '[POST] ', '[GET] ');
eventNameA := Prefix + Header;
eventData := BaseURL + '?' + Query;
NotifyListeners(IE_serverGot);
TTask.Create(procedure
var
RespStr: String;
begin
if IsPOST then
LoadFromURLAsString(BaseURL, RespStr, Query)
else
LoadFromURLAsString(BaseURL + '?' + Query, RespStr);
TThread.Synchronize(nil, procedure
begin
if not Running then
Exit;
if Assigned(HandlerProc) then
HandlerProc(RespStr);
eventNameA := Prefix + Header;
eventData := RespStr;
NotifyListeners(IE_serverSent);
end);
end).Start;
end;
function TICQSession.SendPresenceState: Boolean;
var
Query: UTF8String;
BaseURL: String;
begin
Result := False;
BaseURL := WIM_HOST + 'presence/setState';
Query := '&view=' + IfThen(Visibility = VI_invisible, 'invisible', Status2Srv[Byte(curStatus)]) +
'&invisible=' + IfThen(Visibility = VI_invisible, '1', '0') +
'&assertCaps=' + ParamEncode(GetMyCaps);
//IfThen(curStatus = SC_AWAY, '&away=Seeya', ''); // Not really useful, only you receive your awayMsg :)
if SendSessionRequest(False, BaseURL, Query, 'Set status and visibility', 'Failed to set status') then
begin
// Not needed, same info as in myInfo in fetched event
//ProcessContaсt(json.GetValue('myInfo') as TJSONObject)
Result := True;
end;
end; // SendWebStatusAndVis
procedure TICQSession.SendStatusStr(const st: Byte; const StText: String = '');
var
Query: UTF8String;
BaseURL, TmpStr: String;
begin
eventContact := nil;
if not (st in [Low(XStatusArray)..High(XStatusArray)]) then
Exit;
// XStatus is just for local display
if StText <> ExtStsStrings[st].Desc then
ExtStsStrings[st].Desc := StText;
curXStatus := st;
eventInt := st;
curXStatusStr.Cap := ExtStsStrings[st].Cap;
curXStatusStr.Desc := ExtStsStrings[st].Desc;
eventNameA := UTF(ExtStsStrings[st].Cap);
eventMsgA := UTF(ExtStsStrings[st].Desc);
NotifyListeners(IE_sendingXStatus);
TmpStr := UnUTF(eventNameA);
if curXStatusStr.Cap <> TmpStr then
curXStatusStr.Cap := TmpStr;
TmpStr := UnUTF(eventMsgA);
if curXStatusStr.Desc <> TmpStr then
curXStatusStr.Desc := TmpStr;
SaveCfgDelayed := True;
if IsReady then
if not (Visibility = VI_invisible) then // Do not change msg if invisible, it generates "offline" presence event
begin
BaseURL := WIM_HOST + 'presence/setStatus';
Query := '&statusMsg=' + ParamEncode(curXStatusStr.Desc);
if SendSessionRequest(True, BaseURL, Query, 'Set status string', 'Failed to set status message') then
begin
// Not needed, same info as in myInfo in fetched event
//ProcessContaсt(json.GetValue('myInfo') as TJSONObject)
end;
end;
end; // SendWebStatusStr
procedure CalcKey(IsEcc: Boolean; const EccKey, u1, u2: RawByteString; l1, l2: Int64; var Key: TSHA256Digest);
var
sr: RawByteString;
begin
if isEcc then
PBKDF2_HMAC_SHA256(EccKey, not2Translate[2] + AIM_MD5_STRING + IntToHex(l1, 2) + u1 + IntToHex(l2, 2) + u2, 3, Key)
else
begin
sr := MD5Pass(IntToHex(l1, 2) + not2Translate[2] + u1 + AIM_MD5_STRING);
CopyMemory(@Key[0], @sr[1], SizeOf(TMD5Digest));
sr := MD5Pass(IntToHex(l2, 2) + not2Translate[2] + u2 + AIM_MD5_STRING);
CopyMemory(@Key[16], @sr[1], SizeOf(TMD5Digest));
end;
end;
(*
function TICQSession.SendMsgOld(cnt: TRnQContact; var flags: dword; const msg: String; var requiredACK: Boolean): Integer; // $0406
var
c: TICQcontact;
status: AnsiString;
sutf: RawByteString;
Msg2: String;
Msg2Send: RawByteString;
Msg2SendC: RawByteString;
key: array [0..31] of byte;
ctx: TAESECB;
CrptMsg: RawByteString;
snac: RawByteString;
I, len, len2: Integer;
crc: Cardinal;
CompressType: Word;
flagChar, priorityChar: AnsiChar;
isUnicode: Boolean;
lShouldEncr: Boolean;
isBin: boolean;
begin
Result := -1;
if not IsReady then
Exit;
c:= cnt;
isBin := (Pos(RnQImageTag, msg) > 0) or ((Pos(RnQImageExTag, msg) > 0)) or (IF_Bin and flags > 0);
if isBin then
flags := flags or IF_Bin;
if not UseAdvMsg then
flags := flags or IF_Simple;
if not imVisibleTo(c) then
if addTempVisMsg then
addTemporaryVisible(c);
if imVisibleTo(c) then
status := word_LEasStr(getFullStatusCode)
else
status := #00#00;
flagChar := #0;
if IF_multiple and flags > 0 then
flagChar := #$80;
priorityChar := #1;
if IF_urgent and flags > 0 then
priorityChar := #2;
if IF_noblink and flags > 0 then
priorityChar := #4;
if c.SendTransl and not isBin then
Msg2 := Translit(msg)
else
Msg2 := msg;
sutf := '';
lShouldEncr := (UseCryptMsg and (c.Crypt.supportCryptMsg or (fECCKeys.generated and UseEccCryptMsg and c.crypt.supportEcc)))
and (not useMsgType2For(c) or not isBin);
if (useMsgType2For(c) or lShouldEncr) and not (IF_Simple and flags > 0) then
begin
requiredACK := True;
if ((SendingUTF and ((CAPS_sm_UTF8 in c.capabilitiesSm) or c.isAIM or (c.status = SC_OFFLINE)))
or (lShouldEncr and fECCKeys.generated and UseEccCryptMsg and c.crypt.supportEcc)) and not isBin then
begin
// sutf := Length_DLE(GUIDToString(msgUtf));
sutf := Length_DLE(msgUTFstr);
Msg2Send := UTF(Msg2);
end
else
begin
// sutf := '';
Msg2Send := AnsiString(msg2);
end;
if lShouldEncr then
begin
len := Length(Msg2Send);
crc := (ZipCrc32($FFFFFFFF, @Msg2Send[1], Len) XOR $FFFFFFFF);
CompressType := 0;
Msg2SendC := ZCompressStr(Msg2Send);
{
buf := TMemoryStream.create;
destBuf := TMemoryStream.create;
buf.Write(Msg2Send[1], Len);
buf.Position := 0;
ZlibCompressStreamEx(buf, destBuf, clMax, zsZLib, false);
buf.free;
// Msg2Send := ZCompressStrEx(msg, clMax);
// if Length(Msg2Send) < Len then
i := destBuf.Size;
if i+4 < Len then
begin
setLength(Msg2Send, i+4);
move(i, Msg2Send[1], 4);
destBuf.Position := 0;
destBuf.Read(Msg2Send[5], i);
CompressType := 1;
end;
destBuf.free;
}
i := Length(Msg2SendC);
if i + 4 < Len then
begin
Msg2Send := int2str(i + 4) + Msg2SendC;
CompressType := 1;
end;
CalcKey(fECCKeys.generated and UseEccCryptMsg and c.crypt.supportEcc, c.crypt.EccMsgKey, MyAccount, c.UID2cmp, SNACref, len, TSHA256Digest(key));
ctx := TAESECB.Create(key[0], 256);
// len2 := length(msg);
i := len mod AESBLKSIZE;
if (i>0) then
begin
len2 := len + AESBLKSIZE - i;
SetLength(Msg2Send, len2);
FillChar(Msg2Send[len+1], AESBLKSIZE - i, 0);
end else
len2 := len;
SetLength(CrptMsg, len2);
ctx.Encrypt(@Msg2Send[1], @CrptMsg[1], len2);
ctx.Free;
Msg2Send := Base64EncodeString(CrptMsg);
snac := AnsiChar(MTYPE_PLAIN) + flagChar
+ status
+ priorityChar+#0
+ WNTS(Msg2Send)
+ dword_LEasStr(len)
+ dword_LEasStr(crc)
+ word_LEasStr(CompressType)
+ dword_LEasStr(RDUtils.IfThen(isBin, Integer(2)))+dword_LEasStr($FFFFFF)
// + sutf
;
if fECCKeys.generated and UseEccCryptMsg and c.crypt.supportEcc then
sendEccMSGsnac(c, snac)
else
sendCryptMSGsnac(c.UID2cmp, snac);
flags := flags or IF_Encrypt;
end else
if UseCryptMsg and (CAPS_big_QIP_Secure in c.capabilitiesBig) and (c.Crypt.qippwd > 0) and not isBin then
begin // QIP crypt message
Msg2Send := qip_msg_crypt(msg2send, c.Crypt.qippwd);
// sutf := Length_DLE(GUIDToString(msgQIPpass));
sutf := Length_DLE(msgQIPpassStr);
sendMSGsnac(c.UID, AnsiChar(MTYPE_PLAIN)+flagChar
+status
+priorityChar+#0
+WNTS(Msg2Send)
+dword_LEasStr(0)+dword_LEasStr($FFFFFF)
+sutf
);
flags := flags or IF_Encrypt;
end else
sendMSGsnac(c.UID, AnsiChar(MTYPE_PLAIN)+flagChar
+ status
+ priorityChar +#0
+ WNTS(Msg2Send)
+ dword_LEasStr(0) + dword_LEasStr($FFFFFF)
+ sutf
);
end
else
begin // Simple MSG
// requiredACK:=FALSE;
requiredACK := True;
if SendingUTF
// or (c.status = SC_OFFLINE)
// and ((CAPS_sm_UTF8 in c.capabilitiesSm)or c.isAIM) and (c.isOnline)
and not isBin
then
// if SendingUTF then
begin
sutf := #$00#$02; // UNICODE - ISO 10646.USC-2 Unicode
isUnicode := True;
// msg := StrToUTF8(msg);
// if (c.status = SC_OFFLINE) and (IsSupportHTML) then
// msg := ''+
// msg + '';
// msg := StrToUnicode(msg);
Msg2Send := StrToUnicode(msg2);
end
else
begin
// sutf := z;
// sutf := #$00#$03; // LATIN_1 - ISO 8859-1
sutf := #$00#$00; // ASCII - ANSI ASCII -- ISO 646
isUnicode := False;
Msg2Send := RawByteString(msg2);
end;
flags := IF_Simple or flags;
sendSNAC(ICQ_MSG_FAMILY, CLI_META_MSG, qword_LEasStr(SNACref) + #0#1
+ c.buin
+ TLV(2,
TLV($0501, AnsiChar(#1) + RawByteString(IfThen(isUnicode, AnsiChar(#6)))) // Need for ICQ 2003b!!!!
// TLV($0501, #01)
// TLV($0501, #01#06)
+ TLV($0101, sutf + #$00#$00 + Msg2Send) ) // msg-data-1
// + TLV(5, myUINle+char(MSG_MSG)+flagChar+WNTS(msg) ) // msg-data-4
+ TLV(CLI_META_MSG_ACK, '')
+ TLV(CLI_META_STORE_IF_OFFLINE, '') // <-- if (args->flags & AIM_IMFLAGS_OFFLINE)
);
end;
Result := addRef(REF_msg, c.UID2Cmp);
// if requiredACK then
// acks.add(OE_msg, uin, 0, 'MSG').ID := result;
end; // sendMsgOld
*)
procedure TICQSession.SendMsg(Cnt: TICQContact; Kind: Integer; Flags: DWord; const HistMsg: String; const Msg: String);
var
Msg2, Msg2Enc, CrptMsg: TBytes;
ReadyMsg: String;
Key: TSHA256Digest;
Ctx: TAESECB;
i, Len, Len2, Compressed, Encrypted: Integer;
CRC: Cardinal;
ShouldEncrypt, IsBin, IsSticker: Boolean;
Params: TDictionary;
BaseURL, SReqID: String;
Handler: THandlerProc;
OEv: TOEvent;
begin
if not IsReady then
Exit;
ReqID := AddRef(REF_msg, Cnt.UID2Cmp);
OEv := Account.acks.Add(Kind, Cnt, Flags, 'MSG');
OEv.sID := CreateNewGUID;
OEv.ID := ReqID;
IsBin := (Pos(RnQImageTag, Msg) > 0) or ((Pos(RnQImageExTag, Msg) > 0)) or (Flags and IF_Bin > 0);
if IsBin then
Flags := Flags or IF_Bin;
// if not ImVisibleTo(c) then
// if AddTempVisMsg then
// AddTemporaryVisible(c); // TODO: New proto implementation
IsSticker := Flags and IF_sticker > 0;
if Cnt.SendTransl and not IsBin and not IsSticker then
ReadyMsg := Translit(Msg)
else
ReadyMsg := Msg;
Encrypted := 0;
ShouldEncrypt := (UseCryptMsg and (Cnt.Crypt.SupportCryptMsg or (fECCKeys.Generated and UseEccCryptMsg and Cnt.Crypt.SupportEcc))) and not IsBin;
if ShouldEncrypt and not IsSticker then
begin
Msg2 := TEncoding.UTF8.GetBytes(ReadyMsg);
Len := Length(Msg2);
CRC := ZipCrc32($FFFFFFFF, @Msg2[0], Len) XOR $FFFFFFFF;
Compressed := 0;
Msg2Enc := ZCompressBytes(Msg2);
if Assigned(Msg2Enc) then
if Length(Msg2Enc) < Len then
begin
Msg2 := Msg2Enc;
Compressed := 1;
end;
CalcKey(fECCKeys.Generated and UseEccCryptMsg and Cnt.Crypt.SupportEcc, Cnt.Crypt.EccMsgKey, MyAccount, Cnt.UID2cmp, 0, Len, Key);
i := Len mod AESBLKSIZE;
if (i > 0) then
begin
Len2 := Len + AESBLKSIZE - i;
SetLength(Msg2, Len2);
FillChar(Msg2[Len], AESBLKSIZE - i, 0);
end else
Len2 := Len;
SetLength(CrptMsg, Len2);
Ctx := TAESECB.Create(Key[0], 256);
Ctx.Encrypt(@Msg2[0], @CrptMsg[0], Len2);
Ctx.Free;
SetLength(Msg2, 0);
Base64EncodeBytes(CrptMsg, Msg2);
if fECCKeys.Generated and UseEccCryptMsg and Cnt.Crypt.SupportEcc then
Encrypted := 2
else
Encrypted := 1;
flags := flags or IF_Encrypt;
if Encrypted = 2 then
ReadyMsg := CreateDataPayload([
String2Hex('RDEC0' + Copy(Cnt.Crypt.EccPubKey, 1, 11)),
String2Hex('RDEC1' + Copy(Cnt.Crypt.EccPubKey, 12, 11)),
String2Hex('RDEC2' + Copy(Cnt.Crypt.EccPubKey, 23, 11))
], Msg2, Compressed, CRC, Len)
else if Encrypted = 1 then
ReadyMsg := CreateDataPayload([String2Hex(BigCapability[CAPS_big_CryptMsg].v)], Msg2, Compressed, CRC, Len);
end else
if UseCryptMsg and (CAPS_big_QIP_Secure in Cnt.capabilitiesBig) and (Cnt.Crypt.qippwd > 0) and not IsBin then
begin // QIP crypt message
// Still relevant?
(*
Msg2Send := qip_msg_crypt(msg2send, c.Crypt.qippwd);
// sutf := Length_DLE(GUIDToString(msgQIPpass));
sutf := Length_DLE(msgQIPpassStr);
flags := flags or IF_Encrypt;
*)
end;
if IsBin then
AddOutgoingMessage(Cnt, '', HistMsg, Flags)
else
AddOutgoingMessage(Cnt, HistMsg, '', Flags);
if ReadyMsg = '' then
Exit;
Handler := procedure(RespStr: String)
var
MsgID: TMsgID;
State, sTmp: String;
iTmp: Integer;
Tmp: TJSONValue;
JSON: TJSONObject;
Resp: TPair;
begin
if ParseJSON(RespStr, JSON) then
try
Resp := CheckResponseData(JSON, eventData);
if not (Resp.Key = Integer(EAC_OK)) then
Exit;
JSON.GetValueSafe('state', State);
if State = '' then
Exit;
eventFlags := 0;
eventMsgA := State;
if JSON.GetValueSafe('ts', iTmp) then
eventTime := UnixToDateTime(iTmp, False);
if JSON.GetValueSafe('msgId', sTmp) then
eventWID := sTmp;
if JSON.GetValueSafe('histMsgId', MsgID) then
eventMsgID := MsgID;
NotifyListeners(IE_serverAck);
finally
FreeAndNil(JSON);
end;
end;
Params := TDictionary.Create;
Params.Add('f', 'json');
Params.Add('aimsid', fAimSid);
Params.Add('t', Cnt.UID2Cmp);
Params.Add('r', OEv.sID);
// Access to im/sendDataIM is forbidden :(
// if Encrypted > 0 then
// begin
// if Encrypted = 2 then
// Params.Add('cap', RDEC_CAPS)
// else
// Params.Add('cap', String2Hex(BigCapability[CAPS_big_CryptMsg].v));
//
// Params.Add('type', 'data');
// Params.Add('data', Msg2);
// Params.Add('dataIsBase64', '1');
// Params.Add('hostCheck', '1');
// BaseURL := WIM_HOST + 'im/sendDataIM';
// SendRequestAsync(BaseURL, MakeParams('POST', BaseURL, Params, False));
// end
// parts[quotes], mentions
Params.Add(IfThen(IsSticker, 'stickerId', 'message'), ReadyMsg);
// (is_sms)
// 'displaySMSSegmentData': 'true'
// else
Params.Add('offlineIM', '1');
Params.Add('notifyDelivery', 'true');
BaseURL := WIM_HOST + IfThen(IsSticker, 'im/sendSticker', 'im/sendIM');
SendRequestAsync(True, BaseURL, MakeParams('POST', BaseURL, Params, False), 'Send ' + IfThen(IsSticker, 'sticker', 'message'), Handler);
Params.Free;
end; // SendMsg
function TICQSession.CreateDataPayload(Caps: TArray; const Data: TBytes = nil; Compressed: Integer = -1; CRC: Cardinal = 0; Len: Integer = 0): String;
var
JSON: TJSONObject;
CapsArr: TJSONArray;
Cap: String;
begin
Result := TEncoding.UTF8.GetString(Data);
JSON := TJSONObject.Create;
try
CapsArr := TJSONArray.Create;
for Cap in Caps do
CapsArr.Add(Cap);
JSON.AddPair(TJSONPair.Create('type', 'RnQDataIM'));
JSON.AddPair(TJSONPair.Create('caps', CapsArr));
if Assigned(Data) then
JSON.AddPair(TJSONPair.Create('data', TEncoding.ANSI.GetString(Data)));
if not (Compressed = -1) then
JSON.AddPair(TJSONPair.Create('compressed', TJSONNumber.Create(Compressed)));
if not (CRC = 0) then
JSON.AddPair(TJSONPair.Create('crc', TJSONNumber.Create(CRC)));
if not (Len = 0) then
JSON.AddPair(TJSONPair.Create('length', TJSONNumber.Create(Len)));
Result := JSON.ToString;
finally
JSON.Free;
end;
end;
function TICQSession.SendBuzz(Cnt: TICQContact): Boolean;
var
Params: TDictionary;
Pair: TJSONPair;
BaseURL: String;
begin
Result := False;
if not IsReady or (SecondsBetween(Now, buzzedLastTime) < 15) then
Exit;
BuzzedLastTime := Now;
Params := TDictionary.Create;
try
Params.Add('f', 'json');
Params.Add('aimsid', fAimSid);
Params.Add('t', cnt.UID2Cmp);
Params.Add('r', CreateNewGUID);
Params.Add('message', CreateDataPayload([String2Hex(BigCapability[CAPS_big_Buzz].v)]));
Params.Add('offlineIM', '1');
Params.Add('notifyDelivery', 'true');
BaseURL := WIM_HOST + 'im/sendIM';
SendRequestAsync(True, BaseURL, MakeParams('POST', BaseURL, Params, False), 'Send buzz');
Result := True;
finally
Params.Free;
end;
end;
procedure TICQSession.SendContacts(Cnt: TICQContact; flags: DWord; cl: TRnQCList);
var
s: RawByteString;
c: TICQContact;
begin
if not IsReady then exit;
if cl.empty then exit;
s := IntToStr(TList(cl).count)+#$FE;
for c in cl do
s := s + UTF(c.UID2cmp) +#$FE + UTF(c.nick) + #$FE;
end; // SendContacts
procedure TICQSession.sendWPsearch(wp:TwpSearch; idx : Integer);
function TLVIfNotNull(t : word; const s : RawByteString) : RawByteString; inline;
begin
if s > '' then
result := TLV_LE(t, WNTS(s));
end;
function TLVIfbNotNull(t : word; b : byte) : RawByteString; inline;
begin
if b > 0 then
result := TLV_LE(t, AnsiChar(b));
end;
function TLVIfWNotNull(t : word; w : word) : RawByteString; inline;
begin
if w > 0 then
result := TLV_LE(t, word_LEasStr(w));
end;
function TLVIfDWNotNull(t : word; d : dword) : RawByteString; inline;
begin
if d > 0 then
result := TLV_LE(t, dword_BEasStr(d));
end;
function TLVIfINotNull(t : word; w : word; const s : RawByteString) : RawByteString; inline;
begin
if (w > 0) or (s > '') then
result := TLV_LE(t, word_LEasStr(w) + WNTS(s));
end;
const
TAB:array [boolean] of AnsiChar=(#$B2,#$D0);
var
s : RawByteString;
begin
if not IsReady then exit;
wasUINwp:=wp.uin > '';
if wasUINwp then
begin
// s := TAB[myinfo.uin=wp.uin]+#4+dword_LEasStr(wp.uin);
s := #$1F#5 + dword_LEasStr(StrToIntDef(wp.uin, 0));
end
else
{ if wp.email > '' then
begin
s := word_LEasStr(META_SEARCH_EMAIL)
+ TLV_LE(User_email, WNTS(wp.email));
end
else}
begin
{
s := word_LEasStr(META_SEARCH_GENERIC)
+ TLVIfNotNull(User_First, wp.first)
+ TLVIfNotNull(User_Last, wp.last)
+ TLVIfNotNull(User_Nick, wp.nick)
+ TLVIfNotNull(User_email, wp.email)
+ TLVIfNotNull(User_City, wp.city)
+ TLVIfNotNull(User_State, wp.state)
+ TLVIfINotNull(User_Inter, wp.wInterest, wp.keyword)
+ TLVIfNotNull(User_Lang, wp.lang)
+ TLVIfbNotNull(User_Gender, wp.gender)
+ TLVIfDWNotNull(User_Age, wp.age)
+ TLVIfbNotNull(User_OnOf, Byte(wp.onlineOnly))
+ TLVIfWNotNull(User_Cntry, wp.country)
}
end;
// TODO: Actual WP search
if wasUINwp then
AddRef(REF_wp, wp.uin)
else
AddRef(REF_wp, '');
end; // sendWPsearch
procedure TICQSession.SendWPSearch2(wp: TwpSearch; idx: Integer; IsWP: Boolean = True);
function TLVIfNotNull(t : word; const s : RawByteString) : RawByteString;
begin
if s > '' then
result := TLV(t, WNTS(s));
end;
function TLVIfbNotNull(t : word; b : byte) : RawByteString;
begin
if b > 0 then
result := TLV(t, AnsiChar(b));
end;
function TLVIfWNotNull(t : word; w : word) : RawByteString;
begin
if w > 0 then
result := TLV(t, word_BEasStr(w));
end;
function TLVIfDWNotNull(t : word; d : dword) : RawByteString;
begin
if d > 0 then
result := TLV(t, dword_BEasStr(d));
end;
function TLVIfDWLENotNull(t : word; d : dword) : RawByteString;
begin
if d > 0 then
result := TLV(t, dword_LEasStr(d));
end;
function TLVIfINotNull(t : word; w : word; const s : RawByteString) : RawByteString;
begin
if (w > 0) or (s > '') then
result := TLV(t, word_LEasStr(w) + WNTS(s));
end;
{ function TLVIfSNotNull(t : word; const s : String) : String;
begin
if (s > '') then
result := TLV(t, Length_LE(s));
end;}
function TLVIfSNotNull(t : word; const s : RawByteString) : RawByteString;
begin
if (s > '') then
result := TLV(t, s);
end;
//const
// TAB:array [boolean] of AnsiChar=(#$B2,#$D0);
var
s: RawByteString;
begin
if not IsReady then
Exit;
wasUINwp := false;
// if (not IsWP) and (wp.uin > '') then
// TLVIfSNotNull(META_COMPAD_UID, wp.uin)+
// TLVIfSNotNull(META_COMPAD_INFO_HASH, wp.Token)
// else
begin
s := #$05#$B9#$0F#$A0#$00#$00#$00#$00#$00#$00
+ word_BEasStr($00)
+ word_BEasStr($FDE9) // UTF8
+ word_BEasStr($00)
+ TLV(02, Word(idx))
// + TLV(01,
// + TLVIfNotNull(User_First, wp.first)
// + TLVIfNotNull(User_Last, wp.last)
// TLVIfNotNull(META_COMPAD_UID, wp.uin)
// TLVIfSNotNull(CP_User_NICK, UTF(wp.nick))
// + TLVIfNotNull(User_email, wp.email)
// + TLVIfDWNotNull(CP_User_Cntry, wp.country)
// + TLVIfSNotNull(CP_User_City, UTF(wp.city))
// + TLVIfNotNull(User_State, wp.state)
// + TLVIfINotNull(User_Inter, wp.wInterest, wp.keyword)
// + TLVIfSNotNull(CP_User_Lang, wp.lang)
// + TLVIfbNotNull(CP_User_Gender, wp.gender)
// + TLVIfDWLENotNull(CP_User_Age, wp.age)
// + TLVIfWNotNull(CP_User_ONLINE, word(wp.onlineOnly))
// + TLVIfNotNull(User_, wp.)
// + TLVIfNotNull(User_, wp.)
// );
end;
// TODO: Actual WP2 search
// if wasUINwp then
// AddRef(REF_wp,wp.uin)
// else
if IsWP then
AddRef(REF_wp, '');
end; // sendWPsearch2
procedure TICQSession.GetProfile(const UID: TUID);
var
JSON: TJSONObject;
Query: UTF8String;
BaseURL: String;
user, groups: TJSONValue;
users: TJSONArray;
begin
if not IsReady or (UID = '') then
Exit;
BaseURL := WIM_HOST + 'presence/get';
Query := '&mdir=1' +
'&t=' + ParamEncode(String(UID)) +
AllFieldsAsQuery;
if SendSessionRequest(False, BaseURL, Query, RT_JSON, JSON, 'Contact info') then
try
ProcessUsersAndGroups(JSON);
finally
JSON.Free;
end;
GetContactAttrs(UID);
end;
procedure TICQSession.GetContactAttrs(const UID: TUID);
var
c: TICQContact;
JSON: TJSONObject;
Query: UTF8String;
BaseURL: String;
begin
BaseURL := WIM_HOST + 'buddylist/getBuddyAttribute';
Query := '&buddy=' + ParamEncode(String(UID));
if SendSessionRequest(False, BaseURL, Query, RT_JSON, JSON, 'Get contact [' + String(UID) + '] attributes') then
try
c := GetICQContact(UID);
if Assigned(c) then
with JSON do
begin
GetValueSafe('note', c.ssImportant);
GetValueSafe('smsNumber', c.ssCell);
GetValueSafe('workNumber', c.ssCell2);
GetValueSafe('phoneNumber', c.ssCell3);
GetValueSafe('otherNumber', c.ssCell4);
GetValueSafe('friendly', c.ssNickname)
end;
finally
JSON.Free;
end;
end;
procedure TICQSession.SendContactAttrs(c: TICQContact);
var
Query: UTF8String;
BaseURL: String;
Params: TDictionary;
begin
Params := TDictionary.Create();
BaseURL := WIM_HOST + 'buddylist/setBuddyAttribute';
Params.Clear;
Params.Add('buddy', String(c.UID2Cmp));
if not IsMyAcc(c) then // Returns error for own contact
Params.Add('friendly', c.ssNickname);
Params.Add('note', c.ssImportant); // Not working, value stays unchanged on server
Params.Add('smsNumber', c.ssCell);
Params.Add('workNumber', c.ssCell2);
Params.Add('phoneNumber', c.ssCell3);
Params.Add('otherNumber', c.ssCell4);
SendSessionRequest(True, BaseURL, '&' + MakeParams('POST', BaseURL, Params, False), 'Save my contact attributes', 'Failed to save your contact attributes');
Params.Free;
end;
procedure TICQSession.GetContactInfo(const UID: TUID; const IncludeField: String);
var
JSON: TJSONObject;
Query: UTF8String;
BaseURL: String;
user: TJSONValue;
users: TJSONArray;
begin
if not IsReady or (IncludeField = '') then
Exit;
BaseURL := WIM_HOST + 'presence/get';
Query := '&mdir=0&t=' + ParamEncode(String(UID)) +
'&' + IncludeField + '=1'; // No profile, but still some other fields are there
if SendSessionRequest(False, BaseURL, Query, RT_JSON, JSON, 'Get contact [' + String(UID) + '] info [' + IncludeField + ']') then
try
ProcessUsersAndGroups(JSON);
finally
JSON.Free;
end;
end;
procedure TICQSession.GetCL;
var
JSON: TJSONObject;
Query: UTF8String;
BaseURL: String;
begin
if not IsReady then
Exit;
// BaseURL := WIM_HOST + 'buddylist/get';
// Query := '&includeBuddies=0'; // groups+users or groups only
BaseURL := WIM_HOST + 'presence/get';
Query := '&mdir=1' +
'&bl=1' +
AllFieldsAsQuery;
if SendSessionRequest(False, BaseURL, Query, RT_JSON, JSON, 'Get CL', 'Failed to get CL') then
try
ProcessContactList(JSON.GetValue('groups') as TJSONArray, True);
RnQmain.CLBox.FinishBuild;
finally
JSON.Free;
end;
end;
procedure TICQSession.FindContact;
var
// JSON: TJSONObject;
Query: UTF8String;
BaseURL: String;
begin
if not IsReady then
Exit;
BaseURL := WIM_HOST + 'presence/get';
Query := '&mdir=1';
if SendSessionRequest(False, BaseURL, Query, 'Find contact') then
{
try
ProcessUsersAndGroups(JSON);
finally
JSON.Free;
end;
}
end;
procedure TICQSession.ValidateSid;
var
Query: UTF8String;
BaseURL: String;
begin
if not IsReady then
Exit;
BaseURL := WIM_HOST + 'aim/validateSid';
SendSessionRequest(False, BaseURL, '', 'Validate AimSid');
end;
procedure TICQSession.GetExpressions; // Avatars only?
var
Query: UTF8String;
BaseURL: String;
begin
BaseURL := WIM_HOST + 'expressions/get2'; // expressions/get
Query := 'f=json' +
'&t=' + ParamEncode(String('230490'));
SendRequest(False, BaseURL, Query, 'Get expressions');
end;
procedure TICQSession.GetAllCaps;
var
JSON: TJSONObject;
Query: UTF8String;
BaseURL: String;
Cnt: TICQContact;
user: TJSONValue;
users: TJSONArray;
begin
if not IsReady then
Exit;
BaseURL := WIM_HOST + 'presence/get';
Query := '&capabilities=1';
if fRoster.Count > 0 then
for Cnt in fRoster do
if not (cnt.Status in [SC_OFFLINE, SC_UNK]) then
Query := Query + '&t=' + String(Cnt.UID2cmp);
if SendSessionRequest(True, BaseURL, Query, RT_JSON, JSON, 'Get caps for all online contacts') then
try
ProcessUsersAndGroups(JSON);
finally
JSON.Free;
end;
end;
procedure TICQSession.Test;
var
Query{, Params}: UTF8String;
BaseURL: String;
Params: TDictionary;
begin
// BaseURL := WIM_HOST + 'aim/getSMSInfo'; // Server error
// Query := '&phone=911';
// BaseURL := WIM_HOST + 'memberDir/get';
// Query := '&locale=en-us&t=230490&infoLevel=full';
SendSessionRequest(False, BaseURL, Query, 'Test');
// BaseURL := STORE_HOST + 'store/showcase';
// Params := TDictionary.Create();
// Params.Add('a', fAuthToken);
// Params.Add('f', 'json');
// Params.Add('k', fDevId);
// Params.Add('ts', IntToStr(DateTimeToUnix(Now, False) - fHostOffset));
// Params.Add('r', CreateNewGUID);
// Params.Add('client', 'icq');
// Params.Add('lang', IfThen(IsRuLang, 'ru-ru', 'en-us'));
// Params.Add('platform', 'windows');
// SendRequest(False, BaseURL, MakeParams('GET', BaseURL, Params), 'Test', '');
// Params.Free;
// PatchVersion := '6652078904624284395';
// Params := '{"sn": "' + '230490' + '", "fromMsgId": ' + '6652082520986747860' + ', "count": ' + IntToStr(999) + ', "aimSid": "' + fAimSid + '", "patchVersion": "' + PatchVersion + '"}';
// Query := '{"method": "getHistory", "reqId": "' + IntToStr(ReqId) + '-' + IntToStr(DateTimeToUnix(Now, False) - fHostOffset) + '", "authToken": "' + fRESTToken + '", "clientId": ' + fRESTClientId + ', "params": ' + params + ' }';
// SendRequest(True, REST_HOST, Query, 'Test', '');
// Inc(ReqId);
end;
procedure TICQSession.GetStoreStickerPacks;
var
BaseURL: String;
Params: TDictionary;
Handler: THandlerProc;
begin
if RequiresLogin then
Exit;
BaseURL := STORE_HOST + 'openstore/contentlist';
Params := TDictionary.Create();
Params.Add('a', fAuthToken);
Params.Add('f', 'json');
Params.Add('k', fDevId);
Params.Add('ts', IntToStr(DateTimeToUnix(Now, False) - fHostOffset));
Params.Add('r', CreateNewGUID);
// Params.Add('platform', 'windows');
Params.Add('client', 'icq');
Params.Add('lang', IfThen(IsRuLang, 'ru-ru', 'en-us'));
Handler := procedure(RespStr: String)
var
Tmp: TJSONValue;
JSON: TJSONObject;
Sticker: TJSONValue;
Stickers: TJSONArray;
SRecord: TStickerPack;
begin
if ParseJSON(RespStr, JSON) then
try
if not CheckSimpleData(JSON, True) then
Exit;
Tmp := JSON.GetValue('stickers');
if not Assigned(Tmp) or not (Tmp is TJSONObject) then
Exit;
Tmp := TJSONObject(Tmp).GetValue('sets');
if not Assigned(Tmp) or not (Tmp is TJSONArray) then
Exit;
SQLDB.ClearStickerPacks;
Stickers := TJSONArray(Tmp);
for Sticker in Stickers do
if Assigned(Sticker) then
begin
SRecord := TStickerPack.FromJSON(TJSONObject(Sticker));
// Skip duplicates
if DupStickerPacks.Contains(SRecord.Id) then
Continue;
// Skip disabled, but not the hidden ones
if not SRecord.IsEnabled and not HiddenStickerPacks.Contains(SRecord.Id) then
Continue;
SQLDB.AddStickerPack(SRecord);
end;
finally
FreeAndNil(JSON);
end;
NotifyListeners(IE_stickersupdate);
end;
SendRequestAsync(False, BaseURL, MakeParams('GET', BaseURL, Params), 'Get store sticker packs', Handler);
Params.Free;
end;
procedure TICQSession.SearchStoreStickerPack(const StoreId: String);
var
SRecord: TStickerPack;
begin
SetLength(LastSearchPacks, 0);
SRecord := GetStoreStickerPack(StoreId, True);
if not (SRecord.Id = 0) then
begin
SetLength(LastSearchPacks, 1);
LastSearchPacks[0] := SRecord;
end;
NotifyListeners(IE_stickersearchupdate);
end;
procedure TICQSession.SearchStoreStickerPacks(const Query: String);
var
BaseURL: String;
Params: TDictionary;
Handler: THandlerProc;
begin
if RequiresLogin then
Exit;
BaseURL := STORE_HOST + 'store/showcase';
Params := TDictionary.Create();
Params.Add('a', fAuthToken);
Params.Add('f', 'json');
Params.Add('k', fDevId);
Params.Add('ts', IntToStr(DateTimeToUnix(Now, False) - fHostOffset));
Params.Add('r', CreateNewGUID);
Params.Add('platform', 'windows');
Params.Add('client', 'icq');
Params.Add('lang', IfThen(IsRuLang, 'ru-ru', 'en-us'));
Params.Add('search', Trim(Query));
Handler := procedure(RespStr: String)
var
Tmp: TJSONValue;
JSON: TJSONObject;
Res: TJSONValue;
Ress: TJSONArray;
SRecord: TStickerPack;
begin
SetLength(LastSearchPacks, 0);
if ParseJSON(RespStr, JSON) then
try
if not CheckSimpleData(JSON) then
Exit;
Tmp := JSON.GetValue('top');
if not Assigned(Tmp) or not (Tmp is TJSONArray) then
Exit;
Ress := TJSONArray(Tmp);
for Res in Ress do
if Assigned(Res) then
begin
SRecord := TStickerPack.FromJSON(TJSONObject(Res));
SetLength(LastSearchPacks, Length(LastSearchPacks) + 1);
LastSearchPacks[Length(LastSearchPacks) - 1] := SRecord;
end;
finally
FreeAndNil(JSON);
end;
NotifyListeners(IE_stickersearchupdate);
end;
SendRequestAsync(False, BaseURL, MakeParams('GET', BaseURL, Params), 'Search store sticker packs', Handler);
Params.Free;
end;
function TICQSession.GetStoreStickerPack(const Id: String; IsStoreId: Boolean): TStickerPack;
var
Tmp: TJSONValue;
JSON: TJSONObject;
BaseURL: String;
Params: TDictionary;
ErrHandler: TErrorProc;
begin
Result := Default(TStickerPack);
if RequiresLogin or (Id = '') then
Exit;
BaseURL := STORE_HOST + 'openstore/filespackinfowithmeta'; // packinfo
Params := TDictionary.Create();
Params.Add('a', fAuthToken);
Params.Add('f', 'json');
Params.Add('k', fDevId);
Params.Add('ts', IntToStr(DateTimeToUnix(Now, False) - fHostOffset));
Params.Add('r', CreateNewGUID);
Params.Add('platform', 'windows');
Params.Add('client', 'icq');
Params.Add('lang', IfThen(IsRuLang, 'ru-ru', 'en-us'));
if IsStoreId then
Params.Add('store_id', Id)
else
Params.Add('id', Id);
ErrHandler := procedure(Resp: TPair)
begin
OutputDebugString(PChar('Error: ' + IntToStr(Resp.Key) + ', ' + Resp.Value));
end;
SendRequest(False, BaseURL, MakeParams('GET', BaseURL, Params), RT_JSON, JSON, 'Get sticker pack store id', '', ErrHandler);
if Assigned(JSON) then
try
if CheckSimpleData(JSON) then
Result := TStickerPack.FromJSON(TJSONObject(JSON));
finally
FreeAndNil(JSON);
end;
Params.Free;
end;
procedure TICQSession.BuyStickerPack(const PackId: String);
var
SRecord: TStickerPack;
BaseURL: String;
Params: TDictionary;
Handler: THandlerProc;
PID: Integer;
begin
// Packs that cannot be purchased
if TryStrToInt(PackId, PID) then
if HiddenStickerPacks.Contains(PID) then
begin
SQLDB.ChangeStickerPackStatus(PackId, True);
NotifyListeners(IE_stickersupdate);
Exit;
end;
SRecord := GetStoreStickerPack(PackId, False);
if SRecord.StoreId = '' then
MsgDlg(GetTranslation(ICQError2Str[EC_StoreProblem], [GetTranslation('Unable to get sticker pack store id')]), False, mtError);
if RequiresLogin or (PackId = '') or (SRecord.StoreId = '') then
Exit;
BaseURL := STORE_HOST + 'store/buy/free';
Params := TDictionary.Create();
Params.Add('a', fAuthToken);
Params.Add('f', 'json');
Params.Add('k', fDevId);
Params.Add('ts', IntToStr(DateTimeToUnix(Now, False) - fHostOffset));
Params.Add('r', CreateNewGUID);
Params.Add('platform', 'windows');
Params.Add('client', 'icq');
Params.Add('lang', IfThen(IsRuLang, 'ru-ru', 'en-us'));
Params.Add('product', SRecord.StoreId);
Handler := procedure(RespStr: String)
var
Tmp: TJSONValue;
JSON: TJSONObject;
Verified: Boolean;
begin
if ParseJSON(RespStr, JSON) then
try
if not CheckSimpleData(JSON) then
Exit;
JSON.GetValueSafe('is_verified', Verified);
if Verified then
begin
SQLDB.AddStickerPack(SRecord);
SQLDB.ChangeStickerPackStatus(PackId, True);
NotifyListeners(IE_stickersupdate);
end
else
begin
eventError := EC_StoreProblem;
eventMsgA := GetTranslation('Purchase data failed the verification');
NotifyListeners(IE_error);
end;
finally
FreeAndNil(JSON);
end;
end;
SendRequestAsync(True, BaseURL + '?' + MakeParams('GET', BaseURL, Params), 'product=' + SRecord.StoreId, 'Buy free sticker pack', Handler);
Params.Free;
end;
procedure TICQSession.RemoveStickerPack(const PackId: String);
var
BaseURL: String;
Params: TDictionary;
Handler: THandlerProc;
PID: Integer;
begin
// Packs that cannot be purchased
if TryStrToInt(PackId, PID) then
if HiddenStickerPacks.Contains(PID) then
begin
SQLDB.ChangeStickerPackStatus(PackId, False);
RemoveStickerPackCache(PackId);
NotifyListeners(IE_stickersupdate);
Exit;
end;
if RequiresLogin or (PackId = '') then
Exit;
BaseURL := STORE_HOST + 'store/deletepurchase';
Params := TDictionary.Create();
Params.Add('a', fAuthToken);
Params.Add('f', 'json');
Params.Add('k', fDevId);
Params.Add('ts', IntToStr(DateTimeToUnix(Now, False) - fHostOffset));
Params.Add('r', CreateNewGUID);
Params.Add('platform', 'windows');
Params.Add('client', 'icq');
Params.Add('lang', IfThen(IsRuLang, 'ru-ru', 'en-us'));
// Params.Add('product_id', 'ai_s1');
Handler := procedure(RespStr: String)
var
Tmp: TJSONValue;
JSON: TJSONObject;
Status: String;
begin
if ParseJSON(RespStr, JSON) then
try
if not CheckSimpleData(JSON, True) then
Exit;
JSON.GetValueSafe('description', Status);
if Status = 'OK' then
begin
SQLDB.ChangeStickerPackStatus(PackId, False);
RemoveStickerPackCache(PackId);
NotifyListeners(IE_stickersupdate);
end
else
begin
eventError := EC_StoreProblem;
eventMsgA := GetTranslation('Cannot remove sticker pack: %s', [Status]);
NotifyListeners(IE_error);
end;
finally
FreeAndNil(JSON);
end;
end;
SendRequestAsync(True, BaseURL + '?' + MakeParams('GET', BaseURL, Params), 'product_id=' + PackId, 'Remove sticker pack', Handler);
Params.Free;
end;
procedure TICQSession.SendSMS(const Dest, Msg: String; Ack: Boolean);
begin
if not IsReady then
Exit;
// TODO?
end; // SendSMS
procedure TICQSession.SendSMS2(const Dest, Msg: String; Ack: Boolean);
var
req: RawByteString;
begin
if not IsReady then
Exit;
// msg := '' + msg + '';
// msg := StrToUnicode(msg);
// ODS(hexdumps(msg));
req := qword_LEasStr(SNACref) + word_BEasStr(MTYPE_PLAIN)
+ Length_B(dest)
{ + TLV(CLI_META_MSG_DATA,
AnsiChar(CLI_META_REQ_CAPS_BYTE)
+ AnsiChar(CLI_META_FRAG_VERSION_BYTE)
+ Length_BE(#$01) // no caps
+ AnsiChar(CLI_META_FRAG_ID_BYTE)
+ AnsiChar(CLI_META_FRAG_VERSION_BYTE)
+ Length_BE(word_BEasStr(CLI_META_MSG_CHARSET) + word_BEasStr(CLI_META_MSG_LANGUAGE) + msg))
+ TLV(CLI_META_STORE_IF_OFFLINE, '')
+ TLV(CLI_META_MSG_OWNER, '230490')
+ TLV(CLI_META_MSG_UNK, #$00#$00#$00#$01)};
// if ack then
// req := req + TLV(CLI_META_MSG_ACK, '');
// TODO??
AddRef(REF_sms, '');
end; // SendSMS2
procedure TICQSession.SendSaveMyInfo(c: TICQContact);
var
BaseURL: String;
Params: TDictionary;
begin
if c.birth > 0 then
c.age := YearsBetween(Now, c.birth);
SavingMyInfo.ACKcount := 3;
BaseURL := WIM_HOST + 'memberDir/update';
Params := TDictionary.Create();
Params.Add('set=firstName', c.First);
Params.Add('set=lastName', c.Last);
// Params.Add('set=nick', c.Nick);
Params.Add('set=friendlyName', c.Nick);
Params.Add('set=relationshipStatus', SrvMarStsByID(c.MarStatus));
Params.Add('set=birthDate', IntToStr(DateTimeToUnix(c.Birth)));
Params.Add('set=gender', IfThen(c.Gender = 2, 'male', IfThen(c.Gender = 1, 'female', 'unknown')));
Params.Add('set=lang1', c.Lang[1]);
Params.Add('set=lang2', c.Lang[2]);
Params.Add('set=lang3', c.Lang[3]);
Params.Add('set=tz', '99'{Result.GMThalfs});
Params.Add('set=aboutMe', c.About);
// Params.Add('set=originAddress', '{city=' + ParamEncode(c.BirthCity) + ',state=' + ParamEncode(c.BirthState) + ',' +
// 'country=' + ParamEncode(c.BirthCountry) + '}');
// Params.Add('set=homeAddress', '{street=' + ParamEncode(c.Address) + ',city=' + ParamEncode(c.City) + ',' +
// 'state=' + ParamEncode(c.State) + ',zip=' + ParamEncode(c.ZIP) + ',' +
// 'country=' + ParamEncode(c.Country) + '}');
Params.Add('set=homeAddress', '{city=' + ParamEncode(c.City) + ',state=' + ParamEncode(c.State) + ',' +
'country=' + ParamEncode(c.Country) + '}');
// Params.Add('set=phones', '[{type=home,phone=666h} {type=work,phone=666w} {type=mobile,phone=666m} {type=homeFax,phone=666hf} {type=workFax,phone=666wf} {type=other,phone=666o}]');
// Params.Add('set=jobs', '[{title=1,company=2,website=3,department=4,industry=arts,subIndustry=music,startDate=444,endDate=666,street=5,city=6,state=7,zip=8,country=' + ParamEncode(c.Country) + '}]');
// Params.Add('set=interests', '[{code=art,text=test}]');
if SendSessionRequest(True, BaseURL, '&' + MakeParams('POST', BaseURL, Params, False, True), 'Save my info', 'Failed to save your information') then
NotifyListeners(IE_MyInfoAck);
Params.Free;
end;
procedure TICQSession.AddMsg(Flags: Byte; Urgent: Boolean; const Msg: RawByteString);
begin
if Flags and $80 > 0 then Inc(eventFlags, IF_multiple);
if Flags and $40 > 0 then Inc(eventFlags, IF_no_matter);
if Urgent then Inc(eventFlags, IF_urgent);
eventMsgA := msg;
NotifyListeners(IE_msg);
end; // AddMsg
procedure TICQSession.ParseMsgError(const snac: RawByteString; ref:integer);
begin
eventMsgID := ref;
eventInt := word_BEat(@snac[1]);
eventFlags := 0;
NotifyListeners(IE_msgError);
end; // parseMsgError
function TypeStringToTypeId(const s: AnsiString): Integer;
var
nTypeID : Integer;
begin
nTypeID := 0;
if (s = Str_message) then
nTypeID := MTYPE_PLAIN
else if s = 'StatusMsgExt' then
nTypeID := MTYPE_AUTOAWAY
else if (s = 'Web Page Address (URL)') or
(s = 'Send Web Page Address (URL)') or
(s = 'Send URL') then
nTypeID := MTYPE_URL
else if (s = 'Contacts') or
(s = 'Send Contacts') then
nTypeID := MTYPE_CONTACTS
else if (s = 'ICQ Chat') then
nTypeID := MTYPE_CHAT
else if (s = 'Send / Start ICQ Chat') then
nTypeID := MTYPE_CHAT
else if (s ='File') or
(s = 'File Transfer')or
(s = 'Файл') then
nTypeID := MTYPE_FILEREQ
else if (s = 'Request For Contacts') then
nTypeID := MTYPE_PLUGIN
else if s=PLUGIN_SCRIPT then
nTypeID := MTYPE_XSTATUS
else if (s = 'Greeting Card') or
(s = 'Send Greeting Card')or
(s = 'Отправить открытку') then
nTypeID := MTYPE_GCARD
else if (s = 'T-Zer Message')or(s = 'Send Tzer') then
nTypeID := MTYPE_PLAIN
else if s = 'StatusMsgExt' then
else if pos(AnsiString('Сообщ'), s) > 0 then
nTypeID := MTYPE_PLAIN;
result := nTypeID;
end;
function parseTzerTag(const sA: RawByteString): RawByteString;
var
p : Integer;
imgStr: String;
ext: RawByteString;
begin
p := PosEx('name="', sA);
Result := GetTranslation('tZer') + ': ' + copy(sA, p + 6, PosEx('"', sA, p + 7) - p - 6) + #13#10;
p := PosEx('url="', sA);
Result := Result + copy(sA, p + 5, PosEx('"', sA, p + 6) - p - 5) + #13#10;
p := PosEx('thumb="', sA);
ext := copy(sA, p + 7, PosEx('"', sA, p + 8) - p - 7);
try
imgStr := '';
LoadFromURLAsString(ext, imgStr);
if Trim(imgStr) = '' then
imgStr := ext
else
imgStr := RnQImageExTag + Base64EncodeString(imgStr) + RnQImageExUnTag;
except
imgStr := ext;
end;
Result := Result + imgStr + #13#10;
end;
procedure TICQSession.parse1503(const snac: RawByteString; ref:integer; flags : word);
var
ofs:integer;
procedure extractWP;
var
next:integer;
begin
next := readWORD(snac, ofs);
inc(next,ofs);
eventwp.uin := IntToStr(readINT(snac, ofs));
eventwp.nick := UnUTF(getWNTS(snac, ofs));
eventwp.first := UnUTF(getWNTS(snac, ofs));
eventwp.last := UnUTF(getWNTS(snac, ofs));
eventwp.email := UnUTF(getWNTS(snac, ofs));
eventwp.authRequired := readBYTE(snac, ofs)=0;
eventwp.status := readWORD(snac, ofs);
eventWP.gender := readBYTE(snac, ofs);
eventWP.age := readWORD(snac, ofs);
eventWP.bday := 0;
try
inc(ofs, 3);
// eventWP.BaseID := getWNTS(snac, ofs); //The base ID. (ðàìáëåð, áèãìèð, àòëàñ ...)
except end;
ofs := next;
// request issued from white pages
if wasUINwp or (refs[ref].kind = REF_wp) then
begin
NotifyListeners(IE_wpResult);
Exit;
end;
// request issued for internal use
eventContact := GetICQContact(eventWP.uin);
with eventContact do
begin
nick := eventwp.nick;
first := eventwp.first;
last := eventwp.last;
email := eventwp.email;
NotifyListeners(IE_userinfo);
end;
end; // extractWP
procedure extractWP_CP;
var
s: RawByteString;
Pkt1, Pkt2: RawByteString;
isExstsTLV: Boolean;
t, i, k, ofs1, code: Integer;
// t64: Int64;
sU, PhoneNum, PhoneCnt: String;
cnt : TICQcontact;
begin
// eventwp.uin := getTLVSafe(META_COMPAD_UID, snac, ofs);
if eventwp.uin > '' then
begin
// eventwp.nick := UnUTF( getTLVSafe(META_COMPAD_NICK, snac, ofs) );
// eventwp.first := UnUTF( getTLVSafe(META_COMPAD_FNAME, snac, ofs) );
// eventwp.last := UnUTF( getTLVSafe(META_COMPAD_LNAME, snac, ofs) );
// eventwp.email := UnUTF( getTLVSafe(META_COMPAD_EMAIL, snac, ofs));
// eventwp.authRequired := getTLVSafe(META_COMPAD_AUTH, snac, ofs) = #1; // readBYTE(snac, ofs)=0;
eventwp.status := 0; //readWORD(snac, ofs);
// s := getTLVSafe(META_COMPAD_STATUS, snac, ofs);
if Length(s) = 2 then
eventwp.status := word_LEat(Pointer(s));
eventWP.gender := 0;
// s := getTLVSafe(META_COMPAD_GENDER, snac, ofs);
if s > '' then
eventWP.gender := Byte(s[1]);
// eventWP.bday := Int64AsDouble(getTLVqwordBE(META_COMPAD_BDAY, snac, ofs));
if eventWP.bday > 712 then
begin
eventWP.bday := eventWP.bday + 2;
eventWP.age := YearsBetween(now, eventWP.bday)
end else
eventWP.age := 00; //getTLVSafe(snac, ofs);
// eventWP.StsMSG := UnUTF(getTLVSafe(META_COMPAD_STS_MSG, snac, ofs));
if wasUINwp or (refs[ref].kind = REF_wp) then
begin
NotifyListeners(IE_wpResult);
Exit;
end;
// request issued for internal use (Get status string)
cnt := GetICQContact(eventWP.uin);
if Assigned(cnt) then
with cnt do
begin
InfoUpdatedTo := now;
nick := eventwp.nick;
first := eventwp.first;
last := eventwp.last;
email := eventwp.email;
LifeStatus := eventwp.StsMSG;
birth := eventWP.bday;
gender := eventWP.gender;
// s := getTLVSafe(META_COMPAD_LANG1, snac, ofs);
{ if Length(s) >=2 then
cnt.lang[1] := word_BEat(Pointer(s))
else
cnt.lang[1] := 0;
s := getTLVSafe(META_COMPAD_LANG2, snac, ofs);
if Length(s) >=2 then
cnt.lang[2] := word_BEat(Pointer(s))
else
cnt.lang[2] := 0;
s := getTLVSafe(META_COMPAD_LANG3, snac, ofs);
if Length(s) >=2 then
cnt.lang[3] := word_BEat(Pointer(s))
else
cnt.lang[3] := 0;}
// about := UnUTF(getTLVSafe(META_COMPAD_ABOUT, snac, ofs));
// Pkt1 := getTLVSafe(META_COMPAD_Mails, snac, ofs);
// isExstsTLV := existsTLV(META_COMPAD_HOMES, snac, ofs);
// Pkt1 := getTLVSafe(META_COMPAD_HOMES, snac, ofs);
Pkt1 := getTLVSafe(1, Pkt1);
if pkt1 <> '' then
begin
// city := UnUTF(getTLVSafe(META_COMPAD_HOMES_CITY, Pkt1));
// state := UnUTF(getTLVSafe(META_COMPAD_HOMES_STATE, Pkt1));
// s := getTLVSafe(META_COMPAD_HOMES_COUNTRY, Pkt1);
if s <> '' then
country := UnUTF(s);
end else
if isExstsTLV then
begin
city := '';
state := '';
country := '';
end;
// isExstsTLV := existsTLV(META_COMPAD_FROM, snac, ofs);
// Pkt1 := getTLVSafe(META_COMPAD_FROM, snac, ofs);
Pkt1 := getTLVSafe(1, Pkt1);
if pkt1 <> '' then
begin
// birthcity := UnUTF(getTLVSafe(META_COMPAD_FROM_CITY, Pkt1));
// birthstate := UnUTF(getTLVSafe(META_COMPAD_FROM_STATE, Pkt1));
// s := getTLVSafe(META_COMPAD_FROM_COUNTRY, Pkt1);
end else
if isExstsTLV then
begin
end;
// isExstsTLV := existsTLV(META_COMPAD_PHONES, snac, ofs);
// Pkt1 := getTLVSafe(META_COMPAD_PHONES, snac, ofs);
if (Pkt1 > '') and (Length(Pkt1) > 3) then
begin
t := word_BEat(Pkt1, 1);
ofs1 := 3;
if t > 0 then
for i := 1 to t do
begin
Pkt2 := getBEWNTS(Pkt1, ofs1);
// PhoneNum := UnUTF(getTLVSafe(META_COMPAD_PHONES_NUM, Pkt2, 1));
// PhoneCnt := getTLVSafe(META_COMPAD_PHONES_CNT, Pkt2, 1);
if Length(PhoneCnt) >= 2 then
code := word_BEat(PhoneCnt, 1)
else
code := 0;
case code of
1: regular := PhoneNum;
2: workphone := PhoneNum;
3: cellular := PhoneNum;
end;
end;
end
else
begin
if isExstsTLV then
begin
regular := '';
workphone := '';
end;
// cellular := UnUTF(getTLVSafe(META_COMPAD_MOBILE, snac, ofs));
end;
// s := UnUTF(getTLVSafe(META_COMPAD_HP, snac, ofs));
// if s > '' then
// homepage := s;
MarStatus := $00;
// s := getTLVSafe(META_COMPAD_MARITAL_STATUS, snac, ofs);
if s > '' then
MarStatus := word_BEat(@s[1]);
// isExstsTLV := existsTLV(META_COMPAD_WORKS, snac, ofs);
// Pkt1 := getTLVSafe(META_COMPAD_WORKS, snac, ofs);
Pkt1 := getTLVSafe(1, Pkt1);
if pkt1 <> '' then
begin
// workpage := UnUTF(getTLVSafe(META_COMPAD_WORKS_PAGE, Pkt1));
// workPos := UnUTF(getTLVSafe(META_COMPAD_WORKS_POSITION, Pkt1));
// workCompany := UnUTF(getTLVSafe(META_COMPAD_WORKS_ORG, Pkt1));
// workaddress := UnUTF(getTLVSafe(META_COMPAD_WORKS_ADDRESS, Pkt1));
// workcity := UnUTF(getTLVSafe(META_COMPAD_WORKS_CITY, Pkt1));
// workstate := UnUTF(getTLVSafe(META_COMPAD_WORKS_STATE, Pkt1));
// workDep := UnUTF(getTLVSafe(META_COMPAD_WORKS_DEPT, Pkt1));
// workZip := UnUTF(getTLVSafe(META_COMPAD_WORKS_ZIP, Pkt1));
//workphone := '';
// s := getTLVSafe(META_COMPAD_WORKS_COUNTRY, Pkt1);
// if s <> '' then
// workCountry := dword_BEat(s, 1);
end else
if isExstsTLV then
begin
//workPos := '';
//workCompany := '';
//workDep := '';
//workphone := '';
end;
// isExstsTLV := existsTLV(META_COMPAD_INTERESTS, snac, ofs);
// Pkt1 := getTLVSafe(META_COMPAD_INTERESTS, snac, ofs);
if Length(Pkt1) >= 2 then
begin
k := word_BEat(Pointer(Pkt1));
if (k > 0) and (k <= 4) then
begin
cnt.clearInterests;
cnt.interests.Count := k;
ofs1 := 3;
for i := 1 to k do
begin
Pkt2 := getBEWNTS(Pkt1, ofs1);
// s := getTLVSafe(META_COMPAD_INTEREST_ID, Pkt2, 1);
if Length(s) >= 2 then
code := word_BEat(Pointer(s))
else
code := 0;
// s := getTLVSafe(META_COMPAD_INTEREST_TEXT, Pkt2, 1);
sU := UnUTF(s);
cnt.AddInterest(i - 1, code, sU);
end;
end else
if isExstsTLV then
cnt.clearInterests;
end;
// Pkt1 := getTLVSafe(META_COMPAD_INFO_CHG, snac, ofs);
if Length(Pkt1) = 8 then
cnt.lastInfoUpdate := Int64AsDouble(Qword_BEat(Pointer(Pkt1)));
// Pkt1 := getTLVSafe(META_COMPAD_GMT, snac, ofs);
if Length(Pkt1) = 2 then
cnt.GMThalfs := SmallInt(word_BEat(Pointer(Pkt1)));
if cnt.equals(MyAccount) then
begin
// showInfo := getTLVwordBE(META_COMPAD_INFO_SHOW, snac, ofs);
// s := getTLVSafe(META_COMPAD_WEBAWARE, snac, ofs);
// if Length(s) >= 1 then
// P_WebAware := Byte(s[1]) = 1;
end;
eventContact := cnt;
NotifyListeners(IE_userinfoCP);
end;
end;
// Update settings for the new data
UpdatePrefsFrm
end;
var
d, m: byte;
i: byte;
msgtype, msgflags, tmpb: Byte;
ReplyType, replySubtype: Word;
y, tmpw: Word;
msg: RawByteString;
cont: TICQContact;
// msgU,
sU, OldNick, tmps: String;
cntUID: TUID;
function IsUpdateStrOK(const val: String): Boolean; overload;
begin
Result := (val = '') or (not (tmps = '') and (CompareStr(val, tmps) <> 0));
end;
function IsUpdateWordOK(val: Word): Boolean; overload;
begin
Result := (val = 0) or (not (tmpw = 0) and not (val = tmpw));
end;
function IsUpdateByteOK(val: Byte): Boolean; overload;
begin
Result := (val = 0) or (not (tmpb = 0) and not (val = tmpb));
end;
begin
eventFlags := 0;
cntUID := refs[ref].uid;
if cntUID > '' then
cont := GetICQContact(cntUID)
else
cont := nil;
eventTime := now;
ofs := 1;
readBEWORD(snac, ofs); // TLV.Type(1) - encapsulated META_DATA
readBEWORD(snac, ofs); // TLV.Length
readWORD(snac, ofs); // data chunk size (TLV.Length-2)
readINT(snac, ofs); // request owner uin
ReplyType := readWORD(snac, ofs); // reply type: SRV_META_INFO_REPLY
readWORD(snac, ofs); // request sequence number
//ofs:=11;
// ReWrite Переделать!!!
case ReplyType of
$0041: // offline messages
begin
// inc(ofs,4);
cont := GetICQContact(readINT(snac, ofs));
y := readWORD(snac, ofs);
m := readBYTE(snac, ofs);
d := readBYTE(snac, ofs);
if not tryEncodeDate(y, m, d, eventTime) then
eventTime := 0;
d := readBYTE(snac, ofs); // hours
m := readBYTE(snac, ofs);
eventTime := eventTime + EncodeTime(d,m,0,0) + GMToffset0;
msgtype := readBYTE(snac, ofs);
msgflags := readBYTE(snac, ofs);
msgflags := msgflags or IF_offline;
msg := getWNTS(snac, ofs);
eventContact := cont;
// msgu := UnUTF(msg);
// notificationForMsg(msgtype, msgflags, not dontBotherStatus, msg);
end;
$07DA:
begin
replySubtype := readWORD(snac, ofs);
case replySubtype of // Case2
$0FB4: // last wp result (ComPad)
begin // last wp result
// cont.infoUpdatedTo:=now;
// if ord(snac[ofs])=$A then
if readBYTE(snac, ofs) = $A then
begin
// inc(ofs,3);
readWORD(snac, ofs); // following data size
readWORD(snac, ofs); // $05B9
readWORD(snac, ofs); // $0004 or $0009
y := readWORD(snac, ofs); // $8000 or $0000
if y = $0080 then
inc(ofs, $10);
inc(ofs, $11); // Unknown data
eventInt := readBEWORD(snac, ofs); // Count of all
readBEWORD(snac, ofs); // Всего поисков
readBEWORD(snac, ofs); // Текущие поиск
readBEWORD(snac, ofs); // following data size
extractWP_CP;
// eventInt:=readINT(snac, ofs);
// eventInt:=-1; // Just for now
end else
eventInt := -1;
if refs[ref].kind = REF_wp then
NotifyListeners(IE_wpEnd)
else if Assigned(cont) then
begin
eventContact := cont;
NotifyListeners(IE_userSimpleInfo);
end;
end;
// $B40F : // wp result (ComPad)
$0FAA: // wp result (ComPad)
begin // simple query and wp result
y := word_BEat(snac, ofs);
// cont.nodb:=FALSE;
// cont.infoUpdatedTo:=now;
// if ord(snac[ofs+2])=$A then
if readBYTE(snac, ofs )= $A then
begin
// inc(ofs,3);
readWORD(snac, ofs); // following data size
inc(ofs, $1D); // Unknown data
readBEWORD(snac, ofs); // following data size
extractWP_CP;
eventInt := 0;
eventContact := cont;
if y = $B40F then
NotifyListeners(IE_wpEnd)
else
NotifyListeners(IE_userSimpleInfo);
end else if refs[ref].kind = REF_wp then
begin
eventInt := -1;
NotifyListeners(IE_wpEnd);
end else
begin
if Assigned(cont) then
cont.nodb := True;
eventError := EC_badContact;
eventContact := cont;
NotifyListeners(IE_error);
end;
end;
// else
// case ord(snac[ofs+4]) of // Case3
9992, 9993: //META_SIMPLE_QUERY, SRV_USER_FOUND: // simple query and wp result
begin
// if ord(snac[ofs+2])=$A then
if readBYTE(snac, ofs) = $A then
begin
// inc(ofs,3);
// Для обновления ника на серваке
if Assigned(cont) then
OldNick := cont.displayed;
// nick:=unUTF(getWNTS(snac, ofs));
extractWP;
if Assigned(cont) then
begin
cont.nodb := False;
cont.infoUpdatedTo := now;
if cont.Display = cont.UID then
if cont.nick > '' then
cont.Display := '';
if (cont.displayed <> OldNick) and
isInList(LT_ROSTER, cont) and
not cont.CntIsLocal then
//SSI_UpdateContact(cont);
end;
eventInt := 0;
eventContact := cont;
if wasUINwp then
NotifyListeners(IE_wpEnd)
else
NotifyListeners(IE_userSimpleInfo);
end
else if refs[ref].kind = REF_wp then
begin
eventInt := -1;
NotifyListeners(IE_wpEnd);
end else
begin
if Assigned(cont) then
cont.nodb := True;
eventError := EC_badContact;
eventContact := cont;
NotifyListeners(IE_error);
end;
end;
9994: //SRV_LAST_USER_FOUND: // last wp result
begin
if Assigned(cont) then
cont.infoUpdatedTo := now;
// if ord(snac[ofs+2]) = $A then
if readBYTE(snac, ofs) = $A then
begin
// inc(ofs,3);
extractWP;
eventInt := readINT(snac, ofs);
end else
eventInt := -1;
if refs[ref].kind = REF_wp then
NotifyListeners(IE_wpEnd);
end;
9995: //META_NOTES_USERINFO: // query result (about)
begin
if Assigned(cont) then
begin
cont.infoUpdatedTo := now;
inc(ofs, 1);
tmps := UnUTF(getWNTS(snac, ofs));
if IsUpdateStrOK(cont.about) then
cont.about := tmps;
if (flags and 1) = 0 then
NotifyListeners(IE_userinfo);
end;
end;
9996: //META_AFFILATIONS_USERINFO:
begin
cont.infoUpdatedTo := now;
// if snac[ofs+2]=#$14 then
if readBYTE(snac, ofs) = $14 then
cont.nodb := True;
if (flags and 1) = 0 then
NotifyListeners(IE_userinfo);
end;
9997: //META_BASIC_USERINFO: // query result (main, home)
begin
inc(ofs, 1);
if Assigned(cont) then
with cont do
begin
noDB := False;
infoUpdatedTo := now;
OldNick := displayed;
tmps := UnUTF(getWNTS(snac, ofs));
if IsUpdateStrOK(nick) then
nick := tmps;
if (Display = UID) and not (nick = '') then
Display := '';
tmps := UnUTF(getWNTS(snac, ofs));
if IsUpdateStrOK(first) then
first := tmps;
tmps := UnUTF(getWNTS(snac, ofs));
if IsUpdateStrOK(last) then
last := tmps;
tmps := getWNTS(snac, ofs);
if IsUpdateStrOK(email) then
email := tmps;
tmps := getWNTS(snac, ofs);
if IsUpdateStrOK(city) then
city := tmps;
tmps := getWNTS(snac, ofs);
if IsUpdateStrOK(state) then
state := tmps;
tmps := getWNTS(snac, ofs);
if IsUpdateStrOK(regular) then
regular := tmps;
// skip 1
getWNTS(snac, ofs); // home fax
tmps := getWNTS(snac, ofs);
tmps := UnUTF(getWNTS(snac, ofs));
if IsUpdateStrOK(cellular) then
cellular := tmps;
SMSable := pos(' SMS', cellular) > 0;
if SMSable then
Delete(cellular, Length(cellular) - 3, 4);
tmps := getWNTS(snac, ofs);
tmps := getWNTS(snac, ofs);
if IsUpdateStrOK(country) then
country := tmps;
tmpb := readBYTE(snac, ofs);
if IsUpdateByteOK(GMThalfs) then
GMThalfs := tmpb;
readBYTE(snac, ofs); // authorization flag
readBYTE(snac, ofs); // webaware flag
readBYTE(snac, ofs); // direct connection permissions
// pPublicEmail := not boolean(readBYTE(snac, ofs));
pPublicEmail := boolean(readBYTE(snac, ofs));
if (displayed <> OldNick) and
not cont.CntIsLocal then
//SSI_UpdateContact(cont);
end;
if (flags and 1) = 0 then
NotifyListeners(IE_userinfo);
end;
9998: //META_MORE_USERINFO: // query result (homepage/more)
begin
inc(ofs, 1);
if Assigned(cont) then
with cont do
begin
infoUpdatedTo := now;
tmpw := readWORD(snac, ofs);
if IsUpdateWordOK(age) then
age := tmpw;
tmpb := readBYTE(snac, ofs);
if IsUpdateByteOK(gender) then
gender := tmpb;
tmps := getWNTS(snac, ofs);
y := readWORD(snac, ofs);
m := readBYTE(snac, ofs);
d := readBYTE(snac, ofs);
// skip birthday here, TryEncodeDate is +2 days off
{
if (y > 0) and (m > 0) and (d > 0) then
begin
if not SysUtils.TryEncodeDate(y, m, d, birth) then
birth := 0;
end else
birth := 0;
}
tmpb := readBYTE(snac, ofs);
if IsUpdateStrOK(lang[1]) then
lang[1] := tmps;
tmpb := readBYTE(snac, ofs);
if IsUpdateStrOK(lang[2]) then
lang[2] := tmps;
tmpb := readBYTE(snac, ofs);
if IsUpdateStrOK(lang[3]) then
lang[3] := tmps;
readWORD(snac, ofs); // unknown
tmps := getWNTS(snac, ofs); // original from: city string
tmps := getWNTS(snac, ofs); // original from: state string
tmps := getWNTS(snac, ofs); // original from: country code
tmpb := readBYTE(snac, ofs); // user Marital Status
if IsUpdateByteOK(MarStatus) then
MarStatus := tmpb;
if Equals(MyAccount) then
begin
inc(ofs, 4); // unknown
getWNTS(snac, ofs); // unknown
inc(ofs, 4); // unknown
inc(ofs, 4); // unknown
//AttachedLoginEmail := getWNTS(snac, ofs); //
end;
end;
if (flags and 1) = 0 then
NotifyListeners(IE_userinfo);
end;
9999: //META_WORK_USERINFO: // query result (work)
begin
inc(ofs, 1);
with cont do
begin
infoUpdatedTo:=now;
tmps := getWNTS(snac, ofs);
if IsUpdateStrOK(workphone) then
workphone := tmps;
readWORD(snac, ofs);
end;
if (flags and 1) = 0 then
NotifyListeners(IE_userinfo);
end;
9990: //META_INTERESTS_USERINFO: // Interests
begin
// if ord(snac[ofs+2]) = $A then
if readBYTE(snac, ofs) = $A then
with cont do
begin
// inc(ofs,3);
infoUpdatedTo := now;
Interests.Count := readBYTE(snac, ofs);
// SetLength(Interests.InterestBlock, Interests.Count);
// if Interests.Count > 0 then
for i := 0 to 3 do
begin
Interests.InterestBlock[i].Code := readWORD(snac, ofs);
// Interests.InterestBlock[i].Str := getWNTS(snac, ofs)
if i < Interests.Count then
sU := UnUTF(getWNTS(snac, ofs))
else
sU := '';
if (Interests.InterestBlock[i].Names <> nil)
and Assigned(Interests.InterestBlock[i].Names) then
Interests.InterestBlock[i].Names.Clear
else
Interests.InterestBlock[i].Names:=TStringList.Create;
while sU <> '' do
Interests.InterestBlock[i].Names.Add(chop(',',sU));
// Interests.InterestBlock[i].Count:=int.Count+1;
end;
// Interests[i].code := readWORD(snac, ofs);
// Interests[i].Str := getWNTS(snac, ofs);
end else
eventInt := -1;
if (flags and 1) = 0 then
NotifyListeners(IE_userinfo);
end;
9991: //META_UNREGISTER_ACK:
begin
eventContact := cont;
// NotifyListeners(IE_uinDeleted);
end;
9988: //META_SET_PASSWORD_ACK:
// if ord(snac[ofs+2])=$A then
if readBYTE(snac, ofs) = $A then
begin
// fPwd := waitingNewPwd;
// NotifyListeners(IE_pwdChanged);
end else
begin
eventError := EC_cantchangePwd;
NotifyListeners(IE_error);
end;
8888:
//META_SET_WORKINFO_ACK, META_SET_MOREINFO_ACK,
//META_SET_NOTES_ACK, META_SET_EMAILINFO_ACK,
//META_SET_FULLINFO_ACK: // acks to save-my-info
begin
inc(savingMyinfo.ACKcount);
if savingMyinfo.ACKcount = 4 then
begin
savingMyinfo.running := False;
// sendStatusCode(False); // needed(?) for the server to save publicemail
NotifyListeners(IE_myinfoACK);
end;
end;
// end;//case3
end; //case2
end; // 07DA
end;//case1
end; // parse1503
procedure TICQSession.OnProxyError(Sender : TObject; Error : Integer; const Msg : String);
begin
// if not isAva then
if error <> 0 then
begin
GoneOffline;
// eventInt:=WSocket_WSAGetLastError;
// if eventInt=0 then
eventInt:=error;
eventMsgA := msg;
eventError:=EC_cantconnect;
NotifyListeners(IE_error);
// exit;
end;
end;
function TICQSession.GetMyCaps: String;
var
s: RawByteString;
begin
// Result := String2Hex(CAPS_sm2big(CAPS_sm_UniqueID));
Result := String2Hex(CAPS_sm2big(CAPS_sm_Emoji));
Result := Result + ',' + String2Hex(CAPS_sm2big(CAPS_sm_MailNotify));
// Result := Result + ',' + String2Hex(CAPS_sm2big(CAPS_sm_IntroDlgStates)); // intro/tail messages
Result := Result + ',' + String2Hex(CAPS_sm2big(CAPS_sm_UTF8));
Result := Result + ',' + String2Hex(BigCapability[CAPS_big_Buzz].v);
if ShowClientID then
Result := Result + ',' + String2Hex(CapsCustomBuild) + IntToHex(RnQBuild, 4) + IntToHex(RnQBuildCustom, 4) + String2Hex(#00#00#00);
// Result := Result + ',' + String2Hex(BigCapability[CAPS_big_Build].v);
if SupportTypingNotif then
Result := Result + ',' + String2Hex(BigCapability[CAPS_big_MTN].v);
if AvatarsSupport then
Result := Result + ',' + String2Hex(CAPS_sm2big(CAPS_sm_Avatar));
// What are thoooooose?!
//Result := Result + ',' + '094613584C7F11D18222444553540000';
//Result := Result + ',' + '0946135C4C7F11D18222444553540000';
//Result := Result + ',' + '0946135E4C7F11D18222444553540000';
if UseCryptMsg then
begin
if fECCKeys.Generated then
begin
SetLength(s, 11);
CopyMemory(@s[1], @fECCKeys.pubEccKey[0], Length(s));
Result := Result + ',' + String2Hex('RDEC0' + s);
CopyMemory(@s[1], @fECCKeys.pubEccKey[11], Length(s));
Result := Result + ',' + String2Hex('RDEC1' + s);
CopyMemory(@s[1], @fECCKeys.pubEccKey[22], Length(s));
Result := Result + ',' + String2Hex('RDEC2' + s);
end;
Result := Result + ',' + String2Hex(BigCapability[CAPS_big_CryptMsg].v);
// Result := Result + ',' + String2Hex(BigCapability[CAPS_big_QIP_Secure].v); // QIP protect message
end;
// if (curXStatus > 0) and not (XStatusArray[curXStatus].pidOld = '') then
// Result := Result + ',' + String2Hex(XStatusArray[curXStatus].pidOld);
if AddExtCliCaps and (Length(ExtClientCaps) = 16) then
Result := Result + ',' + String2Hex(ExtClientCaps);
end;
function TICQSession.RemoveContact(c: TICQContact): Boolean;
var
IsLocal: Boolean;
begin
IsLocal := c.CntIsLocal or (c.Group = 0);
Result := NotInList.remove(c);
Result := fRoster.remove(c) or Result;
if Result then
begin
if not IsLocal and IsReady then
SendRemoveContact(c);
c.status := SC_UNK;
eventInt := TList(fRoster).Count;
NotifyListeners(IE_numOfContactsChanged);
end
end;
procedure TICQSession.RemoveContactFromServer(c: TICQContact);
begin
if IsReady then
SendRemoveContact(c);
eventContact := c;
NotifyListeners(IE_contactupdate);
end;
function TICQSession.SetStatusAndVis(st, vi: Byte; IsAuto: Boolean = False): Byte;
begin
if not IsAuto then
autoaway.triggered := TR_NONE;
Result := Byte(CurStatus);
if not (st in [Byte(SC_away), Byte(SC_na)]) then
ImAwaySince := 0
else if not (Byte(LastStatus) in [Byte(SC_away), Byte(SC_na)]) then
ImAwaySince := Now;
LastStatus := st;
if IsOffline and (st <> Byte(SC_OFFLINE)) then
DoConnect
else
DoSetStatus(st, vi);
if st = Byte(SC_OFFLINE) then
begin
StayConnected := False;
SetProgBar(Self, 0);
end;
RnQmain.UpdateStatusGlyphs;
end; // SetStatusAndVis
function TICQSession.SetStatus(st: Byte; IsAuto: Boolean = False): Byte;
begin
Result := SetStatusAndVis(st, GetVisibility, IsAuto);
end;
procedure TICQSession.SetVisibility(vi: Byte);
begin
if IsReady then
SetStatusAndVis(GetStatus, vi)
else
begin
if vi > Byte(High(TVisibility)) then
vi := 0;
Visibility := VI_normal;//TVisibility(vi);
RnQmain.UpdateStatusGlyphs;
end;
end; // SetVisibility
procedure TICQSession.DoSetStatus(st: Byte; vi: Byte);
begin
if st = Byte(SC_OFFLINE) then
begin
Disconnect;
Exit;
end;
if vi > Byte(High(TVisibility)) then
vi := 0;
if (st = Byte(CurStatus)) and (vi = Byte(Visibility)) then
Exit;
if not (st = Byte(CurStatus)) then
begin
eventOldStatus := CurStatus;
StartingStatus := TICQStatus(st);
end;
if not (vi = Byte(Visibility)) then
begin
eventOldInvisible := IsInvisible;
StartingVisibility := VI_normal;//TVisibility(vi);
end;
if IsReady then
begin
CurStatus := TICQStatus(st);
Visibility := VI_normal;//TVisibility(vi);
if SendPresenceState then
begin
eventContact := nil;
if not (eventOldStatus = CurStatus) then
NotifyListeners(IE_statuschanged);
if not (eventOldInvisible = IsInvisible) then
NotifyListeners(IE_visibilityChanged);
end; // else restore status and vis?
SendStatusStr(CurXStatus, ExtStsStrings[CurXStatus].Desc);
end else
Connect;
end; // SetStatus
function TICQSession.GetStatus: Byte;
begin
Result:= Byte(CurStatus)
end;
function TICQSession.GetXStatus: Byte;
begin
Result := CurXStatus;
end;
function TICQSession.GetStatusName(ForTray: Boolean = False): String;
begin
if (ForTray and XStatusInTray and (CurStatus = SC_ONLINE)) and (CurXStatus > 0) then
begin
if CurXStatusStr.Desc > '' then
Result := CurXStatusStr.Desc
else
Result := GetTranslation(Status2ShowStr[CurStatus])
end else
Result := GetTranslation(Status2ShowStr[CurStatus])
end;
function TICQSession.GetStatusImg(ForTray: Boolean = False): TPicName;
begin
if ForTray and XStatusInTray and (CurXStatus > 0) then
Result := XStatusArray[CurXStatus].PicName
else
Result := Status2ImgName(Byte(CurStatus), IsInvisible);
end;
function TICQSession.GetVisibility: Byte;
begin
Result := Byte(fVisibility)
end;
class function TICQSession._GetProtoName: string;
begin
Result := 'ICQ';
end;
function TICQSession.GetICQContact(const uid: TUID): TICQContact;
begin
Result := ContactsDB.Add(uid);
end;
function TICQSession.GetICQContact(const uin: Integer): TICQContact;
begin
Result := ContactsDB.Add(IntToStr(uin));
end;
class function TICQSession._IsProtoUid(const UID: TUID): Boolean; //Static;
begin
Result := _IsValidUid(UID) or _IsValidPhone(UID) or _IsValidMail(UID);
end;
class function TICQSession._IsValidUid(const UIN: TUID): Boolean;
var
k: Integer;
fUIN: Int64;
Temp: TUID;
begin
Result := False;
Temp := TICQContact.TrimUID(uin);
if Length(Temp) = 0 then
Exit;
Val(Temp, fUIN, k);
if k = 0 then
Result := True
end;
class function TICQSession._IsValidPhone(const Phone: TUID): Boolean;
var
k: Integer;
Temp: TUID;
begin
Result := True;
Temp := String(Phone).Trim();
if Length(Temp) = 0 then
Exit(False);
if not (Phone[1] = '+') then
Exit(False);
for k := 1 to Length(Phone) do
if not (Phone[k] in ['0'..'9','+']) then
Result := False;
end;
class function TICQSession._IsValidMail(const Mail: TUID): Boolean;
var
k: Integer;
Temp: TUID;
begin
Result := False;
Temp := String(Mail).Trim();
if Length(Temp) = 0 then
Exit;
for k := 1 to Length(Mail) do
if (Mail[k] in ['@']) then // Super basic :)
Result := True;
end;
procedure TICQSession.AddContactToCL(var c: TICQContact);
begin
if not Assigned(c) then
Exit;
if c.IsInRoster then
Exit;
// if IsReady then
// c.status := SC_OFFLINE
// else
// c.status := SC_UNK;
fRoster.add(c);
eventInt := TList(fRoster).count;
NotifyListeners(IE_numOfContactsChanged);
end;
procedure TICQSession.AddContactsToCL(cl: TRnQCList);
begin
if cl = nil then
Exit;
if TList(cl).count = 0 then
Exit;
cl := cl.clone.remove(fRoster);
if IsReady then
ICQCL_SetStatus(cl, SC_OFFLINE)
else
ICQCL_SetStatus(cl, SC_UNK);
fRoster.add(cl);
eventInt := TList(fRoster).count;
NotifyListeners(IE_numOfContactsChanged);
cl.Free;
end;
function TICQSession.AddContact(c: TICQContact; IsLocal: Boolean = False):boolean;
begin
Result := False;
if (c = nil) or (c.UID2cmp = '') then
Exit;
Result := fRoster.add(c);
Result := Result or (not IsLocal and c.CntIsLocal);
if Result then
begin
if IsReady then
begin
if c.Status = SC_UNK then
c.Status := SC_OFFLINE;
if not IsLocal then
begin
if c.IsInRoster then
SendAddContact(c)
else
c.CntIsLocal := True;
end;
end;
eventInt := TList(fRoster).Count;
NotifyListeners(IE_numOfContactsChanged);
end;
end; // AddContact
function TICQSession.ReadList(l: TLIST_TYPES): TRnQCList;
begin
case l of
LT_ROSTER: Result := fRoster;
LT_SPAM: Result := SpamList;
else
Result := nil;
end;
end;
procedure TICQSession.AddToList(l: TLIST_TYPES; cl: TRnQCList);
begin
case l of
LT_ROSTER: AddContactsToCL(cl);
end;
end;
{
procedure TICQSession.RemFromList(l: TLIST_TYPES; cl: TRnQCList);
begin
case l of
end;
end;
}
procedure TICQSession.AddToList(l: TLIST_TYPES; cnt: TICQContact);
begin
case l of
LT_ROSTER: AddContact(cnt);
LT_SPAM: Add2Ignore(cnt);
end;
end;
procedure TICQSession.RemFromList(l: TLIST_TYPES; cnt: TICQContact);
begin
case l of
LT_ROSTER: RemoveContact(cnt);
LT_SPAM: RemFromIgnore(cnt);
end;
end;
function TICQSession.IsInList(l: TLIST_TYPES; cnt: TICQContact): Boolean;
begin
case l of
LT_ROSTER: Result := fRoster.exists(cnt);
LT_SPAM: Result := SpamList.exists(cnt);
else
Result := False;
end;
end;
function TICQSession.Add2Ignore(c: TICQcontact): Boolean;
var
Query: UTF8String;
BaseURL: String;
begin
Result := False;
if IsReady then
begin
BaseURL := WIM_HOST + 'preference/setPermitDeny';
Query := '&pdIgnore=' + ParamEncode(String(c.UID2Cmp));
Result := SendSessionRequest(False, BaseURL, Query, 'Add to ignore list');
end;
end;
function TICQSession.RemFromIgnore(c: TICQcontact): Boolean;
var
Query: UTF8String;
BaseURL: String;
begin
Result := False;
if IsReady then
begin
BaseURL := WIM_HOST + 'preference/setPermitDeny';
Query := '&pdIgnoreRemove=' + ParamEncode(String(c.UID2Cmp));
Result := SendSessionRequest(False, BaseURL, Query, 'Remove from ignore list');
end;
end;
procedure TICQSession.GetPermitDeny;
var
JSON: TJSONObject;
Query: UTF8String;
BaseURL: String;
begin
if not IsReady then
Exit;
BaseURL := WIM_HOST + 'preference/getPermitDeny';
Query := '';
if SendSessionRequest(False, BaseURL, Query, RT_JSON, JSON, 'Get permit/deny lists') then
try
ProcessPermitDeny(JSON);
finally
JSON.Free;
end;
end;
procedure TICQSession.SetPermitDenyMode(const Mode: String);
var
JSON: TJSONObject;
Query: UTF8String;
BaseURL: String;
begin
if not IsReady then
Exit;
BaseURL := WIM_HOST + 'preference/setPermitDeny';
Query := '&pdMode=' + Mode;
if SendSessionRequest(False, BaseURL, Query, RT_JSON, JSON, 'Set permit/deny mode') then
try
//ProcessPermitDeny(JSON);
finally
JSON.Free;
end;
end;
procedure TICQSession.AddToBlock(const c: String); // Unused
var
Query: UTF8String;
BaseURL: String;
begin
if not IsReady then
Exit;
BaseURL := WIM_HOST + 'preference/setPermitDeny';
Query := '&pdBlock=' + ParamEncode(c);
SendSessionRequest(False, BaseURL, Query, 'Add contact to block list');
end;
procedure TICQSession.RemFromBlock(const c: String); // Unused
var
Query: UTF8String;
BaseURL: String;
begin
if IsReady then
begin
BaseURL := WIM_HOST + 'preference/setPermitDeny';
Query := '&pdBlockRemove=' + ParamEncode(c);
SendSessionRequest(False, BaseURL, Query, 'Remove contact from block list');
end;
end;
procedure TICQSession.SetMuted(c: TICQcontact; Mute: Boolean);
var
Query: UTF8String;
BaseURL: String;
begin
if IsReady then
begin
BaseURL := WIM_HOST + 'buddylist/Mute';
Query := '&buddy=' + c.UID2Cmp +
'&eternal=' + IfThen(Mute, '1', '0');
SendSessionRequest(False, BaseURL, Query, '(Un)mute contact');
end;
end;
function TICQSession.MaxCharsFor(const c: TICQContact; IsBin: Boolean = False): Integer;
begin
// TODO: Change for new proto
Result := 7000;
with c do
begin
if not IsBin then
Result := Result div 2;
if UseCryptMsg and (Crypt.SupportCryptMsg or (UseEccCryptMsg and fECCKeys.Generated and Crypt.SupportEcc)) then
Result := Result * 3 div 4;
end;
end; // MaxCharsFor
function TICQSession.ImVisibleTo(c: TICQContact): Boolean;
begin
Result := Visibility = VI_normal;
end; // ImVisibleTo
function TICQSession.GetLocalIP: Integer;
begin
try
Result := 0;
except
Result := 0;
end;
end;
function TICQSession.CreateNewGUID: String;
var
UID: TGUID;
begin
CreateGuid(UID);
Result := GUIDtoString(UID).Trim(['{', '}']).ToLower;
end;
function TICQSession.IsInvisible: Boolean;
begin
Result := fVisibility = VI_invisible;
end;
function TICQSession.AddRef(k: TRefKind; const uin: TUID): Integer;
begin
Result := SNACref;
Refs[SNACref].Kind := k;
Refs[SNACref].UID := uin;
Inc(SNACref);
if SNACref > maxRefs then
SNACref := 1;
end; // AddRef
function TICQSession.RequestPasswordIfNeeded(DoConnect: Boolean = True): Boolean;
begin
Result := False;
if not IsMobileAccount and RequiresLogin and ((fPwd = '') or (MyAccount = '')) then
begin
eventData := IfThen(DoConnect, '', 'pwdonly');
eventError := EC_missingLogin;
NotifyListeners(IE_error);
Result := True;
end;
end;
procedure TICQSession.Connect;
begin
if not IsOffline then
Exit;
if RequestPasswordIfNeeded then
Exit;
Phase := connecting_;
eventAddress := WIM_HOST;
NotifyListeners(IE_connecting);
SNACref := 1;
if StartSession then
AfterSessionStarted
else
GoneOffline;
end; // Connect
procedure TICQSession.AfterSessionStarted;
begin
StartPolling;
if LastStatus = Byte(SC_OFFLINE) then
DoSetStatus(Byte(SC_ONLINE), Byte(VI_normal))
else if not ExitFromAutoaway then
DoSetStatus(Byte(LastStatus), Byte(Visibility));
end;
function TICQSession.MakeParams(const Method, BaseURL: String; const Params: TDictionary; Sign: Boolean = True; DoublePercent: Boolean = False): String;
var
hash: String;
encparams: TStringList;
begin
encparams := TStringList.Create;
encparams.Sorted := True;
encparams.StrictDelimiter := True;
encparams.Delimiter := '&';
encparams.QuoteChar := #0;
with Params.GetEnumerator do
begin
while MoveNext do
encparams.Add(Current.Key + '=' + IfThen(Current.Key = 'stickerId', Current.Value, ParamEncode(Current.Value, DoublePercent)));
Free;
end;
Result := encparams.DelimitedText;
encparams.Free;
if Sign then
begin
hash := method + '&' + ParamEncode(BaseURL) + '&' + ParamEncode(Result);
Result := Result + '&sig_sha256=' + ParamEncode(HashString(fSessionKey, UTF(hash)));
end;
end;
procedure TICQSession.OpenICQURL(const URL: String);
var
BaseURL: String;
Params: TDictionary;
begin
//if fAuthToken = '' then
//begin
OpenURL(URL);
Exit;
//end;
BaseURL := 'https://www.icq.com/karma_api/karma_client2web_login.php';
Params := TDictionary.Create();
Params.Add('ts', IntToStr(DateTimeToUnix(Now, False) - fHostOffset));
Params.Add('owner', MyAccNum);
Params.Add('a', fAuthToken);
Params.Add('k', fDevId);
Params.Add('d', URL);
OpenURL(BaseURL + '?' + MakeParams('GET', BaseURL, Params));
Params.Free;
end;
function TICQSession.ClientLogin: Boolean;
var
JSON: TJSONObject;
Query: UTF8String;
BaseURL, TransId, SMSCode: String;
ErrHandler: TErrorProc;
begin
Result := False;
if (MyAccNum = '') or (not IsMobileAccount and (fPwd = '')) then
Exit;
Phase := login_;
NotifyListeners(IE_loggin);
if IsMobileAccount then
begin
fSessionKey := '';
BaseURL := SMS_REG + 'requestPhoneValidation.php';
Query := 'locale=ru' +
'&msisdn=' + ParamEncode(MyAccNum) +
'&smsFormatType=human' +
'&k=' + fDevId +
'&r=' + CreateNewGUID;
ErrHandler := procedure(Resp: TPair)
begin
ResetSession;
eventInt := Resp.Key;
eventMsgA := GetTranslation(Resp.Value);
if Resp.Key = Integer(EAC_Unknown) then
eventError := EC_Login_Seq_Failed
else
eventError := EC_other;
NotifyListeners(IE_error);
end;
if SendRequest(False, BaseURL, Query, RT_JSON, JSON, 'Request SMS code', '', ErrHandler) then
try
JSON.GetValueSafe('trans_id', TransId);
SMSCode := '';
InputQuery(GetTranslation('Phone login'), GetTranslation('Enter SMS code'), SMSCode);
if (Trim(SMSCode) = '') or not IsOnlyDigits(SMSCode) then
begin
ResetSession;
Exit;
end;
BaseURL := SMS_REG + 'loginWithPhoneNumber.php';
Query := 'f=json' +
'&locale=ru' +
'&msisdn=' + ParamEncode(MyAccNum) +
'&trans_id=' + ParamEncode(TransId) +
'&sms_code=' + Trim(SMSCode) +
'&create_account=1' +
'&k=' + fDevId +
'&r=' + CreateNewGUID;
if SendRequest(False, BaseURL, Query, RT_JSON, JSON, 'Login using phone and create auth data', '', ErrHandler) then
begin
Result := True;
JSON.GetValueSafe('sessionKey', fSessionKey);
fAuthTokenTime := DateTimeToUnix(Now, False);
if not (JSON.GetValue('hostTime') = nil) then
fHostOffset := DateTimeToUnix(Now, False) - StrToInt(JSON.GetValue('hostTime').Value)
else
fHostOffset := 0;
if not (JSON.GetValue('token') = nil) then
with JSON.GetValue('token') do
begin
GetValueSafe('a', fAuthToken);
GetValueSafe('expiresIn', fAuthTokenExpIn);
end;
end;
finally
FreeAndNil(JSON);
end;
end
else
begin
BaseURL := LOGIN_HOST + 'auth/clientLogin';
Query := 'f=json' +
'&clientName=' + ParamEncode(IfThen(ShowClientID, 'R&Q', 'Mail.ru Windows ICQ')) +
'&clientVersion=' + IfThen(ShowClientID, '0.11.9999.' + IntToStr(RnQBuild) + 'v' + IntToStr(RnQBuildCustom), '10.0.12393') +
'&devId=' + fDevId +
'&tokenType=longterm' +
'&s=' + ParamEncode(String(MyAccNum)) +
'&pwd=' + ParamEncode(fPwd);
ErrHandler := procedure(Resp: TPair)
begin
ResetSession;
eventInt := Resp.Key;
eventMsgA := GetTranslation(Resp.Value);
if Resp.Key = Integer(EAC_Unknown) then
eventError := EC_Login_Seq_Failed
else if Resp.Key = Integer(EAC_Wrong_Login) then
eventError := EC_badPwd
else
eventError := EC_other;
NotifyListeners(IE_error);
end;
if SendRequest(True, BaseURL, Query, RT_JSON, JSON, 'Login using UIN and create auth data', '', ErrHandler) then
try
Result := True;
JSON.GetValueSafe('sessionSecret', fSessionSecret);
fAuthTokenTime := DateTimeToUnix(Now, False);
if not (JSON.GetValue('hostTime') = nil) then
fHostOffset := DateTimeToUnix(Now, False) - StrToInt(JSON.GetValue('hostTime').Value)
else
fHostOffset := 0;
if not (JSON.GetValue('token') = nil) then
with JSON.GetValue('token') do
begin
GetValueSafe('a', fAuthToken);
GetValueSafe('expiresIn', fAuthTokenExpIn);
end;
finally
FreeAndNil(JSON);
end;
end;
{
BaseURL := 'https://icq.com/siteim/icqbar/php/proxy_jsonp_connect.php';
query := 'username=' + String(MyAccNum) + '&password=' + fPwd + '&time=' + IntToStr(DateTimeToUnix(Now, False)) + '&remember=1';
LoggaICQPkt('[POST] Login and create session', WL_sent_text, BaseURL + '?' + query);
// LoggaICQPkt('[POST] Login and create session', WL_sent_text, 'https://icq.com/siteim/icqbar/php/proxy_jsonp_connect.php?[...]');
fs := TMemoryStream.Create;
LoadFromUrl(BaseURL, fs, 0, False, True, query);
SetLength(session, fs.Size);
fs.ReadBuffer(session[1], fs.Size);
fs.Clear;
LoggaICQPkt('[POST] Login and create session', WL_rcvd_text, session);
try
json := TJSONObject.ParseJSONValue(TEncoding.UTF8.GetBytes(session)) as TJSONObject;
if Assigned(json) then
if json.GetValue('statusCode').Value = '200' then
begin
fDevId := json.GetValue('k').Value;
fSessionKey := json.GetValue('sessionKey').Value;
fSessionToken := json.GetValue('a').Value;
fSessionTokenTime := StrToInt(json.GetValue('ts').Value);
if not (json.GetValue('tsDelta') = nil) then
fHostOffset := StrToInt(json.GetValue('tsDelta').Value)
else
fHostOffset := 0;
end;
FreeAndNil(json);
}
if IsMobileAccount then
begin
if fSessionKey = '' then
begin
ODS('Not enough data to login!');
Exit;
end;
end
else
begin
if (getPwd = '') or (fSessionSecret = '') or (fAuthToken = '') then
begin
ODS('Not enough data to login!');
Exit;
end;
fSessionKey := HashString(UTF(getPwd), UTF(fSessionSecret));
end;
end;
function TICQSession.StartSession: Boolean;
var
Query: UTF8String;
ts, Code: Integer;
Hash, BaseURL, RespStr, UnixTime, AutoCaps: String;
Params: TDictionary;
JSON: TJSONObject;
UsingSaved, Relogin, SeqFailed, ProcResult: Boolean;
UID: TGUID;
ErrHandler: TErrorProc;
begin
Result := False;
ProcResult := False;
UsingSaved := True;
SeqFailed := False;
Relogin := False;
if RequiresLogin then
begin
UsingSaved := False;
if not ClientLogin then
Exit;
end else
ODS('Using saved token & key!');
if RequiresLogin and not UsingSaved then
begin
eventInt := Integer(EAC_Not_Enough_Data);
eventMsgA := '';
eventError := EC_other;
NotifyListeners(IE_error);
Exit;
end;
// Start session
BaseURL := WIM_HOST + 'aim/startSession';
UnixTime := IntToStr(DateTimeToUnix(Now, False) - fHostOffset);
//AutoCaps := '8eec67ce70d041009409a7c1602a5c84';
AutoCaps := '';
Params := TDictionary.Create();
Params.Add('f', 'json');
Params.Add('k', fDevId);
Params.Add('a', fAuthToken);
Params.Add('clientName', IfThen(ShowClientID, 'R&Q', 'Mail.ru Windows ICQ'));
Params.Add('clientVersion', IfThen(ShowClientID, IntToStr(RnQBuild) + 'v' + IntToStr(RnQBuildCustom), '5000'));
Params.Add('majorVersion', IfThen(ShowClientID, '0', '100'));
Params.Add('minorVersion', IfThen(ShowClientID, '11', '0'));
Params.Add('buildNumber', IfThen(ShowClientID, '9999', '12417'));
Params.Add('pointVersion', IfThen(ShowClientID, IntToStr(RnQBuild), '0'));
Params.Add('assertCaps', GetMyCaps);
Params.Add('interestCaps', AutoCaps);
Params.Add('ts', UnixTime);
Params.Add('imf', 'plain');
Params.Add('invisible', IfThen(Visibility = VI_invisible, 'true', 'false'));
Params.Add('inactiveView', 'offline');
// Full invisibility is not working, "offline" presence event is still being sent to others when starting/ending session
Params.Add('view', IfThen(Visibility = VI_invisible, 'invisible', 'online'));
Params.Add('activeTimeout', '180');
Params.Add('mobile', '0');
Params.Add('rawMsg', '0');
Params.Add('language', IfThen(IsRuLang, 'ru-ru', 'en-us'));
Params.Add('deviceId', 'dev1');
Params.Add('sessionTimeout', '7776000'); // 90 days
Params.Add('events', 'myInfo,presence,buddylist,typing,dataIM,userAddedToBuddyList,service,webrtcMsg,mchat,hist,hiddenChat,diff,permitDeny,imState,notification,apps' + ',offlineIM,sentIM,alert');
Params.Add('includePresenceFields', AllFieldsAsParam);
// Params.Add('nonce', UnixTime + '-' + nonce);
ErrHandler := procedure(Resp: TPair)
begin
if ((Resp.Key = Integer(EAC_Auth_Required)) or (Resp.Key = Integer(EAC_Wrong_DevKey))) and UsingSaved then
begin
Relogin := True;
ResetSession;
RequestPasswordIfNeeded(False);
ProcResult := StartSession;
end else
begin
SeqFailed := True;
eventInt := Resp.Key;
eventMsgA := GetTranslation(Resp.Value);
if Resp.Key = Integer(EAC_Unknown) then
eventError := EC_Login_Seq_Failed
else
eventError := EC_other;
NotifyListeners(IE_error);
end;
end;
if SendRequest(True, BaseURL, MakeParams('POST', BaseURL, Params), RT_JSON, JSON, 'Start session', '', ErrHandler) then
try
JSON.GetValueSafe('aimsid', fAimSid);
JSON.GetValueSafe('fetchBaseURL', fFetchBaseURL);
JSON.GetValueSafe('ts', ts);
LastFetchBaseURL := fFetchBaseURL;
fHostOffset := DateTimeToUnix(Now, False) - ts;
ProcResult := True;
finally
FreeAndNil(JSON);
end;
Result := ProcResult;
Params.Free;
if Relogin then
Exit;
if SeqFailed then
begin
ResetSession;
Exit;
end;
Phase := settingup_;
NotifyListeners(IE_connected);
if Result then
begin
BaseURL := WIM_HOST + 'timezone/set';
Query := '&TimeZoneOffset=' + IntToStr(DateTimeToUnix(Now, True) - (DateTimeToUnix(Now, False) + fHostOffset));
SendSessionRequest(False, BaseURL, Query, 'Set timezone');
end;
NotifyListeners(IE_almostOnline);
// REST token
BaseURL := REST_HOST + 'genToken';
UnixTime := IntToStr(DateTimeToUnix(Now, False) - fHostOffset);
Params := TDictionary.Create();
Params.Add('a', fAuthToken);
Params.Add('k', fDevId);
Params.Add('ts', UnixTime);
SendRequest(True, BaseURL, MakeParams('POST', BaseURL, Params), RT_JSON, JSON, 'Generate REST auth token', '');
if Assigned(JSON) then
try
if JSON.GetValue('results') = nil then
begin
fRESTToken := '';
MsgDlg('Failed to get REST auth token', True, mtError);
//TJSONObject(JSON.GetValue('status')).GetValue('reason').Value
end else
fRESTToken := TJSONObject(JSON.GetValue('results')).GetValue('authToken').Value;
finally
FreeAndNil(JSON);
end;
Params.Free;
// REST client id
if not (fRESTToken = '') then
begin
BaseURL := REST_HOST + 'addClient';
UnixTime := IntToStr(DateTimeToUnix(Now, False) - fHostOffset);
Params := TDictionary.Create();
Params.Add('a', fAuthToken);
Params.Add('f', 'json');
Params.Add('k', fDevId);
Params.Add('ts', UnixTime);
Params.Add('client', 'icq');
Params.Add('lang', IfThen(IsRuLang, 'ru-ru', 'en-us'));
Params.Add('reqId', CreateNewGUID);
Params.Add('authToken', fRESTToken);
SendRequest(True, BaseURL, MakeParams('POST', BaseURL, Params), RT_JSON, JSON, 'Get REST client id', '');
if Assigned(JSON) then
try
TJSONObject(JSON.GetValue('status')).GetValue('code').TryGetValue(Code);
if not (Code = 20000) or (JSON.GetValue('results') = nil) then
begin
fRESTClientId := '';
MsgDlg('Failed to get REST client id', True, mtError);
//TJSONObject(JSON.GetValue('status')).GetValue('reason').Value
end else
fRESTClientId := TJSONObject(JSON.GetValue('results')).GetValue('clientId').Value;
//"appStamp" : "jySC-tr4EKsnOb6gUIl-f6z4d6QxmGwntxpEJg6Aj_k=",
finally
FreeAndNil(JSON);
end;
Params.Free;
end;
end;
function TICQSession.PingSession: Boolean;
var
JSON: TJSONObject;
Query: UTF8String;
BaseURL: String;
ts: Integer;
begin
Result := False;
BaseURL := WIM_HOST + 'aim/pingSession';
Query := '&k=' + fDevId;
Result := SendSessionRequest(False, BaseURL, Query, RT_JSON, JSON, 'Restore session');
if Result then
try
JSON.GetValueSafe('aimsid', fAimSid);
JSON.GetValueSafe('fetchBaseURL', fFetchBaseURL);
JSON.GetValueSafe('ts', ts);
LastFetchBaseURL := fFetchBaseURL;
fHostOffset := DateTimeToUnix(Now, False) - ts;
AfterSessionStarted;
finally
JSON.Free;
end;
end;
procedure TICQSession.ResetSession;
begin
fSessionSecret := '';
fSessionKey := '';
fAuthToken := '';
fAuthTokenTime := 0;
fAuthTokenExpIn := 0;
fHostOffset := 0;
end;
procedure TICQSession.EndSession(EndToken: Boolean = False);
var
Query: UTF8String;
BaseURL: String;
begin
BaseURL := WIM_HOST + 'aim/endSession';
Query := IfThen(EndToken, '&invalidateToken=1');
SendSessionRequest(False, BaseURL, Query, 'End current session');
GoneOffline;
if EndToken then
ResetSession;
end;
procedure TICQSession.PollError(const ExtraError: String = ''; Silent: Boolean = False);
begin
if CleanDisconnect then
Exit;
Inc(FatalErrorCount);
if (not Silent) then
MsgDlg(GetTranslation('Failed to start listening for events, waiting %d sec before retry...', [Round(ICQErrorReconnectDelay / 1000)]) +
IfThen(ExtraError = '', '', #13#10 + '[' + ExtraError + ']'), False, mtError);
if (FatalErrorCount > 5) then
begin
FatalErrorCount := 0;
LoggaICQPkt('', WL_disconnected, 'Encountered unrecoverable error, disconnecting...');
EndSession;
Exit;
end;
TTask.Create(procedure
begin
Sleep(ICQErrorReconnectDelay);
TThread.Synchronize(nil, procedure
begin
if not Running then
Exit;
// Try to use existing session, get new initial fetch url and start polling again. Go offline if all fails.
if not PingSession then
EndSession;
end);
end).Start;
end;
procedure TICQSession.StartPolling;
var
BaseURL: String;
begin
BaseURL := fFetchBaseURL.TrimRight(['/']);
if (pos('?', BaseURL) = 0) then
BaseURL := BaseURL + '?'
else
BaseURL := BaseURL + '&';
BaseURL := BaseURL + 'f=json&r=' + CreateNewGUID + '&timeout=60000&peek=0';
Inc(ReqId);
LoggaICQPkt('[GET] Event fetch loop started', WL_sent_text, BaseURL);
PollURL(BaseURL);
Phase := online_;
end;
procedure TICQSession.AbortPolling(Sender: TObject);
begin
HttpPoll.Abort;
end;
procedure TICQSession.PollURL(const URL: String);
begin
if not Running then
Exit;
if not Assigned(HttpPoll) or (URL = '') then
begin
PollError('ERR_UNASSIGNED');
Exit;
end;
HttpPoll.URL := URL;
SetupProxy(HttpPoll);
ExecTime := DateTimeToUnix(Now, False);
try
HttpPoll.GetAsync;
Timeout.Enabled := True;
except
on E: OverbyteIcsHttpProt.EHttpException do
begin
if E.ErrorCode = httperrBusy then
HttpPoll.Abort;
HandleError(E, URL, '', False);
PollError('ERR_GETFAIL');
end;
end;
end;
procedure TICQSession.PollRequestDone(Sender: TObject; RqType: THttpRequest; ErrCode: Word);
var
t, ts, code: Integer;
RespStr, SReqID: String;
Resp: TPair;
JSON: TJSONObject;
event, etype, edata: TJSONValue;
events: TJSONArray;
function UnixTimeInMs: Int64;
var
ST: SystemTime;
DT: TDateTime;
begin
Windows.GetSystemTime(ST);
DT := SysUtils.EncodeDate(ST.wYear, ST.wMonth, ST.wDay) +
SysUtils.EncodeTime(ST.wHour, ST.wMinute, ST.wSecond, ST.wMilliseconds);
Result := DateUtils.MilliSecondsBetween(DT, UnixDateDelta);
end;
var
Freq, StartCount, StopCount: Int64;
TimingSeconds: Real;
begin
Timeout.Enabled := False;
if not Assigned(Sender) then
begin
PollError('ERR_NOSENDER');
Exit;
end;
with Sender as TSslHttpCli do
begin
if Assigned(SendStream) then
SendStream.Free;
RespStr := PollStream.DataString;
PollStream.Clear;
end;
// Abort and request fetch URL again every <60 sec to stay online
if ErrCode = httperrAborted then
begin
RestartPolling(1000);
Exit;
end;
// 5 sec delay after HTTP error
if not (HttpPoll.StatusCode = 200) then
begin
if not (HttpPoll.StatusCode = 0) then
MsgDlg('Fetch event bad code: ' + IntToStr(HttpPoll.StatusCode) + #13#10 + 'Header: ' + HttpPoll.RcvdHeader.Text + #13#10 + 'Response: ' + RespStr, False, mtInformation);
if (HttpPoll.StatusCode >= 500) and (HttpPoll.StatusCode < 600) then
RestartPolling(ICQErrorReconnectDelay)
else
PollError('ERR_HTTPCODE', HttpPoll.StatusCode = 0);
Exit;
end;
// if (RespStr = '') and (HttpPoll.ContentLength > 0) then
// begin
// if HttpPoll.State = httpWaitingBody then
// Exit;
// end;
if Trim(RespStr) = '' then
begin
PollError('ERR_EMPTYRESP');
Exit;
end;
ts := 0;
JSON := nil;
try
eventNameA := '[POST] Fetched new events';
eventData := RespStr;
NotifyListeners(IE_serverSent);
LastFetchBaseURL := '';
if not ParseJSON(RespStr, JSON) then
begin
PollError('ERR_NOTAJSON');
Exit;
end;
FatalErrorCount := 0;
Resp := CheckResponseData(JSON, SReqID);
if Resp.Key = Integer(EAC_OK) then
if not (JSON.GetValue('fetchBaseURL') = nil) then
begin
JSON.GetValueSafe('fetchBaseURL', LastFetchBaseURL);
JSON.GetValueSafe('fetchTimeout', t);
t := Max(60, t);
Timeout.Interval := (t - (2 + Random(3))) * 1000;
JSON.GetValueSafe('timeToNextFetch', t);
JSON.GetValueSafe('ts', ts);
fHostOffset := DateTimeToUnix(Now, False) - ts;
// ODS('exec = ' + IntToStr(ExecTime));
// ODS('next url = ' + LastFetchBaseURL);
// ODS('timeToNextFetch = ' + IntToStr(t));
events := JSON.GetValue('events') as TJSONArray;
for event in events do
if Assigned(event) and (event is TJSONObject) then
begin
etype := TJSONObject(event).GetValue('type');
edata := TJSONObject(event).GetValue('eventData');
if Assigned(etype) and Assigned(edata) then
if etype.Value = 'buddylist' then
begin
QueryPerformanceFrequency(Freq);
QueryPerformanceCounter(StartCount);
ProcessContactList(TJSONObject(edata).GetValue('groups') as TJSONArray, True);
QueryPerformanceCounter(StopCount);
TimingSeconds := (StopCount - StartCount) / Freq;
ODS('Populating CL: ' + floattostr(TimingSeconds));
// Get caps of users currently online
GetAllCaps;
NotifyListeners(IE_online);
// Get own profile's settings
GetProfile(MyAccNum);
end else if (etype.Value = 'presence') or (etype.Value = 'myInfo') then
ProcessContact(TJSONObject(edata))
else if etype.Value = 'histDlgState' then
ProcessDialogState(TJSONObject(edata))
else if etype.Value = 'imState' then
ProcessIMState(TJSONObject(edata))
else if etype.Value = 'typing' then
ProcessTyping(TJSONObject(edata))
else if etype.Value = 'userAddedToBuddyList' then
ProcessAddedYou(TJSONObject(edata))
else if etype.Value = 'permitDeny' then
ProcessPermitDeny(TJSONObject(edata))
else if etype.Value = 'diff' then
begin
// TJSONArray(edata)
end else
ODS('Unhandled event type: ' + etype.Value);
end;
end
else // Events that do not continue events fetching
begin
LastFetchBaseURL := '';
events := json.GetValue('events') as TJSONArray;
for event in events do
if Assigned(event) and (event is TJSONObject) then
begin
etype := TJSONObject(event).GetValue('type');
edata := TJSONObject(event).GetValue('eventData');
if Assigned(etype) then
if etype.Value = 'sessionEnded' then
begin
if Assigned(edata) and (edata is TJSONObject) then
if edata.GetValueSafe('endCode', code) then
if (code = 142) or // "offReason" : "Killed Sessions"
(code = 26) then // "offReason" : "User Initiated Bump"
CleanDisconnect := True;
GoneOffline;
end;
end;
end;
finally
JSON.Free;
end;
RestartPolling(t);
end;
procedure TICQSession.RestartPolling(Delay: Integer = 1);
begin
if (LastFetchBaseURL = '') then
PollError('ERR_UNCLEAN')
else
TTask.Create(procedure
begin
Sleep(Max(100, Delay)); // Min 100ms between fetches, just in case :)
TThread.Synchronize(nil, procedure
begin
if not Running then
Exit;
PollURL(LastFetchBaseURL);
end);
end).Start;
end;
procedure TICQSession.ProcessContactList(const CL: TJSONArray; Batch: Boolean = False);
var
buddy, group: TJSONValue;
buddies: TJSONArray;
id: Integer;
name: String;
begin
if not Assigned(CL) then
Exit;
if Batch then
building := True;
try
fRoster.ForEach(procedure(cnt: TICQContact)
begin
cnt.CntIsLocal := True;
end);
groups.MakeAllLocal;
for group in CL do
if Assigned(Group) and (Group is TJSONObject) then
begin
group.GetValueSafe('name', name);
group.GetValueSafe('id', id);
if groups.Exists(id) then
groups.RenameLocal(id, name)
else
groups.AddWithValues(id, name);
groups.SetLocal(id, False);
buddies := TJSONObject(group).GetValue('buddies') as TJSONArray;
for buddy in buddies do
if Assigned(buddy) then
ProcessContact(TJSONObject(buddy), id, True);
end;
except end;
if Batch then
building := False;
end;
function TICQSession.ProcessContact(const Buddy: TJSONObject; GroupToAddTo: Integer = -1; Batch: Boolean = False): TICQContact;
var
i, Mute: Integer;
FoundCap: Boolean;
Tmp, Phone1, Phone2, Phone3, PhoneType, OldXStatusStr: String;
OldPic: TPicName;
TheCap, TheCap2: RawByteString;
UnixTime: Integer;
NewStatus: TICQstatus;
Profile, TmpObj: TJSONObject;
Cap, Ph, TmpArr: TJSONValue;
Caps: TJSONArray;
begin
Result := nil;
if not Assigned(Buddy) then
Exit;
Result := GetICQContact(Buddy.GetValue('aimId').Value);
if not Assigned(Result) then
Exit;
// if Buddy.GetValueSafe('abContactName', Name) then
// if not (Name = '') then
// Result.nick := Name
if (Result.nick = '') then
begin
if Buddy.GetValueSafe('displayId', Tmp) then
if not (Tmp = '') then
Result.nick := Tmp;
if Buddy.GetValueSafe('friendly', Tmp) then
if not (Tmp = '') then
Result.nick := Tmp;
end;
if Buddy.GetValueSafe('emailId', Tmp) then
if not TryStrToInt(Tmp, i) then
Result.Email := Tmp;
// "abPhones" array - more phones, especially for CT_SMS contacts
// Buddy.GetValueSafe('abPhoneNumber', Phone1);
if Buddy.GetValueSafe('cellNumber', Tmp) then
Result.Cellular := Tmp;
if Buddy.GetValueSafe('phoneNumber', Tmp) then
Result.Regular := Tmp;
if Buddy.GetValueSafe('smsNumber', Tmp) then
Result.SMSMobile := Tmp;
Result.SMSable := not (Result.SMSMobile = '');
if Buddy.GetValueSafe('workNumber', Tmp) then
Result.Workphone := Tmp;
// otherNumber
if Buddy.GetValueSafe('official', i) then
Result.Official := i = 1;
if Buddy.GetValueSafe('deleted', i) then
Result.Deleted := i = 1;
Buddy.GetValueSafe('userType', Tmp);
Result.UserType := CT_UNK;
if Tmp = 'sms' then
begin
Result.UserType := CT_SMS;
Result.SMSable := True;
end else if Tmp = 'phone' then
Result.UserType := CT_PHONE
else if Tmp = 'icq' then
Result.UserType := CT_ICQ
else if Tmp = 'oldIcq' then
Result.UserType := CT_OLDICQ;
//Other possible types:
//aim - AIM or AOL
//interop - Gatewayed from another network
//imserv - IMServ group target
if not (Buddy.GetValue('capabilities') = nil) then
begin
Result.LastCapsUpdate := Now;
Result.CapabilitiesSm := [];
Result.CapabilitiesBig := [];
Result.ExtraCapabilities := '';
Caps := Buddy.GetValue('capabilities') as TJSONArray;
if Assigned(Caps) then
for Cap in Caps do
if Assigned(Cap) then
begin
TheCap := Hex2String(Cap.Value);
FoundCap := False;
for i := 1 to Length(BigCapability) do
if TheCap = BigCapability[i].v then
begin
Include(Result.CapabilitiesBig, i);
FoundCap := True;
Break;
end;
if Copy(TheCap, 1, 2) = CapsMakeBig1 then
if Copy(TheCap, 5, 12) = CapsMakeBig2 then
begin
TheCap2 := Copy(TheCap, 3, 2);
for i := 1 to Length(CapsSmall) do
if TheCap2 = CapsSmall[i].v then
begin
Include(Result.CapabilitiesSm, i);
FoundCap := True;
Break;
end;
end;
if not FoundCap then
Result.ExtraCapabilities := Result.ExtraCapabilities + TheCap;
end;
Result.Crypt.SupportCryptMsg := CAPS_big_CryptMsg in Result.CapabilitiesBig;
Result.Crypt.SupportEcc := False;
i := Pos('RDEC0', Result.ExtraCapabilities);
if i > 0 then
begin
Result.Crypt.EccPubKey := Copy(Result.ExtraCapabilities, i + 5, 11);
i := Pos('RDEC1', Result.ExtraCapabilities);
if i > 0 then
begin
Result.Crypt.EccPubKey := Result.Crypt.EccPubKey + Copy(Result.ExtraCapabilities, i + 5, 11);
i := Pos('RDEC2', Result.ExtraCapabilities);
if i > 0 then
begin
Result.Crypt.EccPubKey := Result.Crypt.EccPubKey + Copy(Result.ExtraCapabilities, i + 5, 11);
Result.Crypt.SupportEcc := Length(Result.Crypt.EccPubKey) = 33;
if Result.Crypt.SupportEcc and fECCKeys.generated then
begin
SetLength(Result.Crypt.EccMsgKey, SizeOf(TECCSecretKey));
if not ecdh_shared_secret(PECCPublicKey(Result.Crypt.EccPubKey)^, fECCKeys.PrivKey, PECCSecretKey(Result.Crypt.EccMsgKey)^) then
begin
Result.Crypt.EccMsgKey := '';
Result.Crypt.SupportEcc := False;
end;
end;
end;
end;
end;
Result.Typing.bSupport := CAPS_big_MTN in Result.CapabilitiesBig;
end;
Buddy.GetValueSafe('state', Tmp);
if Tmp = 'online' then
NewStatus := SC_ONLINE
else
NewStatus := SC_OFFLINE;
if Buddy.GetValueSafe('lastseen', UnixTime) then
if not (UnixTime = 0) then
Result.LastTimeSeenOnline := UnixToDateTime(UnixTime, False);
Result.NoClient := False;
if Buddy.GetValueSafe('onlineTime', UnixTime) then
begin
Result.OnlineTime := UnixTime;
if (NewStatus = SC_OFFLINE) and (UnixTime > 0) then
begin
Result.NoClient := True;
Result.ClientClosed := Now - UnixTime * DTseconds;
end;
end;
if Buddy.GetValueSafe('idleTime', UnixTime) then
Result.IdleTime := UnixTime;
if Buddy.GetValueSafe('statusTime', UnixTime) then
Result.LastStatusUpdate := UnixToDateTime(UnixTime, False);
// if Buddy.GetValueSafe('awayTime', UnixTime) then
// Result.AwayTime := UnixTime;
if Buddy.GetValueSafe('memberSince', UnixTime) then
begin
Result.MemberSince := UnixToDateTime(UnixTime);
if NewStatus = SC_OFFLINE then
Result.NoClient := True;
end;
if Buddy.GetValueSafe('mute', Mute) then
Result.Muted := Mute > 0
else
Result.Muted := False;
// awayMsg, profileMsg - ?
try
OldXStatusStr := Result.xStatusStr;
if Buddy.GetValueSafe('statusMsg', Tmp) then
Result.xStatusStr := HTMLEntitiesDecode(Tmp);
if (Result.xStatusStr = '') and Buddy.GetValueSafe('moodTitle', Tmp) then
Result.xStatusStr := HTMLEntitiesDecode(Tmp);
//XStatusArray[curXStatus].pid6
except
// Cannot decode HTML for some reason
end;
// Owner only
if Result.UID2cmp = MyAccNum then
begin
if Buddy.GetValueSafe('attachedPhoneNumber', Tmp) then
if not (Tmp = '') then
AttachedLoginPhone := Tmp;
end;
Profile := TJSONObject(Buddy.GetValue('profile'));
if Assigned(Profile) then
with Profile do
begin
GetValueSafe('firstName', Result.first);
GetValueSafe('lastName', Result.last);
if GetValueSafe('nick', Tmp) then
begin
if not (Tmp = '') then
Result.Nick := Tmp;
end else if GetValueSafe('friendlyName', Tmp) then
if not (Tmp = '') then
Result.Nick := Tmp;
if GetValueSafe('gender', Tmp) then
Result.Gender := SrvStrToGenderI(Tmp)
else
Result.Gender := 0;
// Not there in new proto
if GetValueSafe('relationshipStatus', Tmp) then
Result.MarStatus := SrvStrToMarStI(Tmp)
else
Result.MarStatus := $0000;
if GetValueSafe('birthDate', Tmp) then
if TryStrToInt(Tmp, UnixTime) then
if UnixTime < 0 then
Result.Birth := 0
else
Result.Birth := UnixToDateTime(UnixTime, True);
TmpArr := Profile.GetValue('homeAddress');
if Assigned(TmpArr) and (TmpArr is TJSONArray) and (TJSONArray(TmpArr).Count > 0) then
begin
TmpObj := TJSONObject(TJSONArray(TmpArr).Get(0));
TmpObj.GetValueSafe('country', Result.Country);
TmpObj.GetValueSafe('state', Result.State);
TmpObj.GetValueSafe('city', Result.City);
end;
// Not there in new proto
GetValueSafe('lang1', Result.Lang[1]);
GetValueSafe('lang2', Result.Lang[2]);
GetValueSafe('lang3', Result.Lang[3]);
TmpArr := Profile.GetValue('phones');
if Assigned(TmpArr) and (TmpArr is TJSONArray) and (TJSONArray(TmpArr).Count > 0) then
for Ph in TJSONArray(TmpArr) do
if Assigned(Ph) then
begin
Ph.GetValueSafe('type', PhoneType);
Ph.GetValueSafe('phone', Tmp);
if not (Tmp = '') then
begin
if PhoneType = 'home' then
Result.Regular := Tmp
else if PhoneType = 'mobile' then
Result.Cellular := Tmp
else if PhoneType = 'work' then
Result.WorkPhone := Tmp
else if PhoneType = 'other' then
Result.OtherPhone := Tmp
end;
end;
if GetValueSafe('tz', Tmp) then // Minutes from GMT?..
if TryStrToInt(Tmp, i) then
Result.GMThalfs := SmallInt(0); // 0 for now
GetValueSafe('aboutMe', Result.About);
GetValueSafe('statusLine', Result.LifeStatus);
// Possible fields: jobs[], validatedEmail, pendingEmail, emails[], studies[], interests[], groups[], pasts[]
// anniversary, children, smoking, height, lastupdated, hideFlag, validatedCellular
{
"birthDate" : -2147472000,
"education" : "unknown",
"religion" : "unknown",
"hairColor" : "unknown",
"sexualOrientation" : "unknown",
"userType" : "oldIcq",
"online" : "false",
"photo" : "false",
"betaFlag" : 0,
"autoSms" : "false", // autoforward IM to SMS
}
Result.Authorized := True; // Assume this is the default :)
if GetValueSafe('authRequired', i) then
Result.Authorized := i = 0;
// Owner only
if Result.UID2cmp = MyAccNum then
begin
Result.Authorized := True;
{
if GetValueSafe('webAware', i) then
webAware := i = 1;
if GetValueSafe('authRequired', i) then
authNeeded := i = 1;
if GetValueSafe('hideLevel', Tmp) then
if Tmp = 'none' then
showInfo := 0
else if Tmp = 'emailsAndCellular' then
showInfo := 1
else if Tmp = 'allExceptFln' then
showInfo := 2
else if Tmp = 'all' then
showInfo := 3
else
showInfo := 0;
}
// "privateKey" : "945b74c51c594c4c987f0b194fdabbd6",
// "allowEmail" : "false",
end;
end;
//location - Information that the user has provided about their location
//pending - ICQWEB: For buddylist events, any pending authorization buddies will have this flag
//recent - For buddylist events, any buddies in the Recent Buddies group will have this set
//bot - For buddylist events, any buddies that are BOTs will have this set
//shared - For buddylist events, any buddies in the a shared buddies group will have this set
// Add to roster if it's CL response and group is defined
if GroupToAddTo >= 0 then
begin
Result.CntIsLocal := False;
Result.Group := GroupToAddTo;
if not Result.IsInRoster then
fRoster.add(Result);
end;
// Handle status change
ProcessNewStatus(Result, NewStatus, not (OldXStatusStr = Result.xStatusStr), Batch);
OldPic := Result.ClientPic;
GetClientPicAndDesc4(Result, Result.ClientPic, Result.ClientDesc);
if Buddy.GetValueSafe('iconId', Tmp) then
if not (Tmp = Result.IconID) then
begin
ODS('New avatar for ' + String(Result.UID2cmp) + ': ' + Tmp);
Result.IconID := Tmp;
eventContact := Result;
NotifyListeners(IE_avatar_changed);
if IsMyAcc(Result) then
MyAvatarHash := Result.IconID;
end;
Result.InfoUpdatedTo := Now;
eventTime := Now;
eventContact := Result;
NotifyListeners(IE_userinfo);
end;
procedure TICQSession.ProcessNewStatus(var Cnt: TICQcontact; NewStatus: TICQstatus; XStatusStrChanged: Boolean = False; NoNotify: Boolean = False);
var
StatusChanged: Boolean;
begin
Cnt.PrevStatus := Cnt.Status;
eventOldStatus := Cnt.Status;
eventContact := Cnt;
eventTime := Now;
Cnt.BirthFlag := BirthdayFlag and (not (Cnt.birth = 0) or not (Cnt.birthL = 0));
StatusChanged := not (NewStatus = eventOldStatus);
//ODS(String(Cnt.uid2cmp) + ': ' + inttostr(integer(eventOldStatus)) + ' -> ' + inttostr(integer(newstatus)));
if StatusChanged then
Cnt.Status := NewStatus;
// Very slow with large CL otherwise
if NoNotify then
Exit;
if StatusChanged then
begin
if Cnt.PrevStatus = SC_UNK then
NotifyListeners(IE_statuschanged)
else if Cnt.PrevStatus = SC_OFFLINE then
NotifyListeners(IE_incoming)
else
begin
Cnt.LastTimeSeenOnline := eventTime;
Cnt.ClientClosed := Now;
NotifyListeners(IE_outgoing);
end;
end else if XStatusStrChanged then
NotifyListeners(IE_statuschanged)
else
NotifyListeners(IE_contactupdate);
end; // ProcessNewStatus
procedure TICQSession.ProcessUsersAndGroups(const JSON: TJSONObject);
var
user, groups: TJSONValue;
users: TJSONArray;
begin
if not IsReady then
Exit;
groups := JSON.GetValue('groups');
if Assigned(groups) and (groups is TJSONArray) then
ProcessContactList(TJSONArray(groups));
try
users := JSON.GetValue('users') as TJSONArray;
if Assigned(users) then
for user in users do
ProcessContact(TJSONObject(user), -1, True);
except end;
end;
procedure TICQSession.ProcessDialogState(const Dlg: TJSONObject);
var
c: TICQContact;
starting, outgoing: Boolean;
rbsTmp: RawByteString;
sn, mtype, sTmp, PatchVersion, StickerStr: String;
iTmp: Integer;
LastMsgId: TMsgID;
ExtSticker: TStringDynArray;
Theirs, Msg, MsgPos, Sticker, Person: TJSONValue;
Msgs, Persons: TJSONArray;
SrvHist: TSrvHist;
procedure DecryptMessage(Encrypted: Integer; Payload: TJSONObject; var Msg: String);
var
RQCompressed, RQLen: Integer;
RQCRC: Cardinal;
Ctx: TAESECB;
Key: TSHA256Digest;
Msg2, CrptMsg: TBytes;
i: Integer;
begin
Payload.GetValueSafe('compressed', RQCompressed);
Payload.GetValueSafe('crc', RQCRC);
Payload.GetValueSafe('length', RQLen);
if RQLen = 0 then
begin
eventError := EC_FailedDecrypt;
eventMsgA := GetTranslation('Zero length message');
NotifyListeners(IE_error);
Exit;
end else if RQCRC = 0 then
begin
eventError := EC_FailedDecrypt;
eventMsgA := GetTranslation('Incorrect CRC');
NotifyListeners(IE_error);
Exit;
end else if not (RQCompressed in [0,1]) then
begin
eventError := EC_FailedDecrypt;
eventMsgA := GetTranslation('Unknown type of compression [%d]', [RQCompressed]);
NotifyListeners(IE_error);
Exit;
end else if c.Crypt.EccMsgKey = '' then
begin
eventError := EC_FailedDecrypt;
eventMsgA := GetTranslation('Unable to create encryption key');
NotifyListeners(IE_error);
Exit;
end;
eventFlags := eventFlags or IF_Encrypt;
Msg2 := TEncoding.ANSI.GetBytes(Msg); // Should be Base64
Base64DecodeBytes(Msg2, CrptMsg);
SetLength(Msg2, 0);
CalcKey(Encrypted = 2, IfThen(Encrypted = 2, c.Crypt.EccMsgKey, ''), c.UID2cmp, MyAccount, 0, RQLen, Key);
i := Length(CrptMsg);
SetLength(Msg2, i + AESBLKSIZE);
ctx := TAESECB.Create(Key[0], 256);
ctx.Decrypt(@CrptMsg[0], @Msg2[0], i);
ctx.Free;
//ODS('Compressed: ' + TEncoding.ANSI.GetString(Msg2));
if RQCompressed = 1 then
Msg2 := ZDecompressBytes(Msg2);
//ODS('Decompressed: ' + TEncoding.ANSI.GetString(Msg2));
SetLength(Msg2, RQLen);
//ODS('Length applied: ' + TEncoding.ANSI.GetString(Msg2));
//ODS('My CRC: ' + inttostr(ZipCrc32($FFFFFFFF, @Msg2[0], RQLen) XOR $FFFFFFFF));
//ODS('His CRC: ' + IntToStr(RQCRC));
if Length(Msg2) > 0 then
if not ((ZipCrc32($FFFFFFFF, @Msg2[0], RQLen) XOR $FFFFFFFF) = RQCRC) then
begin
eventError := EC_FailedDecrypt;
eventMsgA := GetTranslation('Bad CRC');
NotifyListeners(IE_error);
//eventFlags := eventFlags and not IF_Bin and not IF_CODEPAGE_MASK;
end else
Msg := TEncoding.UTF8.GetString(Msg2)
else
begin
eventError := EC_FailedDecrypt;
eventMsgA := GetTranslation('Zero length message');
NotifyListeners(IE_error);
end;
{
eventContact := c;
notificationForMsg(msgtype, msgflags, priority=2, msg);
case getStatus of
byte(SC_away): sendACK(thisCnt, ACK_AWAY, '');
byte(SC_na): sendACK(thisCnt, ACK_NA, '');
byte(SC_dnd), byte(SC_occupied):
if priority = 2 then
sendACK(thisCnt, ACK_OK, '', msgDwnCnt)
else
sendACK(thisCnt, ACK_NOBLINK,'')
else sendACK(thisCnt, ACK_OK, '', msgDwnCnt)
end;
}
end;
function CheckDataPayload(var Msg: String): Boolean;
var
Payload: TJSONObject;
RQCaps, RQCap: TJSONValue;
RQType, Cap: String;
Caps: TArray;
Pub: array [0..2] of Integer;
Encryped, ECC, i: Integer;
My: TICQContact;
begin
Result := False;
Encryped := 0;
if ParseJSON(Msg, Payload) then
try
Payload.GetValueSafe('type', RQType);
if not (RQType = 'RnQDataIM') then
Exit;
RQCaps := Payload.GetValue('caps');
if not Assigned(RQCaps) or not (RQCaps is TJSONArray) then
Exit;
Payload.GetValueSafe('data', Msg);
// No caps? Get data as is
if TJSONArray(RQCaps).Count = 0 then
Exit;
for RQCap in TJSONArray(RQCaps) do
if Assigned(RQCap) and (RQCap is TJSONString) then
Insert(TJSONString(RQCap).Value, Caps, High(Caps));
// Buzz
if MatchText(String2Hex(BigCapability[CAPS_big_Buzz].v), Caps) then
begin
eventContact := c;
NotifyListeners(IE_buzz);
Result := True;
Exit;
end;
// Regular crypt
if MatchText(String2Hex(BigCapability[CAPS_big_CryptMsg].v), Caps) then
Encryped := 1;
// ECC crypt
ECC := 0;
for i := Low(Caps) to High(Caps) do
begin
if Caps[i].StartsWith(String2Hex('RDEC0')) then begin Pub[0] := i; Inc(ECC); end
else if Caps[i].StartsWith(String2Hex('RDEC1')) then begin Pub[1] := i; Inc(ECC, 2); end
else if Caps[i].StartsWith(String2Hex('RDEC2')) then begin Pub[2] := i; Inc(ECC, 4); end;
end;
if ECC = 7 then
begin
My := GetMyInfo;
if My.Crypt.EccPubKey = Copy(Hex2String(Caps[Pub[0]]), 6, 11) + Copy(Hex2String(Caps[Pub[1]]), 6, 11) + Copy(Hex2String(Caps[Pub[2]]), 6, 11) then
Encryped := 2
else
begin
eventError := EC_FailedDecrypt;
eventMsgA := GetTranslation('Message was encrypted using another public key');
NotifyListeners(IE_error);
Exit;
end;
end;
if Encryped > 0 then
DecryptMessage(Encryped, Payload, Msg);
finally
FreeAndNil(Payload);
end;
end;
procedure ProcessMsg(Msg: TJSONObject);
var
ID, Ack: Integer;
MsgID: TMsgID;
ev, evtmp: Thevent;
hist: Thistory;
begin
if not Assigned(Msg) then
Exit;
SetLength(eventBinData, 0);
eventFlags := 0;
eventData := '';
eventMsgA := '';
eventEncoding := TEncoding.Default;
eventMsgID := 0;
eventWID := '';
if Msg.GetValueSafe('msgId', MsgID) then
eventMsgID := MsgID;
if Msg.GetValueSafe('wid', sTmp) then
eventWID := sTmp;
if Msg.GetValueSafe('time', iTmp) then
eventTime := UnixToDateTime(iTmp, False)
else
eventTime := Now;
outgoing := False;
if Msg.GetValueSafe('outgoing', outgoing) then
if outgoing then
begin
Msg.GetValueSafe('reqId', sTmp);
Ack := Account.acks.FindID(sTmp);
if Ack >= 0 then // Still no ack?!
begin
ODS('Outgoing msg is already in history, but without ack');
eventData := sTmp;
eventMsgA := 'delivered';
NotifyListeners(IE_serverAck);
eventData := '';
eventMsgA := '';
end;
end;
if not (eventMsgID = 0) then
begin
hist := Thistory.Create(c.UID2Cmp);
evtmp := hist.getByMsgID(eventMsgID);
hist.Free;
if Assigned(evtmp) then
begin
ODS('Msg is already in history (MsgID ' + IntToStr(eventMsgID) + ')');
FreeAndNil(evtmp);
Exit;
end;
end;
// offlineIM/dataIM:
// "imf": "plain"
// "autoresponse" : 0
mtype := 'text'; // text or sticker
if Msg.GetValueSafe('mediaType', sTmp) then
if not (sTmp = '') then
mtype := sTmp;
if (mtype = 'text') and not (Msg.GetValue('sticker') = nil) then
mtype := 'sticker';
if mtype = 'sticker' then
begin
Sticker := Msg.GetValue('sticker');
if Sticker.GetValueSafe('id', sTmp) then
StickerStr := sTmp;
ExtSticker := SplitString(StickerStr, ':');
if (Length(ExtSticker) >= 4) then
begin
eventData := StickerStr;
eventFlags := eventFlags or IF_sticker;
end;
end else if Msg.GetValueSafe('text', sTmp) then
eventData := sTmp;
// eventAddress := sA; // For multichat
// delUpto
// Process special RnQ messages
if not (eventData = '') and ContainsStr(eventData, 'RnQDataIM') then
if CheckDataPayload(eventData) then
Exit;
eventContact := c;
if outgoing then
begin
if Length(eventData) > 0 then
begin
ev := Thevent.new(EK_Msg, c, GetMyInfo, eventTime, eventData, [], eventFlags, eventMsgID, eventWID);
ev.outgoing := Outgoing;
history.WriteToHistory(ev);
end;
end else
NotifyListeners(IE_msg);
end;
begin
if not Assigned(Dlg) then
Exit;
Dlg.GetValueSafe('sn', sn);
if sn = '' then
begin
Persons := TJSONArray(Dlg.GetValue('persons'));
if Assigned(Persons) and (Persons.Count > 0) then
for Person in Persons do
if Person is TJSONObject then
if not (TJSONObject(Person).GetValue('sn').Value = MyAccNum) then
Person.GetValueSafe('sn', sn);
end;
c := nil;
if not (sn = '') then
c := GetICQContact(sn);
if not Assigned(c) then
begin
eventError := EC_MalformedMsg;
eventMsgA := Dlg.ToString;
NotifyListeners(IE_error);
Exit;
end;
with Dlg do
begin
PatchVersion := '';
if GetValueSafe('patchVersion', sTmp) then
PatchVersion := sTmp;
GetValueSafe('lastMsgId', LastMsgId);
// Delivery/read status
Theirs := GetValue('theirs');
if Assigned(Theirs) then
begin
eventContact := c;
if Theirs.GetValueSafe('lastDelivered', sTmp) then
if TryStrToUInt64(sTmp, eventMsgID) then
NotifyListeners(IE_ack);
// if Theirs.GetValueSafe('lastRead', sTmp) then
// if TryStrToUInt64(sTmp, eventMsgID) then
// NotifyListeners(IE_readAck);
end;
GetValueSafe('unreadCnt', iTmp);
GetValueSafe('unreadMentionMeCount', iTmp);
if GetValueSafe('starting', starting) then
if starting then
begin
SrvHist := SQLDB.GetHistDlg(c.UID2Cmp);
SQLDB.UpdateHistDlg(c.UID2Cmp, LastMsgId, PatchVersion);
if (LastMsgId > 0) and (LastMsgId > SrvHist.LastMsgId) then
GetServerHistory(c.UID2Cmp, SrvHist.LastMsgId, SrvHist.PatchVersion);
Exit;
end;
if (LastMsgId > 0) and not (PatchVersion = '') then
SQLDB.UpdateHistDlg(c.UID2Cmp, LastMsgId, PatchVersion);
Msgs := GetValue('messages') as TJSONArray;
if Assigned(Msgs) then
for Msg in Msgs do
if Msg is TJSONObject then
ProcessMsg(TJSONObject(Msg));
MsgPos := GetValue('intro');
if Assigned(MsgPos) and (MsgPos is TJSONObject) then
begin
Msgs := TJSONObject(MsgPos).GetValue('messages') as TJSONArray;
if Assigned(Msgs) then
for Msg in Msgs do
if Msg is TJSONObject then
ProcessMsg(TJSONObject(Msg));
end;
MsgPos := GetValue('tail');
if Assigned(MsgPos) and (MsgPos is TJSONObject) then
begin
Msgs := TJSONObject(MsgPos).GetValue('messages') as TJSONArray;
if Assigned(Msgs) then
for Msg in Msgs do
if Msg is TJSONObject then
ProcessMsg(TJSONObject(Msg));
end;
end;
end;
procedure TICQSession.ProcessIMState(const Data: TJSONObject);
var
IMState: TJSONValue;
IMStates: TJSONArray;
WID, State: String;
UnixTime, ID: Integer;
MsgID: TMsgID;
begin
if not Assigned(Data) then
Exit;
IMStates := Data.GetValue('imStates') as TJSONArray;
if Assigned(IMStates) then
for IMState in IMStates do
with IMState do
begin
GetValueSafe('state', State);
if State = '' then
Continue;
eventFlags := 0;
eventMsgA := State;
if GetValueSafe('ts', UnixTime) then
eventTime := UnixToDateTime(UnixTime, False);
if GetValueSafe('msgId', WID) then
eventWID := WID;
if GetValueSafe('histMsgId', MsgID) then
eventMsgID := MsgID;
if GetValueSafe('sendReqId', eventData) then
NotifyListeners(IE_serverAck);
end;
end;
procedure TICQSession.ProcessTyping(const Data: TJSONObject);
var
c: TICQContact;
TypingStatus: String;
begin
if not Assigned(Data) then
Exit;
c := GetICQContact(Data.GetValue('aimId').Value);
if not Assigned(c) then
Exit;
Data.GetValueSafe('typingStatus', TypingStatus);
if TypingStatus = 'typing' then
eventInt := MTN_BEGUN
else if TypingStatus = 'typed' then
eventInt := MTN_TYPED
else
eventInt := MTN_FINISHED;
eventTime := Now;
eventContact := c;
// eventMsgID := ?;
case eventInt of
MTN_FINISHED, MTN_TYPED, MTN_CLOSED: eventContact.typing.bIsTyping := False;
MTN_BEGUN: eventContact.typing.bIsTyping := True;
end;
NotifyListeners(IE_typing);
end;
procedure TICQSession.ProcessAddedYou(const Data: TJSONObject);
var
c: TICQContact;
NeedAuth: Integer;
Name, Msg: String;
begin
if not Assigned(Data) then
Exit;
c := GetICQContact(Data.GetValue('requester').Value);
if not Assigned(c) then
Exit;
//Data.GetValueSafe('displayAIMid', Name);
Data.GetValueSafe('authRequested', NeedAuth);
Data.GetValueSafe('msg', Msg);
eventContact := c;
eventTime := Now;
eventFlags := 0;
eventMsgA := Msg;
NotifyListeners(IE_addedYou);
if NeedAuth = 1 then
begin
eventContact := c;
eventTime := Now;
eventFlags := 0;
eventMsgA := Msg;
NotifyListeners(IE_authReq);
end;
end;
procedure TICQSession.ProcessPermitDeny(const Data: TJSONObject);
var
c: TICQContact;
Mode: String;
Item, Items: TJSONValue;
begin
if not Assigned(Data) then
Exit;
SpamList.Clear;
Items := Data.GetValue('ignores');
if Assigned(Items) and (Items is TJSONArray) then
for Item in TJSONArray(Items) do
begin
c := nil;
if not (TJSONString(Item).Value = '') then
c := GetICQContact(TJSONString(Item).Value);
if not Assigned(c) then
Continue;
SpamList.Add(c);
AddToIgnoreList(c, True);
eventContact := c;
NotifyListeners(IE_contactupdate);
end;
(* Clear unsupported block list?
Items := Data.GetValue('blocks');
if Assigned(Items) and (Items is TJSONArray) then
for Item in TJSONArray(Items) do
RemFromBlock(TJSONString(Item).Value);
*)
if Data.GetValueSafe('pdMode', Mode) then
if not (Mode = '') then
if (SpamList.Count > 0) and not (Mode = 'denySome') then
SetPermitDenyMode('denySome');
UpdatePrefsFrm;
end;
// Not working, for VoIP?
procedure TICQSession.InitWebRTC;
var
BaseURL, UnixTime: String;
Params: TDictionary;
begin
UnixTime := IntToStr(DateTimeToUnix(Now, False) - fHostOffset);
Params := TDictionary.Create;
Params.Add('a', fAuthToken);
Params.Add('f', 'json');
Params.Add('k', fDevId);
Params.Add('r', CreateNewGUID);
Params.Add('ts', UnixTime);
BaseURL := WIM_HOST + 'webrtc/alloc';
SendRequest(True, BaseURL, MakeParams('POST', BaseURL, Params), 'Init WebRTC');
end;
procedure TICQSession.GetServerHistory(const UID: TUID; FromMsgId: TMsgID; const PatchVer: String);
function SameTextMsgExists(Event: Thevent; const Text: String; Kind: Integer): Boolean;
begin
Result := not (Event = nil) and (Event.GetBodyText = text) and (Event.Kind = kind);
end;
function SameBinMsgExists(Event: Thevent; Bin: TBytes; Kind: Integer): Boolean;
begin
Result := not (Event = nil) and (Event.GetBodyBin = bin) and (Event.Kind = kind);
end;
var
Query, RespStr, WID: String;
Params: TDictionary;
Msg, Patch, Text, Tmp: TJSONValue;
JSON, Results, StickerObj: TJSONObject;
Messages, Patches: TJSONArray;
UnixTime, Code, Ind, Kind: Integer;
PatchMsgId: Int64;
ExtSticker: TStringDynArray;
StickerBin: TBytes;
Time: TDateTime;
Outgoing: Boolean;
ev, evtmp, evtmp2: Thevent;
cht, cnt: TICQContact;
hist: Thistory;
function GetHistoryChunk(PatchVer: String; const From: String; Count: Integer; const Till: String = ''): String;
var
BaseURL, Header: String;
begin
if PatchVer = '' then
PatchVer := '1';
BaseURL := REST_HOST + 'getHistory';
Params := TDictionary.Create();
Params.Add('a', fAuthToken);
Params.Add('f', 'json');
Params.Add('k', fDevId);
Params.Add('ts', IntToStr(DateTimeToUnix(Now, False) - fHostOffset));
Params.Add('client', 'icq');
Params.Add('lang', IfThen(IsRuLang, 'ru-ru', 'en-us'));
Params.Add('reqId', CreateNewGUID);
Params.Add('authToken', fRESTToken);
Params.Add('clientId', fRESTClientId);
Params.Add('sn', UID);
Params.Add('fromMsgId', From);
if not (Till = '') then
Params.Add('tillMsgId', Till);
Params.Add('count', IntToStr(Count));
Params.Add('aimSid', fAimSid);
Params.Add('patchVersion', PatchVer);
Query := MakeParams('POST', BaseURL, Params);
Header := '[POST] Get a chunk of server history [' + From + ':' + IntToStr(Count) + ']';
LoggaICQPkt(Header, WL_sent_text, Query);
LoadFromURLAsString(BaseURL, Result, BaseURL + '?' + Query);
LoggaICQPkt(Header, WL_rcvd_text, Result);
end;
procedure FreeBeforeContinue;
begin
if Assigned(evtmp) then
FreeAndNil(evtmp);
if Assigned(evtmp2) then
FreeAndNil(evtmp2);
end;
begin
if not logpref.writehistory or not RESTAvailable then
Exit;
evtmp := nil;
evtmp2 := nil;
cht := GetContact(UID);
if FromMsgId = 0 then
begin
hist := Thistory.Create(LowerCase(UID));
if hist.GetEventCount > 0 then
begin
hist.Free;
Exit;
end;
hist.Free;
end;
RespStr := GetHistoryChunk(PatchVer, UIntToStr(FromMsgId), MAXINT - 1);
if not ParseJSON(RespStr, JSON) then
Exit;
if TJSONObject(JSON.GetValue('status')).GetValue('code').TryGetValue(Code) then
if not (Code = 20000) then
begin
ODS('Error code: ' + IntToStr(Code));
Exit;
end;
Results := TJSONObject(JSON.GetValue('results'));
if Results = nil then
begin
ODS('No results');
Exit;
end;
// Results.GetValueSafe('patchVersion', PatchVer);
// Patches := TJSONArray(Results.GetValue('patch'));
// for Patch in Patches do
// if Assigned(Patch) and (Patch is TJSONObject) then
// begin
// PatchMsgId := StrToInt64(TJSONString(TJSONObject(Patch).GetValue('msgId')).Value);
// GetHistoryChunk(IntToStr(PatchMsgId - 1), 1);
// Break;
// end;
ProcessDialogState(Results);
Exit;
hist := Thistory.Create(LowerCase(UID));
for Msg in Messages do
if Assigned(Msg) and (Msg is TJSONObject) then
begin
Msg.GetValueSafe('time', UnixTime);
Time := UnixToDateTime(UnixTime, False);
evtmp := hist.getByTime(Time);
Tmp := TJSONObject(Msg).GetValue('outgoing');
Outgoing := Assigned(Tmp) and (Tmp.Value = 'true');
if Outgoing then
cnt := GetMyInfo
else
cnt := cht;
// TODO: Switch all WID to MsgID
WID := '';
Tmp := TJSONObject(Msg).GetValue('wid');
if Assigned(Tmp) then
begin
Tmp.TryGetValue(WID);
if not (WID = '') then
begin
evtmp2 := hist.getByWID(WID);
if Assigned(evtmp2) then
begin
ODS('Msg is already in history (WID ' + wid + ')');
FreeBeforeContinue;
Continue;
end;
end;
end;
Text := TJSONObject(Msg).GetValue('text');
StickerObj := TJSONObject(msg).GetValue('sticker') as TJSONObject;
if Assigned(StickerObj) then
begin
Text := StickerObj.GetValue('id');
ExtSticker := SplitString(Text.Value, ':');
if EnableStickers and (Length(ExtSticker) >= 4) then
begin
Kind := EK_msg;
StickerBin := GetSticker(ExtSticker[1], ExtSticker[3]);
evtmp2 := hist.getByTime(Time);
if SameBinMsgExists(evtmp2, StickerBin, Kind) then
begin
ODS('EK_msg with the same sticker is already in history (WID ' + wid + ')');
FreeBeforeContinue;
Continue;
end;
if Assigned(evtmp2) then
FreeAndNil(evtmp2);
ev := Thevent.new(Kind, cht, cnt, Time, '', [], 0, 0, WID);
ev.outgoing := Outgoing;
ev.setImgBin(StickerBin);
history.WriteToHistory(ev);
FreeBeforeContinue;
Continue;
end;
end;
{ TODO: Add bday, buddy_added and other events }
Tmp := TJSONObject(Msg).GetValue('eventTypeId');
if Assigned(Tmp) then
begin
if Tmp.Value = '27:51000' then
begin
Kind := EK_msg;
if SameTextMsgExists(evtmp, Text.Value, Kind) then
begin
ODS('EK_msg with the same time is already in history');
FreeBeforeContinue;
Continue;
end;
ev := Thevent.new(Kind, cht, cnt, Time, '[' + GetTranslation('Message deleted') + ']', [], IF_not_delivered, 0, WID);
ev.outgoing := Outgoing;
history.WriteToHistory(ev);
FreeBeforeContinue;
Continue;
end else if Tmp.Value = '27:33000' then
begin
Kind := EK_AddedYou;
if SameTextMsgExists(evtmp, Text.Value, Kind) then
begin
ODS('EK_AddedYou with the same time is already in history');
FreeBeforeContinue;
Continue;
end;
ev := Thevent.new(Kind, cht, cht, Time, '', [], 0);
ev.outgoing := False;
history.WriteToHistory(ev);
FreeBeforeContinue;
Continue;
end else if Tmp.Value = '27:33000' then
begin
// Bday event is never saved on disk, ignore
Kind := EK_BirthDay;
FreeBeforeContinue;
Continue;
end;
end;
if Assigned(Text) then
try
Kind := EK_msg;
evtmp2 := hist.getByTime(Time);
if SameTextMsgExists(evtmp2, Text.Value, Kind) then
begin
ODS('EK_msg with the same time/text is already in history (WID ' + wid + ')');
FreeBeforeContinue;
Continue;
end;
if Assigned(evtmp2) then
FreeAndNil(evtmp2);
ev := Thevent.new(Kind, cht, cnt, Time, Text.Value, [], 0, 0, WID);
ev.outgoing := Outgoing;
history.WriteToHistory(ev);
except
ODS('Not a json');
end else
ODS('Empty msg');
if Assigned(evtmp) then
FreeAndNil(evtmp);
end;
hist.Free;
end;
procedure TICQSession.SetListener(l : TProtoNotify);
begin
listener := l;
end;
procedure TICQSession.SendTyping(c: TICQContact; NotifType: Word);
var
TypingStatus: String;
Query: UTF8String;
BaseURL: String;
begin
if not Assigned(c) or (not IsOnline) or (not ImVisibleTo(c)) then
Exit;
TypingStatus := 'none';
if NotifType = MTN_BEGUN then
TypingStatus := 'typing'
else if NotifType = MTN_TYPED then
TypingStatus := 'typed';
BaseURL := WIM_HOST + 'im/setTyping';
Query := '&t=' + ParamEncode(String(c.UID2cmp)) +
'&typingStatus=' + TypingStatus;
SendSessionRequest(False, BaseURL, Query, 'Send typing');
end;
procedure TICQSession.RemoveMeFromHisCL(const uin: TUID);
begin
// TODO?
UnsupportedFeature;
end;
procedure TICQSession.SendAddContact(c: TICQcontact);
var
JSON: TJSONObject;
Query: UTF8String;
BaseURL, ResCode: String;
Results: TJSONArray;
Code: Integer;
begin
BaseURL := WIM_HOST + 'buddylist/addBuddy';
Query := '&buddy=' + ParamEncode(String(c.UID2cmp)) +
'&group=' + ParamEncode(groups.id2name(c.Group)) +
'&authorizationMsg=' + ParamEncode(GetTranslation(Str_AuthRequest)) +
'&preAuthorized=1';
if SendSessionRequest(False, BaseURL, Query, RT_JSON, JSON, 'Add contact', 'Failed to add contact') then
begin
Results := JSON.GetValue('results') as TJSONArray;
if Assigned(Results) and (Results.Count > 0) then
if not (Results.Get(0) = nil) then
if Results.Get(0).GetValueSafe('resultCode', ResCode) then
if TryStrToInt(ResCode, Code) then
begin
if Code = 0 then // Success! Remove local state and get profile of a newly added contact
begin
c.CntIsLocal := False;
GetProfile(c.UID2cmp);
end
else
begin
eventError := EC_AddContact_Error;
eventInt := Code;
NotifyListeners(IE_error);
// Already in CL? Request it again
if Code = 3 then
GetCL;
end;
end;
end;
end;
procedure TICQSession.SendRemoveContact(c: TICQcontact);
var
Query: UTF8String;
BaseURL, ResCode: String;
Code: Integer;
begin
BaseURL := WIM_HOST + 'buddylist/removeBuddy';
Query := '&buddy=' + ParamEncode(String(c.UID2cmp)) +
'&allGroups=1';
if SendSessionRequest(False, BaseURL, Query, 'Remove contact', 'Failed to remove contact') then
c.CntIsLocal := c.IsInRoster
end;
function TICQSession.UpdateGroupOf(c: TICQContact; grp: Integer): Boolean;
var
Query: UTF8String;
BaseURL, ResCode: String;
Code: Integer;
begin
Result := False;
if c.CntIsLocal then
Exit;
BaseURL := WIM_HOST + 'buddylist/moveBuddy';
Query := '&buddy=' + ParamEncode(String(c.UID2cmp)) +
'&group=' + ParamEncode(groups.id2name(c.Group)) +
'&newGroup=' + ParamEncode(groups.id2name(grp));
if SendSessionRequest(False, BaseURL, Query, 'Move contact', 'Failed to move contact') then
begin
Result := True;
eventContact := c;
NotifyListeners(IE_contactupdate);
end;
end;
function TICQSession.SendUpdateGroup(const Name: String; ga: TGroupAction; const Old: String = ''): Boolean;
var
Query: UTF8String;
BaseURL: String;
Code: Integer;
begin
Result := False;
if ga = GA_Add then
begin
BaseURL := WIM_HOST + 'buddylist/addGroup';
Query := '&group=' + ParamEncode(Name);
Result := SendSessionRequest(False, BaseURL, Query, 'Add group', 'Failed to add group');
end else if (ga = GA_Rename) and not (Old = '') then
begin
BaseURL := WIM_HOST + 'buddylist/renameGroup';
Query := '&oldGroup=' + ParamEncode(Old) +
'&newGroup=' + ParamEncode(Name);
Result := SendSessionRequest(False, BaseURL, Query, 'Rename group', 'Failed to rename group');
end else if ga = GA_Remove then
begin
BaseURL := WIM_HOST + 'buddylist/removeGroup';
Query := '&group=' + ParamEncode(Name);
Result := SendSessionRequest(False, BaseURL, Query, 'Remove group', 'Failed to remove group');
end else
Exit;
end;
procedure TICQSession.Authorize(c: TICQContact; Grant: Boolean = True);
var
JSON: TJSONObject;
Query: UTF8String;
BaseURL, ResCode: String;
Code: Integer;
begin
BaseURL := WIM_HOST + 'buddylist/authorizeUser';
Query := '&t=' + ParamEncode(String(c.UID2cmp)) +
'&authorized=' + IfThen(Grant, '1', '0');
SendSessionRequest(False, BaseURL, Query, IfThen(Grant, 'Grant', 'Deny') + ' auth', 'Failed to ' + IfThen(Grant, 'grant', 'deny') + ' authorization');
end;
procedure TICQSession.AuthRequest(c: TICQContact; Reason: String);
var
Query: UTF8String;
BaseURL: String;
iam: TICQContact;
begin
iam := GetMyInfo;
if Reason = '' then
Reason := GetTranslation(Str_AuthRequest);
BaseURL := WIM_HOST + 'buddylist/requestAuthorization';
Query := '&t=' + ParamEncode(String(c.UID2cmp)) +
'&authorizationMsg=' + ParamEncode(Reason);
SendSessionRequest(False, BaseURL, Query, 'Request auth', 'Failed to request authorization')
end;
(*
case Item.ItemType of
FEEDBAG_CLASS_ID_BUDDY:
begin
cnt := GetICQContact(UnUTF(item.ItemName));
cnt.cntIsLocal := False;
cnt.SSIID := Item.ItemID;
cnt.Authorized := not existsTLV($66, item.ExtData);
// if SSI_InServerTransaction then
if SSI_InServerTransaction > 0 then
begin
//SSI_UpdateGroups(item.GroupID);
SSI_InServerTransaction := 1;
//SSIstop;
end;
if cnt.infoUpdatedTo=0 then
// cnt.toQuery := True;
TCE(cnt.data^).toquery := True;
// sendQueryInfo(cnt.uid);
eventContact := cnt;
NotifyListeners(IE_contactupdate);
end;
FEEDBAG_CLASS_ID_GROUP:
begin
// if SSI_InServerTransaction then
if SSI_InServerTransaction >0 then
begin
if item.GroupID <> 0 then
//SSI_UpdateGroups([0]);
//SSIstop;
end;
end;
FEEDBAG_CLASS_ID_PERMIT:
begin
cnt := GetICQContact(UnUTF(item.ItemName));
fVisibleList.add(cnt);
eventContact := cnt;
NotifyListeners(IE_contactupdate);
end;
FEEDBAG_CLASS_ID_DENY:
begin
cnt := GetICQContact(UnUTF(item.ItemName));
fInVisibleList.add(cnt);
eventContact := cnt;
NotifyListeners(IE_contactupdate);
end;
FEEDBAG_CLASS_ID_IGNORE_LIST:
begin
cnt := GetICQContact(UnUTF(item.ItemName));
ignoreList.add(cnt);
spamList.add(cnt);
eventContact := cnt;
NotifyListeners(IE_contactupdate);
end;
end;
item := NIL;
end
else
if (ack = $0A) and (UnUTF(item.ItemName) > '') then
case Item.ItemType of
FEEDBAG_CLASS_ID_BUDDY:
begin
cnt := GetICQContact(UnUTF(item.ItemName));
cnt.cntIsLocal := True;
cnt.SSIID := 0;
end;
FEEDBAG_CLASS_ID_GROUP:
begin
// i := groups.ssi2id(Item.ItemID);
// if i >= 0 then
// groups.a[groups.idxOf(i)].ssiID := 0;
end;
end;
SSI_OPERATION_CODES_REMOVE:
if (UnUTF(item.ItemName) > '') and
((ack = 0)or ((ack=02) and (Item.ItemType = FEEDBAG_CLASS_ID_BUDDY))) then
begin
case Item.ItemType of
FEEDBAG_CLASS_ID_BUDDY:
begin
cnt := GetICQContact(UnUTF(item.ItemName));
eventContact := cnt;
// if SSI_InServerTransaction then
if SSI_InServerTransaction >1 then
begin
// gID := groups.idxOf(cnt.group);
// if (gID >= 0)and(groups.a[gID].ssiID <> item.GroupID) then
begin
// SSI_UpdateGroups([item.GroupID, groups.a[gID].ssiID]);
// SSI_InServerTransaction := 1;
//SSIstop(True);
end
end;
if SSI_InServerTransaction = 1 then
begin
cnt.cntIsLocal := True;
cnt.Authorized := False;
cnt.SSIID := 0;
//SSI_UpdateGroups(item.GroupID);
//SSIstop;
end;
// addContact(cnt, True);
NotifyListeners(IE_contactupdate);
end;
FEEDBAG_CLASS_ID_PERMIT:
begin
cnt := GetICQContact(UnUTF(item.ItemName));
fVisibleList.remove(cnt);
eventContact := cnt;
NotifyListeners(IE_contactupdate);
end;
FEEDBAG_CLASS_ID_DENY:
begin
cnt := GetICQContact(UnUTF(item.ItemName));
fInVisibleList.remove(cnt);
eventContact := cnt;
NotifyListeners(IE_contactupdate);
end;
FEEDBAG_CLASS_ID_IGNORE_LIST:
begin
cnt := GetICQContact(UnUTF(item.ItemName));
ignoreList.remove(cnt);
spamList.remove(cnt);
eventContact := cnt;
NotifyListeners(IE_contactupdate);
end;
end;
end;
end;
*)
class function TICQSession._MaxPWDLen: Integer;
begin
Result := MaxPwdLength;
end;
function TICQSession.GetContact(const UID: TUID): TICQContact;
begin
Result := GetICQContact(uid);
end;
function TICQSession.GetContact(const UIN: Integer): TICQContact;
begin
Result := GetICQContact(uin);
end;
function TICQSession.GetStatuses: TStatusArray;
begin
Result := ICQStatuses;
end;
function TICQSession.GetVisibilities: TStatusArray;
begin
Result := ICQVis;
end;
function TICQSession.GetStatusMenu: TStatusMenu;
begin
Result := StatMenu;
end;
function TICQSession.GetVisMenu: TStatusMenu;
begin
Result := ICQVisMenu;
end;
function TICQSession.GetStatusDisable: TOnStatusDisable;
begin
Result := OnStatusDisable[Byte(curStatus)];
end;
procedure TICQSession.InputChangedFor(c: TICQContact; InpIsEmpty: Boolean; TimeOut: Boolean = False);
begin
if (not SupportTypingNotif) or (not IsSendTypingNotif) or not Assigned(c) then
Exit;
with c do
if (not (c.Status in [SC_OFFLINE, SC_UNK])) {and (Typing.bSupport)} then
begin
if (not InpIsEmpty) then
begin
if TimeOut then
begin
Typing.bIamTyping := False;
SendTyping(c, MTN_TYPED);
end
else
begin
Typing.TypingTime := Now;
if not Typing.bIamTyping then
begin
Typing.bIamTyping := True;
SendTyping(c, MTN_BEGUN);
end;
end;
end else if Typing.bIamTyping then
begin
SendTyping(c, MTN_FINISHED);
Typing.bIamTyping := False;
end
end
end;
function TICQSession.CompareStatusFor(Cnt1, Cnt2: TICQContact): SmallInt;
begin
if StatusPriority[Cnt1.Status] < StatusPriority[Cnt2.Status] then
Result := -1
else if StatusPriority[Cnt1.Status] > StatusPriority[Cnt2.Status] then
Result := +1
else
Result := 0;
end;
procedure TICQSession.GetClientPicAndDesc4(cnt: TICQContact; var pPic: TPicName; var CliDesc: String);
begin
if IsOffline or (cnt = nil) or cnt.IsOffline then
Exit;
GetICQClientPicAndDesc(cnt, pPic, CliDesc);
end;
procedure TICQSession.ApplyBalloon;
function SameMonthDay(d1, d2: TDateTime): Boolean;
begin
Result := (MonthOf(d1) = MonthOf(d2)) and (DayOf(d1) = DayOf(d2))
end;
begin
if GetMyInfo = nil then
raise Exception.create('ApplyBalloon: ICQ.MyInfo is nil');
Self.BirthdayFlag := (SendBalloonOn = BALLOON_BDAY) and SameMonthDay(self.GetMyInfo.birth, Now)
or (SendBalloonOn = BALLOON_DATE) and SameMonthDay(SendBalloonOnDate, Now)
or (SendBalloonOn = BALLOON_ALWAYS);
end; // ApplyBalloon
class constructor TICQSession.InitICQProto;
var
b, b2: Byte;
begin
SetLength(ICQStatuses, Byte(High(TICQStatus)) + 1);
for b := Byte(Low(TICQStatus)) to Byte(High(TICQStatus)) do
with ICQStatuses[b] do
begin
idx := b;
ShortName := Status2Img[b];
Cptn := Status2ShowStr[TICQStatus(b)];
// ImageName := 'status.' + status2str[st1];
ImageName := 'status.' + ShortName;
end;
SetLength(StatMenu, 9);
b2 := 0;
StatMenu[b2] := Byte(SC_ONLINE); Inc(b2);
StatMenu[b2] := Byte(SC_F4C); Inc(b2);
StatMenu[b2] := Byte(SC_OCCUPIED); Inc(b2);
StatMenu[b2] := Byte(SC_DND); Inc(b2);
StatMenu[b2] := Byte(SC_AWAY); Inc(b2);
StatMenu[b2] := Byte(SC_NA); Inc(b2);
StatMenu[b2] := Byte(SC_EVIL); Inc(b2);
StatMenu[b2] := Byte(SC_DEPRESSION); Inc(b2);
StatMenu[b2] := Byte(SC_OFFLINE);
SetLength(ICQVis, Byte(High(TVisibility)) + 1);
for b := Byte(Low(TVisibility)) to Byte(High(TVisibility)) do
with ICQvis[B] do
begin
idx := b;
ShortName := Visib2Str[TVisibility(b)];
Cptn := Visibility2ShowStr[TVisibility(b)];
// ImageName := 'status.' + Status2Str[st1];
ImageName := Visibility2ImgName[TVisibility(b)];
end;
SetLength(ICQVisMenu, 2);
ICQVisMenu[0] := Byte(VI_normal);
ICQVisMenu[1] := Byte(VI_invisible);
end;
class destructor TICQSession.UnInitICQProto;
var
b: Byte;
begin
if Length(ICQStatuses) > 0 then
for b := Byte(Low(TICQStatus)) to Byte(High(TICQStatus)) do
with ICQStatuses[b] do
begin
SetLength(ShortName, 0);
SetLength(Cptn, 0);
SetLength(ImageName, 0);
end;
SetLength(ICQStatuses, 0);
SetLength(StatMenu, 0);
if Length(ICQVis) > 0 then
for b := Byte(Low(TVisibility)) to Byte(High(TVisibility)) do
with ICQVis[B] do
begin
SetLength(ShortName, 0);
SetLength(Cptn, 0);
SetLength(ImageName, 0);
end;
SetLength(ICQVis, 0);
SetLength(ICQVisMenu, 0);
end;
end.