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/ChatBox.pas

5043 lines
147 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 ChatBox;
{$I RnQConfig.inc}
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, System.Character, System.Types, System.UITypes, System.StrUtils,
System.DateUtils, System.NetEncoding, System.Variants, System.JSON, System.RegularExpressionsCore, System.RegularExpressionsConsts,
System.Win.TaskbarCore, Generics.Defaults, Generics.Collections, Vcl.Graphics, Vcl.Forms, Vcl.Imaging.PNGImage, Vcl.Taskbar,
RDGlobal, RnQNet, ICQCommon, ICQContacts, history, events, utilLib, pluginLib, Stickers, GR32, SQLiteDB,
SciterJS, SciterJSAPI, BaseWindow;
{$I PubRTTI.inc}
type
TLinkKind = (LK_FTP, LK_EMAIL, LK_WWW, LK_UIN, LK_ED);
TDrawStyle = (dsNone, dsBuffer, dsMemory, dsGlobalBuffer32);
TItemKind = (PK_NONE, PK_HEAD, PK_TEXT, PK_ARROWS_UP, PK_ARROWS_DN, PK_LINK, PK_SMILE, PK_CRYPTED, PK_RQPIC, PK_RQPICEX, PK_RNQBUTTON);
TSendAction = (SA_APPEND, SA_PREPEND, SA_UPDATE);
TChatItem = record
kind: TItemKind;
stringData: String;
timeData: TDateTime;
end;
TMessageData = record
what, when, prefix, prefixCls, msg, embedded, cls, time, msgid,
statusImg, statusImgExt, eventImg, cryptImg, myReaction: String;
encrypted, eccencrypted, writeHist: Boolean;
patches, reactions: TParams;
end;
TPageSettings = record
splitX, splitY: Integer;
tiled, positioned: String
end;
TChatSettings = record
showSmiles, showRelTimes, showAvatar, showSmileCaption, showHintsInChat, showSmartReplies, showReactions, autoCopy, smoothFontRendering, fontCodes,
viewTextWrap, animatedScroll, quoteSelected, cursorBelow, alwaysOnTop: Boolean;
wheelVelocity, imageQuality, maxImgWidth, maxImgHeight, msgBuffer, sendOnEnter, spellErrorStyle, screenshotFormat: Integer;
cachePath, spellErrorColor, screenshotFilename, chatCSS: String;
end;
TVideoFormat = record
url: String;
quality: String;
format: String;
codecs: String;
title: String;
end;
TParamPair = record
param: String;
value: String;
end;
TIntPair = TPair;
{$I NoRTTI.inc}
TChatMethods = class(TNativeMethods)
class procedure RegisterMethods(var ReturnValue: TSciterValue); override;
class procedure OnChatShow(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure OnChatHide(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure OnChatResize(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure OnChatActivate(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure OnChatMouseEnter(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure OnChatMouseLeave(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure UpdateChatXY(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure AttachWindow(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure DetachWindow(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure LoadHistory(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure UpdateSelection(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure UploadLastSnapshot(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure DeleteSnapshot(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure UploadFiles(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure GetLinkInfo(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure GetYoutubeLinks(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure GetVimeoLinks(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure GetVolumeLevel(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure SaveVolumeLevel(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure RequestChatPageSettings(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure ChatPageSelected(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure ChatPageDeselected(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure PluginPageSelected(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure PluginPageDeselected(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
// class procedure SetTabDragging(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure SaveTabsOrder(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure CloseChatPage(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure ClosePluginPage(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
// class procedure AddUIN2CL(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure CopyLink(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure SavePicture(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure GetEvent(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure SaveAs(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure AddLinkToFav(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure DeleteMessages(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure GetReactions(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure AddToAntispam(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure ToggleSmiles(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure ToggleRelTimes(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure RealizeEvents(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure StoreSplit(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure InputChangedFor(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure GetMessageByIdx(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure WrapText(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure SendChatMessage(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure EditChatMessage(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure GetChatMessageText(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure ClosePages(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure UploadFile(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure SaveEmbeddedFile(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure ChatButtonClick(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure PluginButtonClick(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure ToggleTranslit(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
// class procedure QIPPwd(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure SendStickerToCurrent(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure GetStoreStickerPacks(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure GetStickerPacks(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure SearchStickerPacks(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure BuyStickerPack(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure RemoveStickerPack(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure GetStickerPackContent(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure SearchStickersByKeywords(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure SpanEmojis(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure GetSpellingSuggestions(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure GetEventHeaderTime(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure AddReaction(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure RemoveReaction(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
class procedure GetMyReaction(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
end;
TChatType = (CT_IM, CT_PLUGING);
PChatInfo = ^TChatInfo;
TChatInfo = class
public
ID: IntPtr;
chatType: TChatType;
who: TICQContact;
single: Boolean; // single-message
lastInputText: string; // last input.text before quoting sequence or plugin name
constructor Create;
destructor Destroy; override;
procedure CheckTypingTime;
end;
TChats = class(Tlist)
function validIdx(i: Integer): boolean;
function idxOf(c: TICQContact): Integer;
function idxOfUIN(const uin: TUID): Integer;
function idxOfID(ID: Integer): Integer;
function byIdx(i: Integer): TChatInfo;
function byContact(c: TICQContact): TChatInfo;
function byUIN(const uid: TUID): TChatInfo;
function byID(id: Integer): TChatInfo;
procedure CheckTypingTimeAll;
end;
TInputCallback = procedure(Sender: TObject; selected: String = '') of object;
TTaskbarEx = class(TTaskbar)
protected
function GetFormHandle: HWND; override;
public
function GetMainWindowHwnd: HWND; override;
end;
TChatBox = class(TBaseWindow)
private
Preview: Boolean;
TaskBar: TTaskbarEx;
SelectedText: String;
StartSel, EndSel: TDateTime;
IsWholeEvents: Boolean;
histories: TObjectDictionary;
public
CurrentContact: TICQContact;
LastContact: TICQContact;
Chats: TChats;
selectedUIN: TUID;
plugBtns: TPlugButtons;
MainFormWidth: Integer;
menuWasCancelled: boolean;
PagesEnumStr: String;
constructor Create(MsgPreview: Boolean = False);
destructor Destroy; override;
function ChatWndProc(var Message: TMessage): Boolean;
procedure Show;
procedure Hide;
procedure Resize;
procedure UpdateCaption(const Caption: String);
procedure UpdateHintsTraslation;
function IsChatOpen(otherHand: TICQContact): boolean;
function OpenChat(otherHand: TICQContact; ForceActive: Boolean = False; isAuto: Boolean = False): Boolean;
function OpenOn(c: TICQContact; focus: Boolean = True; pShow: Boolean = True): Boolean;
procedure Open(Focus: Boolean = True);
function NewIMChannel(c: TICQContact): Integer;
procedure UpdateAvatar(c: TICQContact); overload;
function ThisChat: TChatInfo;
function ThisChatUID: TUID;
procedure SetCurrent(idx: Integer);
procedure UserChanged(c: TICQContact);
procedure CloseThisPage;
procedure CloseAllPages(isAuto: boolean = false);
procedure ClosePageAt(idx: Integer);
procedure CloseChat(ci: TChatInfo);
procedure CloseChatWith(c: TICQContact);
procedure CloseChatUID(const UID: TUID);
procedure CloseChatID(ID: Integer);
function SawAllHere: boolean;
function isVisible: boolean;
procedure UpdateContactStatus(cnt: TICQContact = nil);
procedure UpdateChatPreviewArea;
procedure SetStatusbar(const s: String);
function HasEvent(c: TICQContact; ev: Thevent): Boolean;
procedure MoveToEvent(c: TICQContact; ev: Thevent);
procedure SetUnreadEvent(c: TICQContact; ev: Thevent);
function thisContact: TICQContact;
procedure Flash;
procedure Shake;
function Pages2String: String;
procedure LoadPages(const s: String); overload;
procedure LoadPages(const cl: TRnQCList); overload;
procedure UpdateGraphics(c: TICQContact = nil);
procedure Init;
procedure InitDocument;
procedure RefreshTaskbarButtons;
procedure InitSpellCheck;
procedure SpellCheck;
procedure SetupStickersButton;
procedure UpdatePageSettings(cnt: TICQContact);
procedure OpenPage(cnt: TICQContact; focused: Boolean = False); overload;
procedure OpenPage(ID: Integer; const Caption: String); overload;
procedure ClosePage(UID: TUID = ''); overload;
procedure ClosePage(id: Integer); overload;
procedure SwitchToPage(const UID: TUID); overload;
procedure SwitchToPage(id: Integer); overload;
procedure SwitchToNextPage;
procedure SwitchToPrevPage;
procedure UpdateSpelling(data: Variant);
procedure RedrawTabs;
procedure RedrawChatTab(c: TICQContact);
procedure RedrawPluginTab(ci: TChatInfo);
procedure RedrawTab(c: TICQContact; hash, hashadd: LongWord); overload;
procedure RedrawTab(id: Integer; const caption: String; hash: LongWord); overload;
procedure ClearEvents(const UID: TUID = '');
procedure DeleteEvent(const UID: TUID; MsgID: TMsgID);
procedure DeleteEvents(const UID: TUID; st, en: TDateTime);
procedure InitSettings;
procedure InitMsgPreview;
procedure UpdateSmiles;
procedure ReloadSmiles;
procedure ReloadEmojis;
procedure PreloadPickers;
procedure LoadStickers;
procedure LoadSearchResults;
procedure PageFire(const UID: TUID; const name: String; data: Variant); overload;
procedure PageFire(id: Integer; const name: String; data: Variant); overload;
procedure PageCall(const UID: TUID; const method: AnsiString; const args: TParams); overload;
procedure PageCall(id: Integer; const method: AnsiString; const args: TParams); overload;
function GetPage(const UID: TUID): HELEMENT; overload;
function GetPage(id: Integer): HELEMENT; overload;
function GetLastEventTime(const UID: TUID): TDateTime;
procedure AddChatItem(var Params: TParams; var MsgData: TMessageData; Evt: Thevent; Animate: Boolean);
procedure SendChatItems(const UID: TUID; var Params: TParams; Action: TSendAction = SA_APPEND);
procedure HideHistory(const UID: TUID);
procedure ViewInWindow(const title, body: String; const when: String; const formicon: String = '');
procedure ShowServerHistoryNotif(const UID: TUID);
procedure ShowSearchHere;
procedure FinishImage(const link: String);
function GetHistory(const UID: TUID): Thistory;
function GetPluginBounds: TRect;
procedure AddPluginButton(i: Integer);
procedure DelPluginButton(i: Integer);
procedure ModifyPluginButton(i: Integer);
procedure SetSendBtnImage(const pic: TPicName);
procedure ClearAvatar(const UID: TUID);
procedure UpdateAvatar(const UID: TUID); overload;
procedure SetupChatButtons;
procedure SetupSingleBtn(status: Boolean);
procedure SetupFileBtn(status: Boolean);
procedure SetupStickersBtn(status: Boolean);
procedure SetupBuzzBtn(status: Boolean);
procedure ResetHistory;
procedure ApplyTheme;
procedure AddToCurrentInput(const s: String);
function FileUpload(Compress: Boolean; fn: String = ''): String;
procedure MoveToTime(const UID: TUID; time: TDateTime; fast: Boolean = False);
procedure SetFirstUnreadEvent(const UID: TUID; time: TDateTime);
function GetSelectedChatEventsAsHTML: String;
function GetSelectedHistoryEventsAsText(const UID: TUID; Indexes: TArray; AsHTML: Boolean = False): String;
function WholeEventsAreSelected: Boolean;
procedure SetSelection(const UID: TUID; from, to_: TDateTime);
procedure ClearSelection(const UID: TUID);
procedure SelectionAll(const UID: TUID);
function AddEvent_OpenChat(otherHand: TICQContact; ev: Thevent): Boolean;
function AddEvent(Cnt: TICQContact; Ev: Thevent): Boolean; overload;
procedure AddEvent(const UID: TUID; Ev: Thevent); overload;
function UpdateEvent(Cnt: TICQContact; Ev: Thevent): Boolean; overload;
procedure UpdateEvent(const UID: TUID; var Ev: Thevent); overload;
procedure ScrollEvent(d: Integer);
procedure ScrollLine(d: Integer);
procedure ScrollWheel(d: Integer);
procedure ScrollToTop(animate: Boolean);
procedure UpdateMsgStatus(hev: Thevent);
procedure UpdateStatusBar;
procedure UpdateRelTimes;
procedure SetStatusBarHint(const hint: String);
procedure SetSmartReplies(Contact: TICQCOntact; const suggests: TArray);
function ParseMessageBody(const body: String): String;
procedure ReplaceSmileMatch(Sender: TObject; var ReplaceWith: PCREString);
procedure ReplaceOtherMatch(Sender: TObject; var ReplaceWith: PCREString);
procedure FitImage(var ImgWidth: Integer; var ImgHeight: Integer);
function GetReplacement(args: TArray): PCREString;
function ReplaceEmoji(msg: String): String;
procedure DisplayHint(Sender: TObject);
procedure TaskBarThumbButtonClick(Sender: TObject; AButtonID: Integer);
end;
function CHAT_TAB_ADD(Control: Integer; iIcon: HIcon; const TabCaption: String): Integer;
procedure CHAT_TAB_MODIFY(Control: Integer; iIcon: HIcon; const TabCaption: String);
procedure CHAT_TAB_DELETE(Control: Integer);
var
UploadCallbacks: TCallbacks;
TabsIconCache: TObjectDictionary;
EmbeddedImgs: TDictionary;
Emojis: TDictionary, Integer>;
Singles: array of Word;
MouseHook: Cardinal;
implementation
uses
System.Math, Vcl.Clipbrd,
RnQSysUtils, RnQLangs, RDFileUtil, RDUtils, RnQBinUtils, RnQGraphics32, RQUtil, RQThemes, RnQGlobal, RnQPics, RnQDialogs, RnQTips,
Protocols_all, globalLib, iniLib, outboxLib, groupsLib, roasterLib,
ICQConsts, ICQSession, Base64, Murmur2, SpellCheck, HTTPStatus, SciterLib;
const
EmojiExtHints: array [0..8] of String = ('People', 'Nature', 'Foods', 'Activity', 'Travel', 'Objects', 'Symbols', 'Misc', 'Flags');
var
vKeyPicElm: TRnQThemedElementDtls;
msgRegex, youtubeRegex, vimeoRegex: TPerlRegEx;
IsLastParsedEventMine: Boolean = False;
EmojiSize: Integer = 22;
EmojisInARow: Integer = 36;
constructor TChatInfo.Create;
begin
inherited;
end;
destructor TChatInfo.Destroy;
begin
who := nil;
inherited;
end;
{ TChats }
{$WARN UNSAFE_CAST OFF}
function TChats.idxOfUIN(const uin: TUID): Integer;
begin
Result := 0;
while Result < Count do
begin
if TChatInfo(Items[Result]).chatType = CT_IM then
if TChatInfo(Items[Result]).who.equals(uin) then
Exit;
Inc(Result);
end;
Result := -1;
end;
function TChats.idxOfID(ID: Integer): Integer;
begin
Result := 0;
while Result < Count do
begin
if TChatInfo(Items[Result]).chatType = CT_PLUGING then
if TChatInfo(Items[Result]).ID = ID then
Exit;
Inc(Result);
end;
Result := -1;
end;
function TChats.idxOf(c: TICQContact): Integer;
begin
Result := 0;
while Result < Count do
begin
if Assigned(Items[Result]) then
if TChatInfo(Items[Result]).chatType = CT_IM then
if TChatInfo(Items[Result]).who.equals(c) then
Exit;
Inc(Result);
end;
Result := -1;
end;
function TChats.byIdx(i: Integer): TchatInfo;
begin
Result := nil;
if validIdx(i) then
Result := TchatInfo(items[i])
end;
{$WARN UNSAFE_CAST ON}
function TChats.byContact(c: TICQContact): TchatInfo;
begin
Result := byIdx(idxOf(c))
end;
function TChats.byUIN(const UID: TUID): TchatInfo;
begin
Result := byIdx(idxOfUIN(UID))
end;
function TChats.byID(id: Integer): TchatInfo;
begin
Result := byIdx(idxOfID(id))
end;
function TChats.validIdx(i: Integer): boolean;
begin
Result := (i >= 0) and (i < count)
end;
procedure TChats.CheckTypingTimeAll;
begin
if Assigned(Account.AccProto) then
if (Account.AccProto.SupportTypingNotif) and (Account.AccProto.IsSendTypingNotif) then
if Count > 0 then
for var I := Count - 1 downto 0 do
if TChatInfo(Items[I]).chatType = CT_IM then
if Assigned(TChatInfo(Items[I]).who) then
TChatInfo(Items[I]).CheckTypingTime;
end;
{ TTaskbarEx }
function TTaskbarEx.GetFormHandle: HWND;
begin;
Result := UI.Chat.Window;
end;
function TTaskbarEx.GetMainWindowHwnd: HWND;
begin
Result := UI.Chat.Window;
end;
{ TChatBox }
function TChatBox.OpenChat(otherHand: TICQContact; ForceActive: Boolean = False; isAuto: Boolean = False): Boolean;
const
MaxNILpages = 101;
var
i, k: Integer;
wasEmpty, alreadyThere: boolean;
cnt: TICQContact;
firstNILpage, NILcount: Integer;
begin
wasEmpty := chats.Count = 0;
i := chats.idxOf(otherHand);
Result := i < 0;
if Result then
i := NewIMChannel(otherHand);
alreadyThere := otherHand.equals(CurrentContact);
if wasEmpty then
begin
if i >= 0 then
setCurrent(i);
end
else
begin
if not alreadyThere and ForceActive then
if i >= 0 then
setCurrent(i)
else
setCurrent(chats.idxOf(otherHand));
if isAuto then
begin // protection against bruteforce
firstNILpage := -1;
NILcount := 0;
for k := 0 to chats.count - 1 do
begin
if chats.byIdx(k).chatType = CT_IM then
begin
cnt := chats.byIdx(k).who;
if Assigned(cnt) and notInList.exists(cnt) then
begin
Inc(NILcount);
if firstNILpage < 0 then
firstNILpage := k;
end;
end;
end;
if (firstNILpage >= 0) and (NILcount > MaxNILpages) then
ClosePageAt(firstNILpage);
end;
end;
if ForceActive and not Visible then
Show;
end;
function TChatBox.IsChatOpen(otherHand: TICQContact): boolean;
begin
Result := chats.idxOf(otherHand) >= 0
end;
procedure TChatBox.Init;
begin
InitDocument;
InitSpellCheck;
end;
procedure TChatBox.InitDocument;
begin
SetBounds(ChatXY);
theme.smileNotify := ReloadSmiles;
theme.emojisNotify := ReloadEmojis;
if EnableStickers then
begin
PreloadPickers;
LoadStickers;
end;
CreateThreading;
end;
procedure TChatBox.InitSpellCheck;
begin
DoInitSpellCheck;
end;
procedure TChatBox.SpellCheck;
begin
DoSpellCheck;
end;
procedure TChatBox.SetupStickersButton;
begin
SetupStickersBtn(EnableStickers);
end;
procedure TChatBox.SetCurrent(idx: Integer);
var
ch: TchatInfo;
begin
if idx < 0 then
Exit;
ch := chats.byIdx(idx);
if Assigned(ch) then
if ch.chatType = CT_PLUGING then
SwitchToPage(ch.ID)
else
SwitchToPage(ch.who.UID);
end;
procedure TChatBox.UserChanged(c: TICQContact);
var
i: Integer;
ch: TchatInfo;
begin
if c = nil then
Exit;
ch := thisChat;
if ch = nil then
Exit;
i := chats.idxOf(c);
if i < 0 then
Exit;
UpdateContactStatus(c);
RefreshTaskbarButtons;
end;
function TChatBox.OpenOn(c: TICQContact; focus: Boolean = True; pShow: Boolean = True): Boolean;
var
i: Integer;
begin
Result := False;
if c = nil then
Exit;
i := chats.idxOf(c);
if i < 0 then
begin
Result := True;
i := NewIMChannel(c);
end;
if i >= 0 then
SetCurrent(i);
if pShow then
Open(focus);
end;
function TChatBox.NewIMChannel(c: TICQContact): Integer;
var
chat: TchatInfo;
begin
chat := TchatInfo.Create;
chat.who := c;
chat.chatType := CT_IM;
chat.single := singleDefault;
chat.who.typing.bIAmTyping := False;
chats.Add(chat);
Result := chats.Count - 1;
OpenPage(c);
// ChatBox.CheckServerHistory(c);
Resize;
ActionManager.Execute(AK_SAVECONFIG, SaveDelay);
RedrawChatTab(c);
RefreshTaskbarButtons;
end;
procedure TChatBox.UpdateAvatar(c: TICQContact);
var
ci: TchatInfo;
i: Integer;
begin
i := chats.idxOf(c);
if i >= 0 then
begin
ci := chats.byIdx(i);
if Assigned(ci) and Assigned(ci.who) then
if not avatarShowInChat or (c.icon.ToShow = IS_NONE) then
ClearAvatar(ci.who.UID)
else
UpdateAvatar(ci.who.UID);
end;
end;
procedure TchatInfo.CheckTypingTime;
begin
try
if (chatType = CT_IM) and Assigned(who) then
if (who.typing.bIAmTyping) and ((Now - who.typing.TypingTime) * SecsPerDay > TypingInterval) then
Account.AccProto.InputChangedFor(who, False, True);
except end;
end;
function TChatBox.ThisChat: TchatInfo;
begin
if not Assigned(Chats) or (Chats.Count = 0) then
Result := nil
else
Result := Chats.byContact(CurrentContact);
end;
function TChatBox.ThisContact: TICQContact;
var
ch: TchatInfo;
begin
ch := thisChat;
if ch = nil then
Result := nil
else if ch.chatType = CT_IM then
thisContact := ch.who
else
Result := nil;
end;
function TChatBox.ThisChatUID: TUID;
var
cnt: TICQContact;
begin
if not Running then
begin
Result := '';
Exit;
end;
cnt := ThisContact;
if Assigned(cnt) then
Result := cnt.UID
else
Result := '';
end;
procedure TChatBox.CloseThisPage;
var
ch: TchatInfo;
begin
ch := thisChat;
if Assigned(ch) then
CloseChat(ch);
end;
procedure TChatBox.UpdateGraphics(c: TICQContact = nil);
var
ch: TchatInfo;
begin
ch := thisChat;
if ch = nil then
Exit;
if ch.chatType = CT_PLUGING then
Exit;
if Assigned(c) and not ch.who.equals(c) then
Exit;
SetupSingleBtn(ch.single);
UpdateContactStatus;
UpdateStatusBar;
RefreshTaskbarButtons;
end;
function TChatBox.SawAllHere: Boolean;
const
ClearEvents = [EK_msg, EK_auth, EK_authDenied, EK_addedYou];
var
c: TICQContact;
ch: TChatInfo;
k: Integer;
ev0: Thevent;
Found: Boolean;
begin
Result := False;
Found := False;
if eventQ.Count = 0 then
Exit;
ch := thisChat;
if (ch = nil) or (ch.chatType <> CT_IM) then
Exit;
c := ch.who;
k := -1;
repeat
k := eventQ.getNextEventFor(c, k);
if (k >= 0) and (k < eventQ.count) then
begin
ev0 := eventQ.items[k];
if ev0.Kind in ClearEvents then
begin
Found := True;
eventQ.removeAt(k);
if BE_history in behaviour[ev0.Kind].trig then
if not UI.Chat.HasEvent(c, ev0) then
UI.Chat.AddEvent(c, ev0.clone);
try
FreeAndNil(ev0);
except end;
end else
Inc(k);
end else
k := -1;
until (k < 0);
if Found then
begin
Result := True;
RedrawChatTab(c);
roasterLib.UpdateInPlace(c);
ActionManager.Execute(AK_SAVEINBOX, SaveDelay);
end;
UI.Tips.Remove(c);
end;
procedure TChatBox.Open(Focus: Boolean = True);
var
Win: HWND;
begin
if chats.Count = 0 then
Exit;
if not Visible then
Win := GetForegroundWindow
else
Win := 0;
Show;
if (Win > 0) and not Focus then
ForceForegroundWindow(Win);
if Focus then
Activate(True);
end;
function TChatBox.IsVisible: Boolean;
begin
Result := Visible and (GetForegroundWindow = Window);
end;
procedure TChatBox.UpdateContactStatus(cnt: TICQContact = nil);
begin
if cnt = nil then
cnt := thisContact;
if cnt = nil then
begin
SetSendBtnImage(status2imgName(byte(SC_UNK), False));
Exit;
end;
SetSendBtnImage(rosterImgNameFor(cnt));
if (thisChat.chatType = CT_IM) and not (thisChat.who = nil) then
SetupBuzzBtn(thisChat.who.CanBuzz)
else
SetupBuzzBtn(False);
RedrawChatTab(cnt);
end;
procedure TChatBox.ClosePageAt(idx: Integer);
var
ch: TchatInfo;
cnt: TICQContact;
id: Integer;
ltype: TChatType;
begin
ch := chats.byIdx(idx);
if ch = nil then
Exit;
cnt := ch.who;
id := ch.ID;
ltype := ch.chatType;
LastContact := cnt;
if ltype = CT_PLUGING then
plugins.castEv(PE_CLOSETAB, ch.ID)
else if ltype = CT_IM then
Account.AccProto.InputChangedFor(ch.who, True);
try
chats.Items[idx] := nil;
chats.Delete(idx);
FreeAndNil(ch);
except end;
if ltype = CT_PLUGING then
ClosePage(id)
else if ltype = CT_IM then
ClosePage(cnt.UID);
if not (UserStartTime = 0) then
ActionManager.Execute(AK_SAVECONFIG, SaveDelay);
RefreshTaskbarButtons;
end;
procedure TChatBox.CloseChat(ci: TchatInfo);
begin
if ci = nil then
Exit;
if ci.chatType = CT_IM then
ClosePageAt(chats.idxOf(ci.who))
else
ClosePageAt(chats.idxOfID(ci.ID));
end;
procedure TChatBox.CloseChatWith(c: TICQContact);
begin
ClosePageAt(chats.idxOf(c))
end;
procedure TChatBox.CloseChatUID(const UID: TUID);
begin
ClosePageAt(chats.idxOfUIN(UID))
end;
procedure TChatBox.CloseChatID(ID: Integer);
begin
ClosePageAt(chats.idxOfID(ID));
end;
procedure TChatBox.CloseAllPages(isAuto: boolean = false);
begin
if isAuto then
PagesEnumStr := Pages2String
else
PagesEnumStr := '';
while chats.Count > 0 do
ClosePageAt(0);
end;
procedure TChatBox.UpdateChatPreviewArea;
begin
if not Assigned(TaskBar) then
Exit;
TaskBar.PreviewClipRegion.Height := Min(200, GetHeight);
TaskBar.PreviewClipRegion.Width := Min(420, GetWidth);
TaskBar.ApplyClipAreaChanges;
end;
procedure TChatBox.SetStatusbar(const s: String);
var
hint: String;
begin
if IsUploading then
hint := GetTranslation('Uploading file') + ': ' + IntToStr(Trunc(UploadedSize / UploadSize * 100)) + '%'
else
hint := s;
SetStatusbarHint(hint);
if TOSVersion.Check(6, 1) then
if Assigned(TaskBar) then
with TaskBar do
begin
if (UploadedSize < UploadSize) and IsUploading then
begin
ProgressState := TTaskBarProgressState.Normal;
ProgressValue := Trunc(UploadedSize / UploadSize * 100);
end
else
begin
ProgressState := TTaskBarProgressState.None;
ProgressValue := 0;
end;
ApplyProgressChanges;
end;
end;
procedure TChatBox.MoveToEvent(c: TICQContact; ev: Thevent);
begin
MoveToTime(c.UID, ev.when);
end;
function TChatBox.HasEvent(c: TICQContact; ev: Thevent): Boolean;
var
ch: TchatInfo;
tlast: TDateTime;
begin
Result := False;
ch := chats.byContact(c);
if not Assigned(ch) then
Exit;
if logpref.writehistory and (BE_save in behaviour[ev.kind].trig) then
Result := True
else
begin
tlast := GetLastEventTime(c.UID);
if tlast > 0 then
Result := CompareDateTime(tlast, ev.when) >= 0;
end;
end;
procedure TChatBox.SetUnreadEvent(c: TICQContact; ev: Thevent);
var
ch: TchatInfo;
begin
ch := chats.byContact(c);
if not Assigned(ch) then
Exit;
SetFirstUnreadEvent(c.UID, ev.when);
end;
procedure TChatBox.Flash;
var
Rec: FLASHWINFO;
begin
// if doFlashChat then
begin
Rec.cbSize := SizeOf(Rec);
Rec.hwnd := Window;
Rec.dwFlags := FLASHW_CAPTION or FLASHW_TRAY or FLASHW_TIMERNOFG;
Rec.dwTimeout := 0;
Rec.uCount := DWord(-1);
FlashWindowEx(Rec);
end;
end;
procedure TChatBox.Shake;
const
MAXDELTA = 8;
SHAKETIMES = 150;
var
oRect, wRect: TRect;
begin
GetWindowRect(Window, wRect);
oRect := wRect;
Randomize;
TThread.CreateAnonymousThread(procedure
begin
for var cnt := 0 to SHAKETIMES do
begin
wRect := oRect;
System.Types.OffsetRect(wRect, Round(Random(2 * MAXDELTA) - MAXDELTA), 0);
MoveWindow(Window, wRect.Left, wRect.Top, wRect.Right - wRect.Left, wRect.Bottom - wRect.Top, True);
Sleep(10);
end;
MoveWindow(Window, oRect.Left, oRect.Top, oRect.Right - oRect.Left, oRect.Bottom - oRect.Top, True);
end).Start;
end;
function TChatBox.Pages2String: String;
var
ch: TchatInfo;
begin
if (UserStartTime = 0) and (chats.Count = 0) then
Result := PagesEnumStr
else
begin
Result := '';
for var I := 0 to chats.Count - 1 do
try
ch := chats.byIdx(I);
if Assigned(ch) and (ch.chatType = CT_IM) then
Result := Result + ch.who.UID + #10;
except end;
Result := Result.Trim([#10]);
end;
end;
procedure TChatBox.LoadPages(const s: String);
var
Contact: TICQContact;
Pages: TArray;
begin
Pages := s.Split([#10]);
for var I := 0 to Length(Pages) - 1 do
begin
Contact := Account.AccProto.GetContact(Pages[I]);
if Assigned(Contact) and (chats.idxOf(Contact) < 0) then
NewIMChannel(Contact);
end;
Open(True);
end;
procedure TChatBox.LoadPages(const cl: TRnQCList);
begin
for var Contact in cl do
if Assigned(Contact) and (chats.idxOf(Contact) < 0) then
NewIMChannel(Contact);
Open(True);
end;
// ----------------------------------------------------------------------------------------------------------------------
function CHAT_TAB_ADD(Control: Integer; iIcon: HIcon; const TabCaption: string): Integer;
var
chat: TchatInfo;
i: Integer;
begin
i := UI.Chat.chats.idxOfID(Control);
if UI.Chat.chats.validIdx(i) then
begin
UI.Chat.SetCurrent(i);
UI.Chat.Resize;
Result := -1;
Exit;
end;
with UI.Chat do
begin
chat := TchatInfo.Create;
chat.who := nil;
chat.chatType := CT_PLUGING;
chat.single := singleDefault;
chat.lastInputText := TabCaption;
chats.Add(chat);
chat.ID := Control;
if not (iIcon = 0) then
theme.addHIco('plugintab' + IntToStr(chat.ID), iIcon, true);
OpenPage(chat.ID, TabCaption);
RedrawPluginTab(chat);
Resize;
RefreshTaskbarButtons;
ActionManager.Execute(AK_SAVECONFIG, SaveDelay);
Result := Integer(Window);
end;
end;
procedure CHAT_TAB_MODIFY(Control: Integer; iIcon: HIcon; const TabCaption: string);
var
chat: TchatInfo;
begin
chat := nil;
for var I := 0 to UI.Chat.chats.count - 1 do
begin
if UI.Chat.chats.byIdx(I).ID = Control then
begin
chat := UI.Chat.chats.byIdx(I);
break;
end;
end;
if chat = nil then
Exit;
if not (iIcon = 0) then
theme.addHIco('plugintab' + IntToStr(chat.ID), iIcon, true);
chat.lastInputText := TabCaption;
UI.Chat.RedrawPluginTab(chat);
UI.Chat.RefreshTaskbarButtons;
end;
procedure CHAT_TAB_DELETE(Control: Integer);
begin
for var I := 0 to UI.Chat.chats.count - 1 do
begin
if UI.Chat.chats.byIdx(i).ID = Control then
begin
UI.Chat.ClosePageAt(i);
break;
end;
end;
end;
//procedure TChatBox.WMWINDOWPOSCHANGING(var msg: TWMWINDOWPOSCHANGING);
//const
// chkLeft = true;
// chkRight = true;
// chkTop = true;
// chkBottom = true;
//var
// // rWorkArea: TRect;
// rMainRect: TRect;
// StickAt: Word;
//begin
//// if Assigned(mainDlg.RnQmain) then
//// if mainDlg.RnQmain.visible then
//// begin
//// StickAt := 15; // StrToInt(edStickAt.Text);
//// rMainRect := mainDlg.RnQmain.BoundsRect;
//// // SystemParametersInfo
//// // (SPI_GETWORKAREA, 0, @rWorkArea, 0);
////
//// with msg.WindowPos^ do
//// begin
//// if chkLeft then
//// // if ABS(x - rWorkArea.Left) <= StickAt then begin
//// // x := rWorkArea.Left;
//// if (ABS(X - rMainRect.Right) <= StickAt) and (Y < rMainRect.Bottom) and (Y + cy > rMainRect.Top) then
//// X := rMainRect.Right;
////
//// if chkRight then
//// // if abs(x + cx - rWorkArea.Right) <= StickAt then begin
//// // x := rWorkArea.Right - cx;
//// if (ABS(X + cx - rMainRect.Left) <= StickAt) and (Y < rMainRect.Bottom) and (Y + cy > rMainRect.Top) then
//// X := rMainRect.Left - cx;
////
//// if chkTop then
//// if (ABS(Y - rMainRect.Bottom) <= StickAt) and (X < rMainRect.Right) and (X + cx > rMainRect.Left) then
//// Y := rMainRect.Bottom;
////
//// if chkBottom then
//// if (ABS(Y + cy - rMainRect.Top) <= StickAt) and (X < rMainRect.Right) and (X + cx > rMainRect.Left) then
//// Y := rMainRect.Top - cy;
//// end;
//// end;
// inherited;
//end;
procedure TChatBox.DisplayHint(Sender: TObject);
begin
if Assigned(UI.Chat) and UI.Chat.Visible then
UI.Chat.SetStatusbar(Application.Hint);
end;
procedure TChatBox.TaskBarThumbButtonClick(Sender: TObject; AButtonID: Integer);
begin
ODS('AButtonID: ' + inttostr(AButtonID));
if AButtonID > chats.Count then
Exit;
SetCurrent(AButtonID);
Open(true);
end;
procedure TChatBox.RefreshTaskbarButtons;
var
taskBtn: TThumbBarButton;
ci: TchatInfo;
hi: HICON;
cnt: Integer;
bmp: TRnQBitmap;
ev: Thevent;
begin
if not Running then Exit;
if not TOSVersion.Check(6, 1) then Exit;
if not Assigned(TaskBar) or not Assigned(TaskBar.TaskBarButtons) or (TaskBar.TaskBarButtons.Count = 0) then Exit;
if not UI.Chat.Visible then Exit;
cnt := Min(7, chats.Count);
TaskBar.TaskBarButtons.BeginUpdate;
for var I := 0 to 6 do
begin
taskBtn := TaskBar.TaskBarButtons[i];
if Assigned(taskBtn.Icon) and taskBtn.Icon.HandleAllocated then
DestroyIcon(taskBtn.Icon.ReleaseHandle);
if (cnt = 0) or (i > cnt - 1) then
begin
taskBtn.Hint := '';
taskBtn.ButtonState := [TThumbButtonState.Hidden];
end
else
begin
taskBtn.ButtonState := [TThumbButtonState.Enabled, TThumbButtonState.DismissOnClick];
ci := TchatInfo(chats.byIdx(i));
if not Assigned(ci) then
Continue;
case ci.chatType of
CT_IM:
begin
if not (taskBtn.Hint = ci.who.displayed) then
taskBtn.Hint := ci.who.displayed;
ev := eventQ.firstEventFor(ci.who);
if (ev <> nil) then
begin
theme.pic2ico(RQteFormIcon, ev.pic, taskBtn.Icon);
Continue;
end;
end;
CT_PLUGING:
begin
if not (taskBtn.Hint = ci.lastInputText) then
taskBtn.Hint := ci.lastInputText;
theme.pic2ico(RQteFormIcon, 'plugintab' + IntToStr(ci.ID), taskBtn.Icon);
Continue;
end;
end;
if avatarShowInTaskBar and Assigned(ci.who.icon.Bmp) then
begin
if (ci.who.icon.Bmp.Animated) then
bmp := ci.who.icon.Bmp.CloneFrame(0)
else
bmp := ci.who.icon.Bmp.CloneAll;
StretchPic(bmp, 16, 16);
bmp.GetHICON(hi);
taskBtn.Icon.Handle := hi;
FreeAndNil(bmp);
end else
theme.pic2ico(RQteFormIcon, ci.who.statusImg, taskBtn.Icon);
end;
end;
TaskBar.TaskBarButtons.EndUpdate;
TaskBar.ApplyButtonsChanges;
end;
{ Native Methods }
class procedure TChatMethods.RegisterMethods(var ReturnValue: TSciterValue);
begin
AddMethod('OnChatShow', OnChatShow);
AddMethod('OnChatHide', OnChatHide);
AddMethod('OnChatResize', OnChatResize);
AddMethod('OnChatActivate', OnChatActivate);
AddMethod('OnChatMouseEnter', OnChatMouseEnter);
AddMethod('OnChatMouseLeave', OnChatMouseLeave);
AddMethod('UpdateChatXY', UpdateChatXY);
AddMethod('AttachWindow', AttachWindow);
AddMethod('DetachWindow', DetachWindow);
AddMethod('LoadHistory', LoadHistory);
AddMethod('UpdateSelection', UpdateSelection);
AddMethod('UploadLastSnapshot', UploadLastSnapshot);
AddMethod('DeleteSnapshot', DeleteSnapshot);
AddMethod('UploadFiles', UploadFiles);
AddMethod('GetLinkInfo', GetLinkInfo);
AddMethod('GetYoutubeLinks', GetYoutubeLinks);
AddMethod('GetVimeoLinks', GetVimeoLinks);
AddMethod('GetVolumeLevel', GetVolumeLevel);
AddMethod('SaveVolumeLevel', SaveVolumeLevel);
AddMethod('RequestChatPageSettings', RequestChatPageSettings);
AddMethod('ChatPageSelected', ChatPageSelected);
AddMethod('ChatPageDeselected', ChatPageDeselected);
AddMethod('PluginPageSelected', PluginPageSelected);
AddMethod('PluginPageDeselected', PluginPageDeselected);
AddMethod('SaveTabsOrder', SaveTabsOrder);
//AddMethod('SetTabDragging', SetTabDragging);
AddMethod('CloseChatPage', CloseChatPage);
AddMethod('ClosePluginPage', ClosePluginPage);
//AddMethod('AddUIN2CL', AddUIN2CL);
AddMethod('CopyLink', CopyLink);
AddMethod('SavePicture', SavePicture);
AddMethod('GetEvent', GetEvent);
AddMethod('SaveAs', SaveAs);
AddMethod('AddLinkToFav', AddLinkToFav);
AddMethod('DeleteMessages', DeleteMessages);
AddMethod('GetReactions', GetReactions);
AddMethod('AddToAntispam', AddToAntispam);
AddMethod('ToggleSmiles', ToggleSmiles);
AddMethod('ToggleRelTimes', ToggleRelTimes);
AddMethod('RealizeEvents', RealizeEvents);
AddMethod('StoreSplit', StoreSplit);
AddMethod('InputChangedFor', InputChangedFor);
AddMethod('GetMessageByIdx', GetMessageByIdx);
AddMethod('WrapText', WrapText);
AddMethod('SendChatMessage', SendChatMessage);
AddMethod('EditChatMessage', EditChatMessage);
AddMethod('GetChatMessageText', GetChatMessageText);
AddMethod('ClosePages', ClosePages);
AddMethod('UploadFile', UploadFile);
AddMethod('SaveEmbeddedFile', SaveEmbeddedFile);
AddMethod('ChatButtonClick', ChatButtonClick);
AddMethod('PluginButtonClick', PluginButtonClick);
AddMethod('ToggleTranslit', ToggleTranslit);
//AddMethod('QIPPwd', QIPPwd);
AddMethod('SendStickerToCurrent', SendStickerToCurrent);
AddMethod('GetStoreStickerPacks', GetStoreStickerPacks);
AddMethod('GetStickerPacks', GetStickerPacks);
AddMethod('SearchStickerPacks', SearchStickerPacks);
AddMethod('BuyStickerPack', BuyStickerPack);
AddMethod('RemoveStickerPack', RemoveStickerPack);
AddMethod('GetStickerPackContent', GetStickerPackContent);
AddMethod('SearchStickersByKeywords', SearchStickersByKeywords);
AddMethod('SpanEmojis', SpanEmojis);
AddMethod('GetSpellingSuggestions', GetSpellingSuggestions);
AddMethod('GetEventHeaderTime', GetEventHeaderTime);
AddMethod('AddReaction', AddReaction);
AddMethod('RemoveReaction', RemoveReaction);
AddMethod('GetMyReaction', GetMyReaction);
inherited;
end;
class procedure TChatMethods.OnChatActivate(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
Active: Boolean;
I: Integer;
begin
I := 0;
API.ValueIntData(argv, I);
Active := I = 1;
if Assigned(UI) and Assigned(UI.Chat) then
begin
if Active and AutoConsumeEvents then
UI.Chat.SawAllHere;
end else
Exit;
// Wait for active window to switch
TThread.CreateAnonymousThread(procedure
begin
TThread.Queue(nil, procedure
begin
ApplyTransparency(AW_CHAT);
end);
end).Start;
if Active and Assigned(UI.Chat.TaskBar) and Assigned(UI.Chat.TaskBar.TaskBar) then
UI.Chat.TaskBar.ActivateTab;
end;
class procedure TChatMethods.OnChatMouseEnter(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
Style: Integer;
begin
if Running and Assigned(UI.Chat) then
if transparency.forChat and transparency.chgOnMouse then
begin
Style := GetWindowLong(UI.Chat.Window, GWL_EXSTYLE);
if (Style and WS_EX_LAYERED) = 0 then
SetWindowLong(UI.Chat.Window, GWL_EXSTYLE, Style or WS_EX_LAYERED);
SetLayeredWindowAttributes(UI.Chat.Window, 0, transparency.active, LWA_ALPHA);
end;
end;
class procedure TChatMethods.OnChatMouseLeave(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
ExStyle: Integer;
begin
if Running and Assigned(UI.Chat) then
if UI.Chat.Window <> GetForegroundWindow then
if transparency.forChat and transparency.chgOnMouse then
begin
ExStyle := GetWindowLong(UI.Chat.Window, GWL_EXSTYLE);
if (ExStyle and WS_EX_LAYERED) = 0 then
SetWindowLong(UI.Chat.Window, GWL_EXSTYLE, ExStyle or WS_EX_LAYERED);
SetLayeredWindowAttributes(UI.Chat.Window, 0, transparency.inactive, LWA_ALPHA);
end;
end;
function applyHtmlFont(fnt: Tfont; const s: string): string;
var
h, q: string;
begin
h := '';
q := '';
if fsItalic in fnt.style then
begin
h := h + '';
q := '' + q;
end;
if fsBold in fnt.style then
begin
h := h + '';
q := '' + q;
end;
result := h + s + q;
end;
function str2html2(const s: string): string;
begin
result := template(s, ['&', '&', '<', '<', '>', '>', CRLF, '
', #13, '
', #10, '
']);
end;
function color2html(color: TColor): AnsiString;
begin
// if not ColorToIdent(Color, Result) then
begin
color := ABCD_ADCB(ColorToRGB(color));
result := '#' + IntToHex(color, 6);
end;
end;
function GetEventsAsText(const UID: TUID; var Events: Thevents; AsHTML: Boolean = False): String;
const
HTMLTemplate = '' + CRLF + CRLF +
'' + CRLF +
'' + CRLF +
' %TITLE%' + CRLF +
' ' + CRLF +
' ' + CRLF +
'' + CRLF +
'' + CRLF +
'%CONTENT% ' + CRLF +
'' + CRLF +
'';
var
Ev: Thevent;
Content: String;
HTMLElement: String;
Tmp, BodyText, Body: String;
Header: THeader;
function MakeSafeUIN(UIN: String): String;
begin
Result := ReplaceText(UIN, '@', '');
Result := ReplaceText(Result, '.', '');
end;
function MakeElement(const UIN: TUID; IsMy: Boolean): String;
begin
Result := ' .uin' + UTF(UIN) + ' {' + CRLF +
' color: #333;' + CRLF;
Result := Result + ' font-family: "Segoe UI";' + CRLF;
Result := Result + ' font-size: 14px;' + CRLF;
// if fsBold in Font.Style then
// Result := Result + ' font-weight: 500;' + CRLF;
// if fsItalic in Font.Style then
// Result := Result + ' text-decoration: italic;' + CRLF;
// if fsUnderline in Font.Style then
// Result := Result + ' text-decoration: underline;' + CRLF;
Result := Result + ' }' + CRLF;
Result := Result + ' .uin' + UIN + ' .title {' + CRLF;
if IsMy then
Result := Result + ' color: #283593;' + CRLF
else
Result := Result + ' color: #844103;' + CRLF;
Result := Result + ' }';
end;
begin
Result := '';
Content := '';
if Assigned(Events) and (Length(Events) > 0) then
for Ev in Events do
if Assigned(Ev) then
try
Header := Ev.GetHeaderTexts;
BodyText := Ev.GetBodyText;
if not (BodyText = '') then
Body := Trim(BodyText)
else if (Length(Ev.GetBodyBin) > 0) and (Ev.Kind = EK_msg) then
Body := '[' + GetTranslation('Binary data, %u B', [Length(Ev.GetBodyBin)]) + ']'
else
Body := '';
Tmp := '';
if AsHTML then
begin
Tmp := '
';
if not (Ev.kind = EK_msg) then
Tmp := Tmp + '[' + GetTranslation(event2ShowStr[Ev.kind]) + '] ';
Tmp := Tmp + Header.Date + ', ' +
Header.What + IfThen(Length(Header.Prefix) > 0, ' ' + Header.Prefix, '') + '' + '
' +
str2html2(Body) + '';
end
else
begin
if not (Ev.kind = EK_msg) then
Tmp := Tmp + '[' + GetTranslation(event2ShowStr[Ev.kind]) + '] ';
Tmp := Tmp + Header.Date + ', ' + Header.What + IfThen(Length(Header.Prefix) > 0, ' ' + Header.Prefix, '') + CRLF + Body;
end;
Content := Content + Tmp + CRLF;
except end;
if AsHTML then
begin
HTMLElement := GetTranslation('History between [%s] and [%s]', [Account.AccProto.MyInfo.Displayed, Account.AccProto.GetContact(UID).Displayed]);
Result := StringReplace(HTMLTemplate, '%TITLE%', HTMLElement, []);
HTMLElement := ' body {' + CRLF +
' background-color: ' + color2html(theme.GetColor('history.bg', clWindow)) + ';' + CRLF +
' }' + CRLF +
' div {' + CRLF +
' margin-top: 5px' + CRLF +
' }';
Result := StringReplace(Result, '%BODY%', HTMLElement, []);
HTMLElement := MakeElement(Account.AccProto.MyAccNum, True);
Result := StringReplace(Result, '%HOST%', HTMLElement, []);
HTMLElement := MakeElement(MakeSafeUIN(UID), False);
Result := StringReplace(Result, '%GUEST%', HTMLElement, []);
Result := StringReplace(Result, '%CONTENT%', Content.Trim, []);
end else
Result := Content;
Content := '';
HTMLElement := '';
end;
function TChatBox.GetSelectedChatEventsAsHTML: String;
var
SOS, EOS: TDateTime;
History: Thistory;
Events: Thevents;
Ev: Thevent;
begin
Result := '';
if (CurrentContact = nil) or (StartSel = 0) or (EndSel = 0) then
Exit;
if CompareDateTime(EndSel, StartSel) >= 0 then
begin
SOS := StartSel;
EOS := EndSel;
end
else
begin
SOS := EndSel;
EOS := StartSel;
end;
History := GetHistory(CurrentContact.UID);
Events := nil;
if Assigned(History) then
Events := History.GetTimeRange(SOS, EOS);
if Assigned(Events) then
begin
Result := GetEventsAsText(CurrentContact.UID, Events, True);
for Ev in Events do
FreeAndNil(Ev);
Finalize(Events);
end;
end;
function TChatBox.GetSelectedHistoryEventsAsText(const UID: TUID; Indexes: TArray; AsHTML: Boolean = False): String;
var
Ev: Thevent;
Events: Thevents;
AllEvents: Thevents;
History: Thistory;
begin
History := Thistory.Create(UID);
AllEvents := History.GetAllEvents;
for var I in Indexes do
Events := Events + [AllEvents[I]];
Result := GetEventsAsText(UID, Events, AsHTML);
for Ev in AllEvents do
FreeAndNil(Ev);
Finalize(AllEvents);
FreeAndNil(History);
end;
(*
PK_CRYPTED:
if enterPwdDlg(histcrypt.pwd) then
histcrypt.pwdkey := calculate_KEY(histcrypt.pwd);
*)
procedure TChatBox.MoveToTime(const UID: TUID; time: TDateTime; fast: Boolean = False);
var
ffs: TFormatSettings;
begin
ffs := TFormatSettings.Create(LOCALE_USER_DEFAULT);
ffs.DecimalSeparator := '.';
PageCall(UID, 'moveToTime', [FloatToStr(time, ffs), fast]);
end;
procedure TChatBox.SetFirstUnreadEvent(const UID: TUID; time: TDateTime);
var
ffs: TFormatSettings;
begin
ffs := TFormatSettings.Create(LOCALE_USER_DEFAULT);
ffs.DecimalSeparator := '.';
PageCall(UID, 'setFirstUnreadEvent', [FloatToStr(time, ffs)]);
end;
function TChatBox.WholeEventsAreSelected: Boolean;
begin
Result := (StartSel > 0) and (EndSel > 0) and IsWholeEvents
end;
procedure TChatBox.SetSelection(const UID: TUID; from, to_: TDateTime);
var
args: TParams;
ffs: TFormatSettings;
begin
StartSel := from;
EndSel := to_;
IsWholeEvents := True;
SetLength(args, 2);
ffs := TFormatSettings.Create(LOCALE_USER_DEFAULT);
ffs.DecimalSeparator := '.';
args[0] := FloatToStr(from, ffs);
args[1] := FloatToStr(to_, ffs);
PageCall(UID, 'setSelection', args);
end;
procedure TChatBox.ClearSelection(const UID: TUID);
begin
StartSel := 0;
IsWholeEvents := False;
PageCall(UID, 'clearSelection', []);
end;
procedure TChatBox.SelectionAll(const UID: TUID);
begin
PageCall(UID, 'selectAll', []);
end;
function TChatBox.AddEvent_OpenChat(otherHand: TICQContact; ev: Thevent): Boolean;
begin
OpenChat(otherHand);
Result := AddEvent(otherHand, ev);
end;
function TChatBox.AddEvent(Cnt: TICQContact; Ev: Thevent): Boolean;
var
ch: TchatInfo;
begin
Result := False;
ch := chats.byContact(Cnt);
if Assigned(ch) then
begin
Result := True;
AddEvent(Cnt.UID, Ev);
end;
FreeAndNil(Ev);
end;
function TChatBox.UpdateEvent(Cnt: TICQContact; Ev: Thevent): Boolean;
var
ch: TchatInfo;
begin
Result := False;
ch := chats.byContact(Cnt);
if Assigned(ch) then
begin
Result := True;
UpdateEvent(Cnt.UID, Ev);
end;
FreeAndNil(Ev);
end;
procedure TChatBox.AddEvent(const UID: TUID; Ev: Thevent);
var
Params: TParams;
MsgData: TMessageData;
begin
AddChatItem(Params, MsgData, Ev, True);
SendChatItems(UID, Params);
end;
procedure TChatBox.UpdateEvent(const UID: TUID; var Ev: Thevent);
var
Params: TParams;
MsgData: TMessageData;
begin
AddChatItem(Params, MsgData, Ev, False);
SendChatItems(UID, Params, SA_UPDATE);
end;
procedure TChatBox.UpdateMsgStatus(hev: Thevent);
var
Status: TMessageStatus;
EvPic: TSprite;
ffs: TFormatSettings;
begin
ffs := TFormatSettings.Create(LOCALE_USER_DEFAULT);
ffs.DecimalSeparator := '.';
Status.when := FloatToStr(hev.when, ffs);
Status.msgid := UIntToStr(hev.ID);
EvPic := MakeSprite(hev.pic);
Status.eventimg := UI.RecordToVar(EvPic);
PageFire(hev.chat.UID, 'updatemsgstatus', UI.RecordToVar(Status));
end;
procedure TChatBox.UpdateStatusBar;
var
Trlt: Boolean;
EncPic: TPicName;
EncHint: String;
begin
Trlt := Assigned(TranslitList) and (TranslitList.Count > 0) and Assigned(CurrentContact) and CurrentContact.SendTransl;
EncPic := '';
EncHint := GetTranslation('Encryption status for current contact');
if Assigned(CurrentContact) then
if Account.AccProto.UseCryptMsg and (
(CurrentContact.crypt.SupportCryptMsg) or
(CurrentContact.crypt.SupportEcc and Account.AccProto.UseEccCryptMsg)) then
begin
if CurrentContact.crypt.SupportEcc then
begin
EncPic := PIC_KEY_ECC;
EncHint := EncHint + ' [ECDH & AES 256-bit]';
end else if CurrentContact.crypt.SupportCryptMsg then
begin
EncPic := PIC_KEY;
EncHint := EncHint + ' [AES 256-bit]';
end else if CAPS_big_QIP_Secure in CurrentContact.CapabilitiesBig then
begin
// if CurrentContact.crypt.qippwd > 0 then
// EncPic1 := PIC_KEY;
// EncPic2 := PIC_CLI_QIP;
// EncHint := EncHint + ' [QIP]';
end;
end;
Call('updateStatusBar', [Account.outbox.stFor(CurrentContact), Trlt, EncPic, EncHint]);
end;
procedure TChatBox.UpdateRelTimes;
begin
Call('updateRelTimes', []);
end;
procedure TChatBox.SetStatusBarHint(const hint: String);
begin
Call('setStatusBarHint', [hint]);
end;
procedure TChatBox.SetSmartReplies(Contact: TICQCOntact; const suggests: TArray);
begin
Call('setSmartReplies', [Contact.UID, suggests]);
end;
procedure TChatBox.ScrollEvent(d: Integer);
begin
PageCall('', 'scrollEvent', [d]);
end;
procedure TChatBox.ScrollLine(d: Integer);
begin
PageCall('', 'scrollLine', [d]);
end;
procedure TChatBox.ScrollWheel(d: Integer);
begin
PageCall('', 'scrollWheel', [d]);
end;
procedure TChatBox.ScrollToTop(animate: Boolean);
begin
PageCall('', 'scrollToTop', [animate]);
end;
procedure TChatBox.Resize;
//var
// ch: TchatInfo;
begin
UpdateChatPreviewArea;
// ch := ThisChat;
// if ch = nil then
// Exit;
// if ch.chatType = CT_PLUGING then
// plugins.castEv(PE_SELECTTAB, ch.ID)
end;
class procedure TChatMethods.UpdateChatXY(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
Rect: Variant;
begin
S2V(argv, Rect);
ChatXY.Left := Rect[0];
ChatXY.Top := Rect[1];
ChatXY.Width := Rect[2];
ChatXY.Height := Rect[3];
end;
class procedure TChatMethods.AttachWindow(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
El: HELEMENT;
Page: Integer;
begin
El := nil;
API.SciterElementUnwrap(argv, El);
Inc(argv);
API.ValueIntData(argv, Page);
API.SciterAttachHwndToElement(El, Page);
end;
class procedure TChatMethods.DetachWindow(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
El: HELEMENT;
Pb: Cardinal;
begin
El := nil;
API.SciterElementUnwrap(argv, El);
API.SciterAttachHwndToElement(El, 0);
end;
class procedure TChatMethods.OnChatShow(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
with UI.Chat do
if Assigned(TaskBar) then
begin
if Assigned(TaskBar.TaskBarButtons) and (TaskBar.TaskBarButtons.Count = 0) then
for var I := 0 to 6 do
TaskBar.TaskbarButtons.Add;
TaskBar.Initialize;
TaskBar.CheckApplyChanges;
RefreshTaskbarButtons;
UpdateChatPreviewArea;
UpdateContactStatus;
end;
end;
class procedure TChatMethods.OnChatHide(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
if Assigned(UI.Chat.TaskBar) then
UI.Chat.TaskBar.UnregisterTab;
TabsIconCache.Clear;
if UI.Chat.Chats.Count = 0 then
EmbeddedImgs.Clear;
end;
class procedure TChatMethods.OnChatResize(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
if Assigned(UI) and Assigned(UI.Chat) then
UI.Chat.Resize;
end;
class procedure TChatMethods.LoadHistory(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
UID: TUID;
offset, msgs, evId, evCnt: Integer;
// topTime: Double;
params: TParams;
noMoreMessages: Boolean;
events: Thevents;
history: Thistory;
MsgDatas: array of TMessageData;
begin
if argc = 0 then
Exit;
UID := SciterVarToString(argv);
msgs := 1;
if argc = 3 then
begin
Inc(argv);
API.ValueIntData(argv, offset);
Inc(argv);
API.ValueIntData(argv, msgs);
end;
with UI.Chat do
begin
history := GetHistory(UID);
if not Assigned(history) then
Exit;
events := history.getLastEvents(offset, msgs, noMoreMessages);
evCnt := Length(events);
SetLength(MsgDatas, evCnt);
if evCnt > 0 then
for evId := 0 to evCnt - 1 do
if Assigned(events[evId]) then
begin
AddChatItem(params, MsgDatas[evId], events[evId], False);
FreeAndNil(events[evId]);
end;
Finalize(events);
if Length(params) > 0 then
SendChatItems(UID, params, SA_PREPEND);
Finalize(MsgDatas);
if noMoreMessages then
HideHistory(UID);
end;
end;
class procedure TChatMethods.UpdateSelection(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
text, sOfs, eOfs: String;
sOfsT, eOfsT: TDateTime;
isWhole: Bool;
ffs: TFormatSettings;
tmpInt: Integer;
begin
if argc < 4 then
Exit;
text := SciterVarToString(argv);
UI.Chat.SelectedText := text;
Inc(argv);
sOfs := SciterVarToString(argv);
Inc(argv);
eOfs := SciterVarToString(argv);
Inc(argv);
API.ValueIntData(argv, tmpInt);
isWhole := tmpInt = 1;
ffs := TFormatSettings.Create(LOCALE_USER_DEFAULT);
ffs.DecimalSeparator := '.';
sOfsT := StrToFloat(sOfs, ffs);
eOfsT := StrToFloat(eOfs, ffs);
with UI.Chat do
if (sOfsT <= 0) or (eOfsT <= 0) then
begin
StartSel := 0;
EndSel := 0;
IsWholeEvents := False;
end
else
begin
StartSel := sOfsT;
EndSel := eOfsT;
IsWholeEvents := isWhole;
end;
end;
function GetSnapshotExt: String;
begin
if ScreenshotFormat = 2 then
Result := 'webp'
else if ScreenshotFormat = 1 then
Result := 'jpg'
else
Result := 'png'
end;
function GetSnapshotFilename: String;
begin
Result := 'snapshot.' + GetSnapshotExt;
end;
class procedure TChatMethods.UploadLastSnapshot(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
with UI.Chat do
begin
AddToCurrentInput(FileUpload(False, CacheDir + GetSnapshotFilename));
DeleteFile(CacheDir + GetSnapshotFilename);
end;
end;
class procedure TChatMethods.DeleteSnapshot(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
DeleteFile(CacheDir + GetSnapshotFilename);
end;
class procedure TChatMethods.UploadFiles(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
I: Integer;
Files: String;
begin
with UI.Chat do
begin
I := 0;
API.ValueIntData(argv, I);
Inc(argv);
Files := SciterVarToString(argv);
AddToCurrentInput(FileUpload(I = 1, Files));
end;
end;
procedure CreateMessageRegex;
var
emailRegex, protocolRegex, wwwRegex, domainNameRegex, tldRegex, urlSuffixRegex: String;
function FindWordExp(const n, w: String; const m: String = ''): String;
begin
Result := '((?<=^|\s)' + m + '(?<' + n + '>' + w + ')' + m + '(?=$|\s))';
end;
begin
emailRegex := '[\-;:&=\+\$,\w\.]+@';
protocolRegex := '[a-z][-.+a-z0-9]*:(?![a-z][-.+a-z0-9]*:\/\/)(?!\d+\/?)(\/\/)?';
wwwRegex := 'www\.';
domainNameRegex := '[а-яА-ЯёЁa-z0-9\.\-]*[а-яА-ЯёЁa-z0-9\-]';
tldRegex := '\.[а-яА-ЯёЁa-z]{2,63}';
urlSuffixRegex := '[\—\-а-яА-ЯёЁa-z0-9+&@#\/%=~_()|''$*\[\]?!:,.;]*[\—\-а-яА-ЯёЁa-z0-9+&@#\/%=~_()|''$*\[\]]';
msgRegex := TPerlRegEx.Create;
msgRegex.RegEx :=
FindWordExp('mail', emailRegex + domainNameRegex + tldRegex) +
'|' + FindWordExp('url',
'((' + protocolRegex + domainNameRegex + tldRegex + ')' +
'|' +
'((.?//)?' + wwwRegex + domainNameRegex + tldRegex + '))' +
'(' + urlSuffixRegex + ')?') +
'|' + FindWordExp('uin', '\d{5,9}') +
'|' + '(\[code=?(?.*?)\](\r\n|\r|\n)?(?.+?)(\r\n|\r|\n)?\[\/code\])' +
'|' + FindWordExp('bold', '\S.+?\S', '\*') +
'|' + FindWordExp('underline', '\S.+?\S', '_') +
'|' + '((?m-s)^\>\; (?.+)$(?s-m))';
msgRegex.State := [preNotEmpty];
msgRegex.Options := [preCaseLess, preSingleLine, preNoAutoCapture];
msgRegex.Study;
youtubeRegex := TPerlRegEx.Create;
youtubeRegex.RegEx := '(www\.)?(youtube\.com\/watch|youtu.be\/).+';
youtubeRegex.State := [preNotEmpty];
youtubeRegex.Options := [preCaseLess];
youtubeRegex.Study;
vimeoRegex := TPerlRegEx.Create;
vimeoRegex.RegEx := '(www\.)?(vimeo\.com\/).+';
vimeoRegex.State := [preNotEmpty];
vimeoRegex.Options := [preCaseLess];
vimeoRegex.Study;
end;
procedure FreeMessageRegex;
begin
msgRegex.Free;
youtubeRegex.Free;
vimeoRegex.Free;
end;
procedure TChatBox.FitImage(var ImgWidth: Integer; var ImgHeight: Integer);
var
Ratio: Extended;
begin
Ratio := ImgHeight / ImgWidth;
if LimitMaxChatImgWidth and (MaxChatImgWidthVal > 0) then
begin
if (ImgWidth > MaxChatImgWidthVal) then
begin
ImgWidth := MaxChatImgWidthVal;
ImgHeight := Floor(MaxChatImgWidthVal * Ratio);
end;
end else if (ImgWidth > Self.GetWidth) then
begin
ImgWidth := Self.GetWidth;
ImgHeight := Floor(Self.GetWidth * Ratio);
end;
if LimitMaxChatImgHeight and (MaxChatImgHeightVal > 0) and (ImgHeight > MaxChatImgHeightVal) then
begin
ImgWidth := Floor(MaxChatImgHeightVal / Ratio);
ImgHeight := MaxChatImgHeightVal;
end;
end;
function TChatBox.GetReplacement(args: TArray): PCREString;
procedure AppendImage(const URL: String; ImgWidth: Integer = 0; ImgHeight: Integer = 0);
var
DataLink, Display, Style, Action: PCREString;
Cached, Lottie: Boolean;
begin
DataLink := 'data-link="' + THTMLEncoding.HTML.Encode(URL) + '" ';
Action := 'check';
Display := ' hidden';
Lottie := IsLottieMime(URL);
Cached := (ImgWidth > 0) and (ImgHeight > 0);
if Cached then
begin
Action := 'download';
Display := '';
if not Lottie then
FitImage(ImgWidth, ImgHeight);
end;
Style := IfThen(Cached, 'style="width: ' + IntToStr(ImgWidth) + 'px; height: ' + IntToStr(ImgHeight) + 'px" ');
Result := Result + '
';
if Lottie then
Result := Result + ''
else
Result := Result + '';
Result := Result + '';
end;
function GetLinkDomain(Link: String): String;
var
DomainRegex: TPerlRegex;
begin
DomainRegex := TPerlRegEx.Create;
DomainRegex.RegEx := 'http[s]?\:\/\/([а-яА-ЯёЁa-z0-9\.\-]*[а-яА-ЯёЁa-z0-9\-])[\/]?';
DomainRegex.State := [preNotEmpty];
DomainRegex.Options := [preCaseLess, preSingleLine];
DomainRegex.Subject := Link;
if DomainRegex.Match and (DomainRegex.GroupCount > 0) then
begin
if DomainRegex.Groups[1] = Link then
Result := Link
else
Result := DomainRegex.Groups[1]
end else
Result := Link;
DomainRegex.Free;
end;
var
match, mail, url, uin, srcLang, srcCode, bold, underlined, comment,
videoHref, srcCodeHTML: String;
srcCodeArr: TStringList;
IsVideoLink: Boolean;
begin
match := args[0];
mail := args[1];
url := args[2];
uin := args[3];
srcLang := args[4];
srcCode := args[5];
bold := args[6];
underlined := args[7];
comment := args[8];
if not (mail = '') then
Result := '' + mail + ''
else if not (url = '') then
begin
Result := '' + GetLinkDomain(url) + '';
videoHref := '
IsVideoLink := False;
youtubeRegex.Subject := url;
vimeoRegex.Subject := url;
if youtubeRegex.Match then
begin
Result := Result + videoHref + ' kind="youtube">
';
IsVideoLink := True;
end else if vimeoRegex.Match then
begin
Result := Result + videoHref + ' kind="vimeo">
';
IsVideoLink := True;
end else
Result := Result + '
';
if (not IsLastParsedEventMine and EnableImgLinksIn) or (IsLastParsedEventMine and EnableImgLinksOut) then
if (not IsVideoLink) or (IsVideoLink and EnableVideoLinks) then
if ImgCacheInfo.ValueExists(url, 'hash') then
AppendImage(url, ImgCacheInfo.ReadInteger(url, 'width', 256), ImgCacheInfo.ReadInteger(url, 'height', 256))
else
AppendImage(url);
end else if not (uin = '') then
Result := '' + uin + ''
else if not (srcCode = '') then
begin
if Assigned(Root) and Call('isSyntaxHighlightEnabled') then
begin
srcCodeArr := TStringList.Create;
if TrimMsgNewLines then
srcCodeArr.Text := srcCode.Trim([#13, #10])
else
srcCodeArr.Text := srcCode;
srcCodeHTML := '';
for var I := 0 to srcCodeArr.Count - 1 do
srcCodeHTML := srcCodeHTML + '' + srcCodeArr[I] + '';
srcCodeArr.Free;
Result := '
' +
'
' +
'' + srcCodeHTML + '';
end else
Result := match;
end else if not (bold = '') then
Result := '' + bold + ''
else if not (underlined = '') then
Result := '' + underlined + ''
else if not (comment = '') then
Result := '> ' + comment + ''
else
Result := match;
end;
procedure TChatBox.ReplaceOtherMatch(Sender: TObject; var ReplaceWith: PCREString);
var
args: TArray;
begin
SetLength(args, 9);
for var I := 0 to msgRegex.GroupCount do // +1 for total match
args[i] := msgRegex.Groups[i];
ReplaceWith := GetReplacement(args);
end;
procedure TChatBox.ReplaceSmileMatch(Sender: TObject; var ReplaceWith: PCREString);
var
smileData: TPair;
smile: String;
begin
if (theme.smileRegEx.GroupCount > 0) and theme.smileArray.TryGetValue(theme.smileRegEx.Groups[1], smileData) then
begin
smile := THTMLEncoding.HTML.Encode(theme.smileRegEx.Groups[1]);
ReplaceWith := '
IntToStr(smileData.Value.Width) + 'px; height: ' + IntToStr(smileData.Value.Height) + 'px;">' + smile + '' +
'' + smile + ''
end else
ReplaceWith := theme.smileRegEx.Groups[0];
end;
function TChatBox.ReplaceEmoji(msg: String): String;
var
i: Integer;
Chr1: Cardinal;
Done: Boolean;
procedure GetReplacedEmoji(cp1, cp2: Cardinal; const emoji: String; var Processed: Boolean); overload;
var
pos: Integer;
cp: TPair;
begin
Processed := False;
if not theme.HasOrigPic('emojis.sprite') then
Exit;
cp := TPair.Create(cp1, cp2);
if not emojis.TryGetValue(cp, pos) then
Exit;
Result := Result + '
IntToStr(EmojiSize) + 'px; background-position: ' + IntToStr(-(pos mod EmojisInARow) * EmojiSize) + ' ' +
IntToStr(-floor(pos / EmojisInARow) * EmojiSize) + ';">' + emoji + '' + emoji + '';
Inc(i, Length(emoji));
Processed := True;
end;
function IsSingle(const Num: Integer): Boolean;
var
C: Integer;
begin
Result := False;
for C := Low(Singles) to High(Singles) do
if Num = Singles[C] then
Exit(True);
end;
begin
i := 1;
Result := '';
msg := ReplaceText(msg, Char($200D), ''); // Kill Zero Width Joiner
msg := ReplaceText(msg, Char($FE0F), ''); // Kill Emoji Variant
while i <= Length(msg) do
begin
Done := False;
if IsSurrogate(msg, i) and IsSurrogatePair(msg, i) then
begin
if (i+3 <= Length(msg)) and IsSurrogate(msg, i+2) and IsSurrogatePair(msg, i+2)
and (IsHighSurrogate(msg, i+2)) and (IsLowSurrogate(msg, i+3)) then
GetReplacedEmoji(ConvertToUtf32(msg, i), ConvertToUtf32(msg, i+2), Copy(msg, i, 4), Done);
if not Done and (i+2 <= Length(msg)) then
GetReplacedEmoji(ConvertToUtf32(msg, i), Ord(msg[i+2]), Copy(msg, i, 3), Done);
if not Done and (IsHighSurrogate(msg, i)) and (IsLowSurrogate(msg, i+1)) then
GetReplacedEmoji(ConvertToUtf32(msg, i), 0, Copy(msg, i, 2), Done);
end else if (i+1 <= Length(msg)) and (CharInSet(msg[i], ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*']) and (Ord(msg[i+1]) = $20E3)) then
GetReplacedEmoji(Ord(msg[i]), Ord(msg[i+1]), Copy(msg, i, 2), Done)
else if IsSingle(Ord(msg[i])) then
GetReplacedEmoji(Ord(msg[i]), 0, msg[i], Done);
if not Done then
begin
Result := Result + msg[i];
Inc(i);
end;
end;
end;
function TChatBox.ParseMessageBody(const body: String): String;
function FastReplaceAll(const text, searchExp, rep: PCREString): PCREString;
var
theRegex: TPerlRegEx;
begin
theRegex := TPerlRegEx.Create;
theRegex.RegEx := searchExp;
theRegex.State := [preNotEmpty];
theRegex.Subject := text;
theRegex.Replacement := rep;
if theRegex.ReplaceAll then
Result := theRegex.Subject
else
Result := text;
theRegex.Free;
end;
var
Res: PCREString;
begin
if body = '' then
Exit('');
Res := THTMLEncoding.HTML.Encode(body);
Res := FastReplaceAll(Res, '\x{0000}', '');
if theme.smileArray.Count > 0 then
begin
theme.smileRegEx.Subject := Res;
theme.smileRegEx.OnReplace := ReplaceSmileMatch;
if theme.smileRegEx.ReplaceAll then
Res := theme.smileRegEx.Subject;
end;
msgRegex.Subject := Res;
msgRegex.OnReplace := ReplaceOtherMatch;
if msgRegex.ReplaceAll then
Res := msgRegex.Subject;
Result := ReplaceEmoji(Res);
end;
class procedure TChatMethods.GetLinkInfo(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
Link, FileId, Contact, ViralClass, Transcript: String;
LinkInfo: TLinkInfo;
FileInfo: TICQFileInfo;
Infos: TArray;
Params: TParams;
Anketa: TAnketa;
begin
Link := SciterVarToString(argv);
SetLength(Infos, 0);
if ContainsText(Link, 'files.icq.net/') then
begin
FileInfo := GetICQFileLinkInfoPublic(Link);
if not (FileInfo.filename = '') then
begin
SetLength(Infos, 4);
Infos[0].param := GetTranslation('Filename');
Infos[0].value := FileInfo.filename;
Infos[1].param := GetTranslation('Type');
Infos[1].value := FileInfo.mime;
Infos[2].param := GetTranslation('Size');
Infos[2].value := HumanReadableSize(FileInfo.filesize);
if FileInfo.avstatus = '' then
FileInfo.avstatus := 'no data';
ViralClass := IfThen((FileInfo.avstatus = 'healthy') or (FileInfo.avstatus = 'safe'), 'safe', 'unsafe');
Infos[3].param := GetTranslation('Viral status');
Infos[3].value := '' + GetTranslation(FileInfo.avstatus) + '';
if FileInfo.recognized then
begin
FileId := ReplaceText(Trim(Link), 'https://files.icq.net/get/', '');
FileId := ReplaceText(FileId, 'https://files.icq.net/files/get?fileId=', '');
Transcript := Account.AccProto.GetSpeechToText(FileId);
if not (Transcript = '') then
begin
SetLength(Infos, 5);
Infos[4].param := 'TRANSCRIPTION';
Infos[4].value := Transcript;
end;
end;
end;
end else if Account.AccProto.IsOnline and ContainsText(Link, 'icq.im/') then
begin
Contact := Link.Substring(Link.LastIndexOf('icq.im/') + 7, Length(Link));
if Account.AccProto.SearchContact(Contact, Anketa) then
begin
SetLength(Infos, 4);
Infos[0].param := 'UIN';
Infos[0].value := Anketa.UID;
Infos[1].param := GetTranslation('Nick');
Infos[1].value := Anketa.Nick;
Infos[2].param := GetTranslation('Name');
Infos[2].value := Anketa.Friendly;
Infos[3].param := 'AVATAR';
Infos[3].value := Anketa.AvatarURL;
if Anketa.Bot then
begin
SetLength(Infos, 5);
Infos[4].param := GetTranslation('Bot contact');
Infos[4].value := '';
end;
end;
end;
if Length(Infos) = 0 then
begin
LinkInfo := InfoFromURL(Link);
SetLength(Infos, 3);
Infos[0].param := GetTranslation('Response code');
Infos[0].value := IntToStr(LinkInfo.code) + ' ' + StatusText(LinkInfo.code);
Infos[1].param := GetTranslation('Type');
Infos[1].value := LinkInfo.mime;
Infos[2].param := GetTranslation('Size');
Infos[2].value := IfThen(LinkInfo.size < 0, '-', HumanReadableSize(LinkInfo.size));
// if LinkInfo.redirects > 0 then
// begin
// SetLength(Infos, 4);
// Infos[3].param := GetTranslation('Redirects');
// Infos[3].value := IntToStr(LinkInfo.redirects);
// end;
end;
SetLength(Params, Length(Infos));
for var I := 0 to Length(Infos) - 1 do
Params[I] := UI.RecordToVar(Infos[I]);
V2S(Params, retval);
end;
function DecodeFormat(format: String): String;
begin
Result := '';
format := DecodeURL(format);
if format.ToLower.Contains('video/mp4') then
Result := 'MP4'
else if format.ToLower.Contains('video/webm') then
Result := 'WEBM'
else if format.ToLower.Contains('video/x-flv') then
Result := 'FLV'
else if format.ToLower.Contains('video/3gpp') then
Result := '3GPP'
end;
class procedure TChatMethods.GetYoutubeLinks(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
ytlink: String;
ytpage, ytjson, yttitle, anchor: RawByteString;
ytmap: TJSONArray;
ytitem: TStringList;
ytfmts: TDictionary;
ytfmt: TVideoFormat;
fs: TMemoryStream;
i, j, p, arrsize: Integer;
ignore3GPP: Boolean;
mimeType: String;
tmpVal: TSciterValue;
JSONObject: TJSONObject;
begin
arrsize := 0;
ytlink := '';
if argc > 0 then
ytlink := SciterVarToString(argv);
if ytlink = '' then
Exit;
fs := TMemoryStream.Create;
LoadFromURLAsStream(ytlink, fs);
SetLength(ytpage, fs.Size);
fs.ReadBuffer(ytpage[1], fs.Size);
fs.Free;
anchor := 'ytInitialPlayerResponse =';
i := pos(anchor, ytpage);
if i = 0 then
Exit;
ytjson := copy(ytpage, i + length(anchor));
ytjson := copy(ytjson, 1, pos('};', ytjson));
ytfmts := nil;
JSONObject := TJSONObject.ParseJSONValue(Trim(ytjson)) as TJSONObject;
if Assigned(JSONObject) then
try
ytfmts := TDictionary.Create;
ytmap := (JSONObject.GetValue('streamingData') as TJSONObject).GetValue('formats') as TJSONArray;
for i := 0 to ytmap.Count - 1 do
begin
ytfmt := Default(TVideoFormat);
ytfmt.url := (ytmap.Items[i] as TJSONObject).GetValue('url').Value;
ytfmt.quality := (ytmap.Items[i] as TJSONObject).GetValue('quality').Value;
mimeType := (ytmap.Items[i] as TJSONObject).GetValue('mimeType').Value;
p := pos(';', mimeType);
if p > 0 then
begin
ytfmt.format := copy(mimeType, 1, p - 1);
p := pos('codecs=', mimeType);
ytfmt.codecs := copy(mimeType, p + 7).Replace('"', '');
end else
ytfmt.format := mimeType;
ytfmts.Add(i, ytfmt);
end;
except end;
if not Assigned(ytfmts) then
Exit;
anchor := 'property="og:title" content="';
yttitle := Copy(ytpage, pos(anchor, ytpage) + length(anchor));
yttitle := Copy(yttitle, 1, pos('"', yttitle) - 1);
yttitle := DecodeURL(UnUTF(yttitle));
ytitem := TStringList.Create;
ytitem.Delimiter := '|';
ytitem.StrictDelimiter := True;
ytitem.Sorted := False;
ignore3GPP := False;
for i := 0 to ytfmts.Count - 1 do
begin
ytfmts.TryGetValue(i, ytfmt);
if ((PreferredResolution = 0) and ytfmt.quality.Contains('1080'))
or ((PreferredResolution = 1) and (ytfmt.quality.Contains('720') or ytfmt.quality.ToLower.Contains('hd')))
or ((PreferredResolution = 2) and ytfmt.quality.ToLower.Contains('medium'))
or ((PreferredResolution = 3) and ytfmt.quality.ToLower.Contains('small')) then
begin
if ytfmt.format.ToLower.Contains('video/3gpp') and ignore3GPP then
Continue;
//OutputDebugString(PChar('Preferred: ' + ytfmt.quality + ', ' + ytfmt.format));
ytlink := '{"format":"' + DecodeFormat(ytfmt.format) + '","codecs":"' + DecodeURL(ytfmt.codecs) + '","title":"' + yttitle + '","url":"' + DecodeURL(ytfmt.url) + '"}';
API.ValueInit(@tmpVal);
API.ValueStringDataSet(@tmpVal, PWideChar(ytlink), Length(ytlink), 0);
API.ValueNthElementValueSet(retval, arrsize, @tmpVal);
API.ValueClear(@tmpVal);
Inc(arrsize);
if ytfmt.format.ToLower.Contains('video/3gpp') then
ignore3GPP := True;
end;
end;
if arrsize = 0 then
begin
ytfmts.TryGetValue(ytfmts.Count - 1, ytfmt);
//OutputDebugString(PChar('No preferred: ' + ytfmt.quality + ', ' + ytfmt.format));
ytlink := '{"format":"' + DecodeFormat(ytfmt.format) + '","codecs":"' + DecodeURL(ytfmt.codecs) + '","title":"' + yttitle + '","url":"' + DecodeURL(ytfmt.url) + '"}';
API.ValueInit(@tmpVal);
API.ValueStringDataSet(@tmpVal, PWideChar(ytlink), Length(ytlink), 0);
API.ValueNthElementValueSet(retval, 0, @tmpVal);
API.ValueClear(@tmpVal);
end;
end;
class procedure TChatMethods.GetVimeoLinks(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
vmlink: String;
vmpage, vmurl, vmtitle, anchor: RawByteString;
vmmap: TJSONArray;
vmfmts: TDictionary;
vmfmt: TVideoFormat;
fs: TMemoryStream;
i, arrsize: Integer;
JSONObject: TJSONObject;
tmpVal: TSciterValue;
begin
arrsize := 0;
vmlink := '';
if argc > 0 then
vmlink := SciterVarToString(argv);
if vmlink = '' then
Exit;
fs := TMemoryStream.Create;
LoadFromURLAsStream(vmlink, fs);
SetLength(vmpage, fs.Size);
fs.ReadBuffer(vmpage[1], fs.Size);
anchor := 'config_url":"';
i := pos(anchor, vmpage);
if i = 0 then
Exit;
vmurl := copy(vmpage, i + Length(anchor));
vmurl := copy(vmurl, 1, pos('"', vmurl) - 1);
vmurl := String(vmurl).Replace('\/', '/');
anchor := 'property="og:title" content="';
vmtitle := copy(vmpage, pos(anchor, vmpage) + Length(anchor));
vmtitle := copy(vmtitle, 1, pos('"', vmtitle) - 1);
vmtitle := DecodeURL(UnUTF(vmtitle));
fs.Clear;
LoadFromURLAsStream(vmurl, fs);
SetLength(vmpage, fs.Size);
fs.ReadBuffer(vmpage[1], fs.Size);
fs.Free;
vmfmts := TDictionary.Create;
JSONObject := TJSONObject.ParseJSONValue(vmpage) as TJSONObject;
if Assigned(JSONObject) then
try
vmmap := (((JSONObject.GetValue('request') as TJSONObject).GetValue('files') as TJSONObject).GetValue('progressive') as TJSONArray);
for i := 0 to vmmap.Count - 1 do
begin
vmfmt.url := (vmmap.Items[i] as TJSONObject).GetValue('url').Value;
vmfmt.quality := (vmmap.Items[i] as TJSONObject).GetValue('quality').Value;
vmfmt.format := (vmmap.Items[i] as TJSONObject).GetValue('mime').Value;
vmfmts.Add(i, vmfmt);
end;
except end;
for i := 0 to vmfmts.Count - 1 do
begin
vmfmts.TryGetValue(i, vmfmt);
if ((PreferredResolution = 0) and (vmfmt.quality = '1080p'))
or ((PreferredResolution = 1) and (vmfmt.quality = '720p'))
or ((PreferredResolution = 2) and (vmfmt.quality = '540p'))
or ((PreferredResolution = 3) and (vmfmt.quality = '360p')) then
begin
//OutputDebugString(PChar('Preferred: ' + vmfmt.quality + ', ' + vmfmt.format));
vmlink := '{"format":"' + DecodeFormat(vmfmt.format) + '","codecs":"","title":"' + vmtitle + '","url":"' + vmfmt.url + '"}';
API.ValueInit(@tmpVal);
API.ValueStringDataSet(@tmpVal, PWideChar(vmlink), Length(vmlink), 0);
API.ValueNthElementValueSet(retval, arrsize, @tmpVal);
API.ValueClear(@tmpVal);
Inc(arrsize);
end;
end;
if arrsize = 0 then
begin
vmfmts.TryGetValue(0, vmfmt);
//OutputDebugString(PChar('No preferred: ' + vmfmt.quality + ', ' + vmfmt.format));
vmlink := '{"format":"' + DecodeFormat(vmfmt.format) + '","codecs":"","title":"' + vmtitle + '","url":"' + vmfmt.url + '"}';
API.ValueInit(@tmpVal);
API.ValueStringDataSet(@tmpVal, PWideChar(vmlink), Length(vmlink), 0);
API.ValueNthElementValueSet(retval, 0, @tmpVal);
API.ValueClear(@tmpVal);
end;
end;
class procedure TChatMethods.GetVolumeLevel(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
level: String;
leveldb: Double;
begin
MainPrefs.getPrefStr('chat-video-volume-level', level);
if TryStrToFloat(level, leveldb) then
API.ValueFloatDataSet(retval, leveldb, T_FLOAT, 0)
else
API.ValueFloatDataSet(retval, 0.85, T_FLOAT, 0); // Volume 50%
end;
class procedure TChatMethods.SaveVolumeLevel(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
leveldb: Double;
begin
leveldb := 0.85;
if argc > 0 then
API.ValueFloatData(argv, leveldb);
MainPrefs.addPrefStr('chat-video-volume-level', FloatToStr(leveldb));
end;
class procedure TChatMethods.RequestChatPageSettings(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
UID: TUID;
Cnt: TICQCOntact;
begin
if argc = 0 then
Exit;
UID := SciterVarToString(argv);
if UID = '' then
Exit;
Cnt := Account.AccProto.GetContact(UID);
UI.Chat.UpdatePageSettings(Cnt);
end;
class procedure TChatMethods.ChatPageSelected(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
uid: TUID;
txt: String;
ch: TChatInfo;
begin
if argc < 2 then
Exit;
uid := SciterVarToString(argv);
Inc(argv);
txt := SciterVarToString(argv);
with UI.Chat do
if not (uid = '') and Assigned(chats) and not (chats.Count = 0) then
begin
ch := chats.byUIN(uid);
CurrentContact := ch.who;
if AutoConsumeEvents and IsVisible then
SawAllHere;
if autoSwitchKL and Assigned(LastContact) and not (LastContact = CurrentContact) and (pTCE(CurrentContact.data).keylay <> 0) then
ActivateKeyboardLayout(pTCE(CurrentContact.data).keylay, 0);
LastContact := nil;
if Running and EnableSpellCheck then
begin
SetSpellText(txt);
DoSpellCheck;
end;
UpdateGraphics;
SetupStickersBtn(EnableStickers);
SetupBuzzBtn(CurrentContact.CanBuzz);
plugins.castEv(PE_SELECTPAGE, CurrentContact.UID);
end else
begin
CurrentContact := nil;
OutputDebugString(PChar('CurrentContact is nil'));
end;
end;
class procedure TChatMethods.ChatPageDeselected(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
UID: TUID;
Ch: TChatInfo;
begin
UID := SciterVarToString(argv);
with UI.Chat do
if not (UID = '') and Assigned(chats) and not (chats.count = 0) then
begin
LastContact := nil;
Ch := chats.byUIN(UID);
if Assigned(Ch) then
with Ch do
begin
LastContact := who;
if Assigned(who) then
begin
pTCE(who.data).keylay := GetKeyboardLayout(0);
plugins.castEv(PE_DESELECTPAGE, who.UID);
end;
end;
end;
end;
class procedure TChatMethods.PluginPageSelected(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
id: Integer;
ch: TChatInfo;
begin
id := 0;
API.ValueIntData(argv, id);
with UI.Chat do
if not (id = 0) and Assigned(chats) and not (chats.count = 0) then
begin
ch := chats.byID(id);
CurrentContact := nil;
SetupBuzzBtn(False);
SetupStickersBtn(False);
RedrawPluginTab(ch);
plugins.castEv(PE_SELECTTAB, ch.ID);
end else
CurrentContact := nil;
end;
class procedure TChatMethods.PluginPageDeselected(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
id: Integer;
ch: TChatInfo;
begin
API.ValueIntData(argv, id);
with UI.Chat do
if not (id = 0) and Assigned(chats) and not (chats.count = 0) then
begin
LastContact := nil;
ch := chats.byID(id);
if Assigned(ch) then
plugins.castEv(PE_DESELECTTAB, ch.ID);
end;
end;
//class procedure TChatMethods.SetTabDragging(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
//var
// tmpInt: Integer;
//begin
// if (argc = 0) or not Assigned(UI.Chat) then
// Exit;
//
// API.ValueIntData(argv, tmpInt);
// UI.Chat.DraggingTab := tmpInt = 1;
//end;
class procedure TChatMethods.SaveTabsOrder(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
Pages: Variant;
begin
S2V(argv, Pages);
for var I := VarArrayLowBound(Pages, 1) to VarArrayHighBound(Pages, 1) do
begin
var Index := UI.Chat.chats.idxOfUIN(TUID(Pages[I]));
if (Index >= 0) and (Index < UI.Chat.chats.Count) then
UI.Chat.chats.Move(Index, I);
end;
end;
class procedure TChatMethods.CloseChatPage(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
UID: TUID;
begin
if (argc = 0) or not Assigned(UI.Chat) then
Exit;
UID := SciterVarToString(argv);
UI.Chat.CloseChatUID(UID);
end;
class procedure TChatMethods.ClosePluginPage(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
id: Integer;
begin
if (argc = 0) or not Assigned(UI.Chat) then
Exit;
API.ValueIntData(argv, id);
UI.Chat.CloseChatID(id);
end;
//class procedure TChatMethods.AddUIN2CL(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
//var
// uid: TUID;
// MTag: String;
// _MTag: Integer;
// Contact: TICQContact;
//begin
// if argc < 2 then
// Exit;
//
// UID := SciterVarToString(argv);
// Inc(argv);
// MTag := SciterVarToString(argv);
//
// Contact := UI.Chat.CurrentContact;
// if Assigned(Contact) then
// Contact := Account.AccProto.GetContact(UID);
// if Assigned(Contact) and TryStrToInt(MTag, _MTag) then
// utilLib.AddToRoster(Contact, _MTag, Contact.CntIsLocal)
//end;
function StripProtocol(const StringData: String): String;
begin
if StartsText('uin:', StringData) then
Result := copy(StringData, 5, length(StringData))
else if StartsText('link:', stringData) then
Result := copy(StringData, 6, length(StringData))
else if StartsText('mailto:', stringData) then
Result := copy(StringData, 8, length(StringData))
else
Result := stringData;
end;
class procedure TChatMethods.CopyLink(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
if argc > 0 then
Clipboard.AsText := StripProtocol(SciterVarToString(argv));
end;
class procedure TChatMethods.SavePicture(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
str: String;
fs: TFileStream;
img: TBytes;
pic: TMemoryStream;
hash: LongWord;
realurl, fn, fmt: String;
begin
if argc = 0 then
Exit;
str := SciterVarToString(argv);
if Assigned(UI.Chat.CurrentContact) then
with UI.Chat.CurrentContact do
if StartsText('download:', str) then
begin
realurl := Copy(str, 10, Length(str));
fn := myPath + 'Cache\Images\' + ImgCacheInfo.ReadString(realurl, 'hash', '0') + '.' + ImgCacheInfo.ReadString(realurl, 'ext', 'jpg');
if FileExists(fn) then
begin
fs := TFileStream.Create(fn, fmOpenRead);
pic := TMemoryStream.Create;
pic.LoadFromStream(fs);
fmt := PAFormat[DetectFileFormatStream(pic)];
Delete(fmt, 1, 1);
fmt := openSaveDlg(nil, '', false, fmt);
if fmt > '' then
pic.SaveToFile(fmt);
pic.Free;
if Assigned(fs) then
fs.Free;
end;
end else if StartsText('embedded:', str) then
begin
realurl := copy(str, 10, length(str));
if TryStrToLongWord(realurl, hash) and Assigned(EmbeddedImgs) and EmbeddedImgs.TryGetValue(hash, img) then
begin
pic := TMemoryStream.Create;
pic.Write(img, Length(img));
fmt := PAFormat[DetectFileFormatStream(pic)];
Delete(fmt, 1, 1);
fmt := openSaveDlg(nil, '', false, fmt);
if fmt > '' then
pic.SaveToFile(fmt);
pic.free;
end;
end;
end;
class procedure TChatMethods.GetEvent(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
uid: TUID;
clickedTime: String;
time: TDateTime;
ev: Thevent;
hdr: THeader;
MessageHeader: TMessageHeader;
// imgList: TImgBytes;
// imgcnt: Integer;
// imgs,
ffs: TFormatSettings;
history: Thistory;
begin
if argc = 0 then
Exit;
ev := nil;
uid := SciterVarToString(argv);
Inc(argv);
clickedTime := SciterVarToString(argv);
ffs := TFormatSettings.Create(LOCALE_USER_DEFAULT);
ffs.DecimalSeparator := '.';
time := StrToFloat(clickedTime, ffs);
if uid = '' then
Exit;
history := UI.Chat.GetHistory(uid);
if Assigned(history) then
ev := history.getByTime(time);
if ev = nil then
Exit;
{
if (ev.kind = EK_msg) and (Length(ev.getBodyBin) > 0) then
begin
getMsgImages(ev.getBodyBin, imgList);
imgs := VarArrayCreate([0, Length(imgList) - 1], varByte);
for imgcnt := 0 to Length(imgList) - 1 do
imgs[imgcnt] := imgList[imgcnt];
SetLength(imgList, 0);
end;
}
hdr := ev.getHeaderTexts;
ffs := TFormatSettings.Create(LOCALE_USER_DEFAULT);
ffs.DecimalSeparator := '.';
MessageHeader.caption := IfThen(Length(hdr.Prefix) > 0, hdr.Prefix + ' ', '') + hdr.date + ', ' + hdr.what;
MessageHeader.text := ev.getBodyText;
MessageHeader.when := FloatToStr(ev.when, ffs);
MessageHeader.img := ev.pic;
V2S(UI.RecordToVar(MessageHeader), retval);
ev.Free;
end;
class procedure TChatMethods.SaveAs(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
format: Integer;
fn, caption, ext: String;
begin
if argc = 0 then
Exit;
API.ValueIntData(argv, format);
if format = 0 then
begin
caption := 'Save text as UTF-8 file';
ext := 'txt';
end else if format = 1 then
begin
caption := 'Save as HTML';
ext := 'html';
end else if format = 2 then
begin
caption := 'Save screenshot as file';
ext := GetSnapshotExt;
end;
fn := openSavedlg(nil, GetTranslation(caption), False, ext);
if not (fn = '') then
if format = 0 then
saveTextFile(fn, UI.Chat.SelectedText)
else if format = 1 then
saveTextFile(fn, UI.Chat.GetSelectedChatEventsAsHTML)
else if format = 2 then
if not MoveFileEx(PWideChar(CacheDir + GetSnapshotFilename), PWideChar(fn), MOVEFILE_REPLACE_EXISTING or MOVEFILE_COPY_ALLOWED) then
begin
MsgDlg(GetTranslation('Failed to save screenshot\n[%d] %s', [GetLastError, SysErrorMessage(GetLastError)]), True, mtWarning);
DeleteFile(CacheDir + GetSnapshotFilename);
end;
end;
class procedure TChatMethods.AddLinkToFav(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
if argc > 0 then
AddLinkToFavorites(StripProtocol(SciterVarToString(argv)));
end;
class procedure TChatMethods.DeleteMessages(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
UID: TUID;
Option: String;
History: Thistory;
IDs: Variant;
MsgIDs: TArray;
begin
if argc = 0 then
Exit;
UID := SciterVarToString(argv);
Inc(argv);
Option := SciterVarToString(argv);
with UI.Chat do
begin
if not WholeEventsAreSelected then
Exit;
if Option = 'local' then
begin
if MessageDlg(GetTranslation('All messages between first and last selected will be removed! Are you sure you want to proceed?'), mtConfirmation, [mbYes, mbNo]) = mrNo then
Exit;
if StartSel > EndSel then
swap4(StartSel, EndSel);
History := GetHistory(UID);
if Assigned(History) then
begin
History.DeleteFromToTime(StartSel, EndSel);
DeleteEvents(UID, StartSel, EndSel);
ClearSelection(UID);
end;
end
else
begin
Inc(argv);
S2V(argv, IDs);
if (Length(TArray(IDs)) = 0) or not OnlFeature(Account.AccProto) then
Exit;
if MessageDlg(GetTranslation('Selected messages will be removed permanently!\nAre you sure you want to proceed?'), mtConfirmation, [mbYes, mbNo]) = mrNo then
Exit;
MsgIDs := IDs;
Account.AccProto.DeleteMessages(UID, MsgIDs, Option = 'forall');
History := GetHistory(UID);
if Assigned(History) then
for var MsgID in MsgIDs do
begin
History.DeleteByMsgID(StrToUInt64(MsgID));
DeleteEvent(UID, StrToUInt64(MsgID));
end;
ClearSelection(UID);
end;
end;
end;
class procedure TChatMethods.GetReactions(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
Chat: TUID;
ID: String;
MsgID: TMsgID;
begin
Chat := SciterVarToString(argv);
Inc(argv);
ID := SciterVarToString(argv);
if TryStrToUInt64(ID, MsgID) then
if OnlFeature(Account.AccProto) then
Account.AccProto.GetReactions(Chat, MsgID);
end;
class procedure TChatMethods.AddToAntispam(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
if not (spamfilter.badwords = '') and not (spamfilter.badwords[Length(spamfilter.badwords)] = ';') then
spamfilter.badwords := spamfilter.badwords + ';';
spamfilter.badwords := spamfilter.badwords + UI.Chat.SelectedText;
end;
class procedure TChatMethods.ToggleSmiles(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
UseSmiles := not UseSmiles;
UI.Chat.InitSettings;
UI.Chat.UpdateSmiles;
end;
class procedure TChatMethods.ToggleRelTimes(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
RelativeTimeInChat := not RelativeTimeInChat;
end;
class procedure TChatMethods.RealizeEvents(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
UID: TUID;
Ev: Thevent;
Res: Boolean;
begin
Res := False;
if argc > 0 then
begin
UID := SciterVarToString(argv);
Ev := eventQ.firstEventFor(Account.AccProto.GetContact(UID));
if Assigned(Ev) then
begin
eventQ.Remove(Ev);
realizeEvent(Ev);
Res := True;
end;
end;
API.ValueIntDataSet(retval, RDUtils.IfThen(Res, 1), T_BOOL, 0);
end;
class procedure TChatMethods.StoreSplit(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
t, val: Integer;
begin
if argc < 2 then
Exit;
API.ValueIntData(argv, t);
Inc(argv);
val := 150;
API.ValueIntData(argv, val);
if val > 0 then
if t = 0 then
SplitX := val
else
SplitY := val;
end;
class procedure TChatMethods.InputChangedFor(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
UID: TUID;
Txt: String;
TxtLen: Integer;
begin
if argc < 2 then
Exit;
UID := SciterVarToString(argv);
Inc(argv);
Txt := SciterVarToString(argv);
TxtLen := Length(Txt);
if EnableSpellCheck and SpellTextChanged(Txt) then
begin
SetSpellText(Txt);
DoSpellCheck;
end;
// Send typing notify
Account.AccProto.InputChangedFor(Account.AccProto.GetContact(UID), TxtLen = 0);
end;
class procedure TChatMethods.GetMessageByIdx(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
UID: TUID;
he: Thevent;
history: Thistory;
pQuoteIdx: Integer;
res: TParams;
quote: String;
begin
if argc < 2 then
Exit;
UID := SciterVarToString(argv);
Inc(argv);
API.ValueIntData(argv, pQuoteIdx);
quote := '';
history := UI.Chat.GetHistory(UID);
if Assigned(history) then
with history do
begin
// search for a msg to quote
he := nil;
if pQuoteIdx > 0 then
begin
Inc(pQuoteIdx);
he := getLastEvent(pQuoteIdx);
while Assigned(he) and (Account.AccProto.IsMyAcc(he.who) or not (he.kind in [EK_msg])) do
begin
Inc(pQuoteIdx);
he.Free;
he := getLastEvent(pQuoteIdx);
end;
if he = nil then
pQuoteIdx := 0;
end;
if pQuoteIdx = 0 then // nothing found, try restarting search from the end
begin
pQuoteIdx := 1;
he := getLastEvent(pQuoteIdx);
while Assigned(he) and (Account.AccProto.IsMyAcc(he.who) or not (he.kind in [EK_msg])) do
begin
Inc(pQuoteIdx);
he.Free;
he := getLastEvent(pQuoteIdx);
end;
if he = nil then
pQuoteIdx := 0;
end;
if pQuoteIdx = 0 then
begin
API.ValueIntDataSet(retval, 0, T_BOOL, 0);
Exit; // nothing found, really
end;
if Assigned(he) then
begin
quote := he.getBodyText();
FreeAndNil(he);
end;
end;
SetLength(res, 2);
res[0] := pQuoteIdx;
res[1] := quote;
V2S(res, retval);
end;
class procedure TChatMethods.WrapText(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
str: String;
WrappedStr: String;
Limit: Integer;
begin
if argc = 0 then
Exit;
Str := SciterVarToString(argv);
if argc > 1 then
begin
Inc(argv);
API.ValueIntData(argv, Limit);
end else
Limit := 50;
WrappedStr := System.SysUtils.WrapText(Str, Limit);
API.ValueStringDataSet(retval, PWideChar(WrappedStr), Length(WrappedStr), 0);
end;
procedure Send(ch: TChatInfo; flags_: Integer; const msg: String = '');
begin
if Trim(msg) = '' then
begin
MsgDlg('Can''t send an empty message', True, mtWarning);
Exit;
end;
if Assigned(UI.Chat) then
with UI.Chat do
begin
SawAllHere;
OutboxAdd(OE_msg, ch.who, flags_, msg);
if ch.single then
begin
if ClosePageOnSingle then
CloseChat(ch)
else
Hide;
end;
end;
end;
function CheckOfflineEncryption(var Cnt: TICQContact; const Msg: String; Flag: Integer): Boolean;
var
ShouldEncrypt, IsBin: Boolean;
begin
Result := True;
IsBin := (Pos(RnQImageTag, Msg) > 0) or ((Pos(RnQImageExTag, Msg) > 0)) or (IF_Bin and Flag > 0);
ShouldEncrypt := (Account.AccProto.UseCryptMsg and (Cnt.Crypt.SupportCryptMsg or
(Account.AccProto.fECCKeys.Generated and Account.AccProto.UseEccCryptMsg and Cnt.crypt.SupportEcc)))
and not IsBin;
if ShouldEncrypt and Cnt.IsOffline then
if MessageDlg(GetTranslation('Encrypted messages cannot be delivered to offline contacts. Send without encryption?'), mtConfirmation, [mbYes, mbNo]) = mrYes then
begin
Cnt.Crypt.SupportCryptMsg := False;
Cnt.Crypt.SupportEcc := False;
end else
Result := False;
end;
class procedure TChatMethods.SendChatMessage(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
UID: TUID;
Msg: String;
opt, flag: Integer;
ch: TChatInfo;
s, s1: String;
Max, I: Integer;
UINs: Variant;
begin
if argc < 3 then
Exit;
API.ValueIntData(argv, opt);
Inc(argv);
UID := SciterVarToString(argv);
Inc(argv);
Msg := SciterVarToString(argv);
flag := 0;
ch := UI.Chat.chats.byUIN(UID);
if (ch = nil) or (ch.chatType = CT_PLUGING) or (ch.who = nil) then
Exit;
case opt of
0:
begin
if not CheckOfflineEncryption(ch.who, msg, flag) then
Exit;
Max := Account.AccProto.MaxCharsFor(ch.who);
if Length(msg) > Max then
begin
if MessageDlg(getTranslation('Your message is too long. Max %d characters.\nSplit the message?',
[Max]), mtInformation, [mbYes, mbNo]) = mrYes then
begin
s := msg;
repeat
s1 := Copy(s, 1, Max - 1);
Delete(s, 1, Max - 1);
Send(ch, flag, s1);
until Length(s) < Max;
Send(ch, flag, s);
V2S(True, retval);
Exit;
end
end
else
begin
if Trim(msg) = '' then
begin
V2S(False, retval);
if CloseChatOnSend then
begin
if ClosePageOnSingle then
UI.Chat.CloseChat(ch)
else
UI.Chat.Hide
end else
Send(ch, flag, msg); // Just show empty msg alert
end
else
begin
V2S(True, retval);
Send(ch, flag, msg);
end;
end;
end;
1:
begin
Inc(argv);
S2V(argv, UINs);
for I := VarArrayLowBound(UINs, 1) to VarArrayHighBound(UINs, 1) do
OutboxAdd(OE_msg, Account.AccProto.GetContact(TUID(UINs[I])), IF_multiple, msg);
V2S(True, retval);
end;
2:
begin
with UI.Chat do
for I := 0 to chats.count - 1 do
if chats.byIdx(I).chatType = CT_IM then
OutboxAdd(OE_msg, chats.byIdx(I).who, IF_multiple, msg);
V2S(True, retval);
end;
end;
end;
class procedure TChatMethods.EditChatMessage(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
Chat: TUID;
Cnt: TICQContact;
ID, Text: String;
MsgID: TMsgID;
begin
Chat := SciterVarToString(argv);
Inc(argv);
ID := SciterVarToString(argv);
if not TryStrToUInt64(ID, MsgID) then
Exit;
Inc(argv);
Text := SciterVarToString(argv);
Cnt := Account.AccProto.GetContact(Chat);
if CheckOfflineEncryption(Cnt, Text, 0) then
Account.AccProto.SendMsg(Cnt, EK_Msg, 0, '', Text, MsgID);
end;
class procedure TChatMethods.GetChatMessageText(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
Chat: TUID;
ID: String;
MsgID: TMsgID;
Event: Thevent;
begin
Chat := SciterVarToString(argv);
Inc(argv);
ID := SciterVarToString(argv);
if not TryStrToUInt64(ID, MsgID) then
Exit;
Event := SQLDB.GetByMsgID(Chat, MsgID, False);
if Event = nil then
Exit;
V2S(Event.textData, retval);
end;
class procedure TChatMethods.ClosePages(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
opt, i: Integer;
begin
if argc = 0 then
Exit;
opt := -1;
API.ValueIntData(argv, opt);
with UI.Chat do
case opt of
0:
begin
SawAllHere;
CloseThisPage;
end;
1: CloseAllPages;
2:
begin
for I := chats.Count - 1 downto 0 do
if not (chats.byIdx(I).who.equals(UI.Chat.CurrentContact)) then
ClosePageAt(I);
end;
3:
try
for I := chats.count - 1 downto 0 do
if chats.byIdx(I).chatType = CT_IM then
if chats.byIdx(I).who.isOffline then
ClosePageAt(I);
except end;
4:
begin
SawAllHere;
addToIgnoreList(UI.Chat.CurrentContact);
if MessageDlg(GetTranslation('Do you want to remove %s from your contact list?', [UI.Chat.CurrentContact.displayed]), mtConfirmation,
[mbYes, mbNo]) = mrYes then
removeFromRoster(UI.Chat.CurrentContact);
CloseThisPage;
end;
5:
begin
if MessageDlg(GetTranslation('Are you sure you want to ignore multiple contacts?'), mtConfirmation, [mbYes, mbNo]) <> mrYes then
Exit;
for I := chats.count - 1 downto 0 do
if chats.byIdx(I).chatType = CT_IM then
if notInList.exists(chats.byIdx(I).who) then
begin
addToIgnoreList(chats.byIdx(I).who);
removeFromRoster(chats.byIdx(I).who);
ClosePageAt(I);
end;
end;
end;
end;
class procedure TChatMethods.UploadFile(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
opt: Integer;
url: String;
begin
if argc = 0 then
Exit;
API.ValueIntData(argv, opt);
url := UI.Chat.FileUpload(opt = 2);
V2S(url, retval);
end;
class procedure TChatMethods.SaveEmbeddedFile(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
Pic: TMemoryStream;
StrUrl: String;
Hash: LongWord;
Img: TBytes;
Fmt: String;
begin
if argc = 0 then
Exit;
StrUrl := SciterVarToString(argv);
if TryStrToLongWord(Copy(StrUrl, 10, Length(StrUrl)), Hash) and Assigned(EmbeddedImgs) and EmbeddedImgs.TryGetValue(Hash, Img) then
begin
Pic := TMemoryStream.Create;
Pic.Write(Img, Length(Img));
Fmt := PAFormat[DetectFileFormatStream(Pic)];
Delete(Fmt, 1, 1);
Fmt := OpenSaveDlg(nil, '', False, Fmt);
if Fmt > '' then
Pic.SaveToFile(Fmt);
Pic.Free;
end;
end;
class procedure TChatMethods.ChatButtonClick(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
ID: String;
right: Boolean;
r, x, y, PicMaxSize: Integer;
fn, sU: String;
s: RawByteString;
fs: TFileStream;
isRnQPic: Boolean;
ev: Thevent;
begin
if argc = 0 then
Exit;
ID := SciterVarToString(argv);
r := 0;
if argc > 1 then
begin
Inc(argv);
API.ValueIntData(argv, r);
end;
right := r = 1;
x := 0;
if argc > 2 then
begin
Inc(argv);
API.ValueIntData(argv, x);
end;
y := 0;
if argc > 3 then
begin
Inc(argv);
API.ValueIntData(argv, y);
end;
with UI.Chat do
if ID = 'infoBtn' then
CurrentContact.ViewInfo
else if ID = 'singleBtn' then
UI.Chat.chats.byContact(CurrentContact).single := right
else if ID = 'picsBtn' then
begin
if not OnlFeature(Account.AccProto) then Exit;
if OpenSaveFileDialog(Application.Handle, '*', getSupPicExts, //+ ';'#0 + 'R&Q Pics Files (wbmp)|*.wbmp'
'', 'Select R&Q Pic File', fn, True) then
begin
if not FileExists(fn) then
begin
MsgDlg('File doesn''t exist', true, mtError);
Exit;
end;
if not isSupportedPicFile(fn) then
begin
MsgDlg('This picture format is not supported', True, mtError);
Exit;
end;
PicMaxSize := Round(Account.AccProto.MaxCharsFor(CurrentContact) * 3 / 4 ) - 100;
fs := TFileStream.Create(fn, fmOpenRead or fmShareDenyNone);
sU := ExtractFileExt(fn);
isRnQPic := (sU = '.wbmp') or (sU = '.wbm');
if (not isRnQPic and (fs.Size > PicMaxSize)) or (fs.Size < 4) then
begin
MsgDlg('Max ' + IntToStr(PicMaxSize) + ' bytes', true, mtError);
MsgDlg('This file is too big', true, mtError);
fs.Free;
Exit;
end;
if (isRnQPic and (fs.Size > 0)) or (fs.Size < 4) then
begin
// Unsupported for now!
MsgDlg('This picture format is not supported', True, mtError);
fs.Free;
Exit;
end;
SetLength(s, fs.Size);
if fs.Size > 1 then
fs.Read(s[1], Length(s))
else
s := '';
fs.Free;
OutboxAdd(OE_msg, CurrentContact, IF_Bin, RnQImageExTag + Base64EncodeString(s) + RnQImageExUnTag);
s := '';
end;
end else if ID = 'buzzBtn' then
begin
if not OnlFeature(Account.AccProto) then Exit;
if Account.AccProto.SendBuzz(CurrentContact) then
begin
ev := Thevent.new(EK_buzz, nil, Account.AccProto.getMyInfo, Now, '', [], 0);
ev.outgoing := True;
UI.Chat.AddEvent(CurrentContact, ev);
end else
MsgDlg('Wait at least 15 seconds before buzzing again', True, mtBuzz)
end;
end;
class procedure TChatMethods.PluginButtonClick(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
title: String;
id, mbtn: Integer;
pr: procedure(Button: integer);
begin
if argc = 0 then
Exit;
id := 0;
API.ValueIntData(argv, id);
mbtn := 0;
Inc(argv);
title := SciterVarToString(argv);
if argc > 2 then
begin
Inc(argv);
API.ValueIntData(argv, mbtn);
if mbtn = 1 then mbtn := 0
else if mbtn = 2 then mbtn := 1
else if mbtn = 4 then mbtn := 2;
end;
if (id > 0) then
try
pr := Pointer(id);
pr(mbtn);
except
MsgDlg(GetTranslation('Error at plugin "%s"', [title]), False, mtError);
end;
end;
class procedure TChatMethods.ToggleTranslit(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
with UI.Chat do
if Assigned(TranslitList) and (TranslitList.Count > 0) and Assigned(CurrentContact) then
begin
CurrentContact.SendTransl := not CurrentContact.SendTransl;
UpdateStatusBar;
UpdateViewInfo(CurrentContact);
end;
end;
//class procedure TChatMethods.QIPPwd(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
//var
// opt: Integer;
// pwd: String;
//begin
// if argc = 0 then
// Exit;
//
// opt := 0;
// API.ValueIntData(argv, opt);
//
// with UI.Chat do
// begin
// if (CurrentContact = nil) or not (CurrentContact is TICQContact) then
// Exit;
//
// if opt = 1 then
// begin
// pwd := UI.EnterPassword(GetTranslation('Enter password for %s', [CurrentContact.displayed]), 32);
// if not (pwd = '') then
// CurrentContact.crypt.qippwd := qip_str2pass(pwd);
// end else if opt = 2 then
// CurrentContact.crypt.qippwd := 0;
// UI.Chat.UpdateContactStatus(CurrentContact);
// end;
//end;
class procedure TChatMethods.SendStickerToCurrent(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
sticker: String;
begin
if argc = 0 then
Exit;
sticker := SciterVarToString(argv);
with UI.Chat do
if OnlFeature(Account.AccProto) then
begin
UI.Chat.SawAllHere;
OutboxAdd(OE_msg, CurrentContact, 0, GET_FILES_HOST + sticker);
end;
end;
class procedure TChatMethods.GetStoreStickerPacks(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
Account.AccProto.GetStoreStickerPacks;
end;
class procedure TChatMethods.GetStickerPacks(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
StickerPacks: TStickerPacks;
StickerPacksVar: TParams;
begin
StickerPacks := SQLDB.GetStickerPacks;
SetLength(StickerPacksVar, Length(StickerPacks));
for var I := 0 to Length(StickerPacks) - 1 do
begin
if (Length(StickerPacks[I].Content) = 0) and (StickerPacks[I].ListIconLink = '') then
StickerPacks[I].ListIconLink := 'listicon:n' + IntToStr(StickerPacks[I].Id);
StickerPacksVar[I] := UI.RecordToVar(StickerPacks[I]);
end;
V2S(StickerPacksVar, retval);
end;
class procedure TChatMethods.SearchStickerPacks(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
Qry: String;
begin
if argc = 0 then
Exit;
Qry := Trim(SciterVarToString(argv));
if Qry = '' then
Exit;
if Qry.StartsWith('storeid:') then
Account.AccProto.SearchStoreStickerPack(Qry.Replace('storeid:', ''), SIDT_STOREID)
else if Qry.StartsWith('fileid:') then
Account.AccProto.SearchStoreStickerPack(Qry.Replace('fileid:', ''), SIDT_FILEID)
else
Account.AccProto.SearchStoreStickerPacks(Qry);
end;
class procedure TChatMethods.BuyStickerPack(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
if argc > 0 then
Account.AccProto.BuyStickerPack(SciterVarToString(argv));
end;
class procedure TChatMethods.RemoveStickerPack(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
if argc > 0 then
Account.AccProto.RemoveStickerPack(SciterVarToString(argv));
end;
class procedure TChatMethods.GetStickerPackContent(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
Content: TArray;
begin
if argc > 0 then
begin
Content := Stickers.GetStickerPackContent(SciterVarToString(argv));
V2S(Content, retval);
end;
end;
class procedure TChatMethods.SearchStickersByKeywords(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
Packs: TStickerPacks;
Keywords: String;
Found: TParams;
label
BreakOut;
begin
if argc = 0 then
Exit;
Keywords := Trim(ReplaceText(SciterVarToString(argv), '∙', ''));
Packs := SQLDB.GetStickerPacks(True);
if Length(Packs) > 0 then
for var Pack in Packs do
for var I := 0 to Length(Pack.Keywords) - 1 do
if ContainsText(Pack.Keywords[I], Keywords) and (I < Length(Pack.Content)) then
begin
SetLength(Found, Length(Found) + 1);
Found[High(Found)] := VarArrayCreate([0, 2], varVariant);
Found[High(Found)][0] := Pack.ContentType;
Found[High(Found)][1] := Pack.Content[I];
Found[High(Found)][2] := Pack.Keywords[I];
if Length(Found) >= 40 then
goto BreakOut;
end;
BreakOut:
Finalize(Packs);
V2S(Found, retval);
end;
class procedure TChatMethods.SpanEmojis(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
S: String;
First: Integer;
begin
S := SciterVarToString(argv);
if S = '' then
begin
V2S(S, retval);
Exit;
end;
First := -1;
for var I := 1 to Length(S) do
if (Ord(S[I]) > $20) and (Ord(S[I]) < $451) then
begin
First := I - 1;
Break;
end;
if First = -1 then
S := '' + S + ''
else if First > 1 then
S := '' + Copy(S, 1, First - 1) + '' + Copy(S, First, Length(S));
V2S(S, retval);
end;
class procedure TChatMethods.GetSpellingSuggestions(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
if EnableSpellCheck and (argc > 0) then
V2S(GetSuggestions(SciterVarToString(argv)), retval);
end;
procedure TChatBox.Show;
begin
Call('show');
end;
procedure TChatBox.Hide;
begin
Call('hide');
end;
procedure TChatBox.UpdateCaption(const Caption: String);
begin
Call('updateCaption', [Caption]);
end;
procedure TChatBox.UpdateHintsTraslation;
begin
Call('updateHintsTraslation');
end;
procedure TChatBox.UpdatePageSettings(cnt: TICQContact);
var
UIDBG, GrpBG: TPicName;
IsUseCntThemes: Boolean;
Settings: TPageSettings;
begin
IsUseCntThemes := UseContactThemes and Assigned(ContactsTheme) and Assigned(cnt);
if IsUseCntThemes then
begin
UIDBG := TPicName(AnsiLowerCase(cnt.UID)) + '.' + PIC_CHAT_BG;
GrpBG := TPicName('group.') + TPicName(AnsiLowerCase(groups.id2name(cnt.group))) + '.' + PIC_CHAT_BG;
end;
Settings.splitX := SplitX;
Settings.splitY := SplitY;
// Tiled background image
Settings.tiled := '';
if IsUseCntThemes and (ContactsTheme.GetPicSize(RQteDefault, UIDBG + '5').cx > 0) then
Settings.tiled := 'contactpic:' + UIDBG + '5'
else if IsUseCntThemes and (ContactsTheme.GetPicSize(RQteDefault, GrpBG + '5').cx > 0) then
Settings.tiled := 'contactpic:' + GrpBG + '5'
else if theme.GetPicSize(RQteDefault, PIC_CHAT_BG + '5').cx > 0 then
Settings.tiled := 'themepic:' + PIC_CHAT_BG + '5';
// Positioned background image
Settings.positioned := '';
if IsUseCntThemes then
if ContactsTheme.GetPicSize(RQteDefault, UIDBG + '1').cx > 0 then
Settings.positioned := 'contactpic:' + UIDBG + '1'
else if ContactsTheme.GetPicSize(RQteDefault, UIDBG + '2').cx > 0 then
Settings.positioned := 'contactpic:' + UIDBG + '2'
else if ContactsTheme.GetPicSize(RQteDefault, UIDBG + '3').cx > 0 then
Settings.positioned := 'contactpic:' + UIDBG + '3'
else if ContactsTheme.GetPicSize(RQteDefault, UIDBG + '4').cx > 0 then
Settings.positioned := 'contactpic:' + UIDBG + '4';
if IsUseCntThemes and (Settings.positioned = '') then
if ContactsTheme.GetPicSize(RQteDefault, GrpBG + '1').cx > 0 then
Settings.positioned := 'contactpic:' + GrpBG + '1'
else if ContactsTheme.GetPicSize(RQteDefault, GrpBG + '2').cx > 0 then
Settings.positioned := 'contactpic:' + GrpBG + '2'
else if ContactsTheme.GetPicSize(RQteDefault, GrpBG + '3').cx > 0 then
Settings.positioned := 'contactpic:' + GrpBG + '3'
else if ContactsTheme.GetPicSize(RQteDefault, GrpBG + '4').cx > 0 then
Settings.positioned := 'contactpic:' + GrpBG + '4';
if Settings.positioned = '' then
if theme.GetPicSize(RQteDefault, PIC_CHAT_BG + '1').cx > 0 then
Settings.positioned := 'themepic:' + PIC_CHAT_BG + '1'
else if theme.GetPicSize(RQteDefault, PIC_CHAT_BG + '2').cx > 0 then
Settings.positioned := 'themepic:' + PIC_CHAT_BG + '2'
else if theme.GetPicSize(RQteDefault, PIC_CHAT_BG + '3').cx > 0 then
Settings.positioned := 'themepic:' + PIC_CHAT_BG + '3'
else if theme.GetPicSize(RQteDefault, PIC_CHAT_BG + '4').cx > 0 then
Settings.positioned := 'themepic:' + PIC_CHAT_BG + '4';
try
PageCall(cnt.UID, 'updateSettings', [UI.RecordToVar(Settings)]);
except
on e: ESciterCallException do
MsgDlg('Error in updateSettings: ' + e.Message, False, mtError);
end;
end;
procedure TChatBox.OpenPage(cnt: TICQContact; focused: Boolean = False);
begin
if not Assigned(cnt) then
Exit;
if not histories.ContainsKey(cnt.UID) then
histories.Add(cnt.UID, Thistory.Create(cnt.UID));
if Preview then
InitMsgPreview;
Call('openChatPage', [cnt.UID, cnt.displayed, focused]);
end;
procedure TChatBox.OpenPage(ID: Integer; const Caption: String);
begin
Call('openPluginPage', [ID, Caption]);
end;
procedure TChatBox.ClosePage(uid: TUID = '');
var
h: Thistory;
el: HELEMENT;
begin
el := GetPage(uid);
if Assigned(el) and IsValid(el) then
begin
if uid = '' then
uid := UI.CallOnElement(el, 'getChatId', [], True);
if histories.TryGetValue(uid, h) then
histories.Remove(uid);
UI.CallOnElement(el, 'close', [], True);
end;
end;
procedure TChatBox.ClosePage(id: Integer);
begin
PageCall(id, 'close', []);
end;
procedure TChatBox.SwitchToPage(const uid: TUID);
begin
PageFire(uid, 'switchtopage', Null);
end;
procedure TChatBox.SwitchToPage(id: Integer);
begin
PageFire(id, 'switchtopage', Null);
end;
procedure TChatBox.SwitchToNextPage;
begin
Call('setCurrentPage', [1]);
end;
procedure TChatBox.SwitchToPrevPage;
begin
Call('setCurrentPage', [-1]);
end;
procedure TChatBox.UpdateSpelling(data: Variant);
begin
PageFire('', 'updatespell', data);
end;
procedure TChatBox.RedrawTabs;
var
ch: TchatInfo;
begin
for var I := 0 to chats.Count - 1 do
begin
ch := chats.byIdx(I);
if Assigned(ch) and Assigned(ch.who) then
RedrawChatTab(ch.who);
end;
end;
procedure TChatBox.RedrawChatTab(c: TICQContact);
var
ci: TchatInfo;
ev: Thevent;
tabPic, add: TPicName;
png, pngadd: TPNGImage;
hash, hashadd: LongWord;
mem: TMemoryStream;
begin
ci := chats.byContact(c);
if ci = nil then
Exit;
tabPic := '';
ev := eventQ.firstEventFor(c);
if Assigned(ev) and not (ev.pic = '') then
tabPic := ev.pic
else if c.typing.bIsTyping then
tabPic := PIC_TYPING
else if showStatusOnTabs then
tabPic := c.StatusImg
else
tabPic := '';
add := '';
if c.Official then
add := PIC_OFFICIAL
else if c.Bot then
add := PIC_BOT;
hash := 0;
hashadd := 0;
if not (tabPic = '') then
begin
// Main icon
if theme.Pic2PNG(tabPic, png) then
if Assigned(png) then
begin
mem := TMemoryStream.Create;
try
png.SaveToStream(mem);
hash := CalcMurmur2(mem);
finally
FreeAndNil(mem);
end;
end;
if not (hash = 0) then
if TabsIconCache.ContainsKey(hash) then
FreeAndNil(png)
else if Assigned(png) then
TabsIconCache.AddOrSetValue(hash, png);
// Additional icon in the bottom right corner
if not (add = '') then
begin
if theme.Pic2PNG(add, pngadd) then
if Assigned(pngadd) then
begin
mem := TMemoryStream.Create;
try
pngadd.SaveToStream(mem);
hashadd := CalcMurmur2(mem);
finally
FreeAndNil(mem);
end;
end;
if not (hashadd = 0) then
if TabsIconCache.ContainsKey(hashadd) then
FreeAndNil(pngadd)
else if Assigned(pngadd) then
TabsIconCache.AddOrSetValue(hashadd, pngadd);
end;
end;
RedrawTab(c, hash, hashadd);
end;
procedure TChatBox.RedrawPluginTab(ci: TchatInfo);
var
tabPic: TPicName;
png: TPNGImage;
hash: LongWord;
mem: TMemoryStream;
begin
if ci = nil then
Exit;
hash := 0;
tabPic := 'plugintab' + IntToStr(ci.ID);
if theme.Pic2PNG(tabPic, png) then
if Assigned(png) then
begin
mem := TMemoryStream.Create;
try
png.SaveToStream(mem);
hash := CalcMurmur2(mem);
finally
FreeAndNil(mem);
end;
end;
if not (hash = 0) then
if TabsIconCache.ContainsKey(hash) then
FreeAndNil(png)
else if Assigned(png) then
TabsIconCache.AddOrSetValue(hash, png);
RedrawTab(ci.ID, ci.lastInputText, hash);
end;
procedure TChatBox.RedrawTab(c: TICQContact; hash, hashadd: LongWord);
begin
//OutputDebugString(PChar('redraw chat tab: ' + inttostr(hash) + ' | ' + c.displayed));
PageCall(c.UID, 'redrawTab', [c.displayed, hash, hashadd]);
end;
procedure TChatBox.RedrawTab(id: Integer; const caption: String; hash: LongWord);
begin
//OutputDebugString(PChar('redraw plugin tab: ' + inttostr(hash) + ' | ' + caption));
PageCall(id, 'redrawTab', [caption, hash]);
end;
procedure TChatBox.ClearEvents(const UID: TUID = '');
begin
PageCall(UID, 'clearEvents', []);
end;
procedure TChatBox.DeleteEvent(const UID: TUID; MsgID: TMsgID);
begin
PageCall(UID, 'deleteEvent', [UIntToStr(MsgID)]);
end;
procedure TChatBox.DeleteEvents(const UID: TUID; st, en: TDateTime);
var
ffs: TFormatSettings;
begin
ffs := TFormatSettings.Create(LOCALE_USER_DEFAULT);
ffs.DecimalSeparator := '.';
PageCall(UID, 'deleteEvents', [FloatToStr(st, ffs), FloatToStr(en, ffs)]);
end;
procedure TChatBox.InitSettings;
var
Settings: TChatSettings;
begin
Settings.showSmiles := useSmiles;
Settings.showAvatar := avatarShowInChat;
Settings.showSmileCaption := ShowSmileCaption;
Settings.showHintsInChat := ShowHintsInChat;
Settings.showSmartReplies := ShowSmartReplies;
Settings.showReactions := ShowReactions;
Settings.autoCopy := autocopyHist;
Settings.wheelVelocity := wheelVelocity;
Settings.imageQuality := ChatImageQuality;
Settings.smoothFontRendering := ChatSmoothFontRendering;
Settings.maxImgWidth := RDUtils.IfThen(LimitMaxChatImgWidth, MaxChatImgWidthVal);
Settings.maxImgHeight := RDUtils.IfThen(LimitMaxChatImgHeight, MaxChatImgHeightVal);
Settings.fontCodes := FontStyleCodes.Enabled;
Settings.showRelTimes := RelativeTimeInChat;
Settings.cachePath := UI.FilePathToURL(CacheDir);
Settings.viewTextWrap := bViewTextWrap;
Settings.animatedScroll := AnimatedScroll;
Settings.sendOnEnter := sendOnEnter;
Settings.quoteSelected := quoting.quoteselected;
Settings.cursorBelow := quoting.cursorBelow;
Settings.spellErrorStyle := spellErrorStyle;
Settings.spellErrorColor := color2html(spellErrorColor);
Settings.screenshotFormat := ScreenshotFormat;
Settings.screenshotFilename := GetSnapshotFilename;
Settings.chatCSS := ChatCSS;
Settings.msgBuffer := ChatLoadBuffer;
Settings.alwaysOnTop := ChatAlwaysOnTop;
try
Call('initSettings', [UI.RecordToVar(Settings)]);
except
on e: ESciterCallException do
MsgDlg('Error in InitSettings: ' + e.Message, False, mtError);
end;
end;
procedure TChatBox.InitMsgPreview;
begin
try
Call('initMsgPreview', [GetTranslation('Select message to render its full version here')]);
except
on e: ESciterCallException do
MsgDlg('Error in InitSettingsMsgPreview: ' + e.Message, false, mtError);
end;
end;
function TChatBox.GetPluginBounds: TRect;
var
bounds: Variant;
begin
bounds := Call('getPluginBounds', []);
Result.Left := bounds[0];
Result.Top := bounds[1];
Result.Right := bounds[2];
Result.Bottom := bounds[3];
end;
procedure TChatBox.AddPluginButton(i: Integer);
begin
with UI.Chat.plugBtns do
Call('addPluginButton', [NativeInt(btns[i].proc)]);
end;
procedure TChatBox.DelPluginButton(i: Integer);
begin
with UI.Chat.plugBtns do
Call('delPluginButton', [NativeInt(btns[i].proc)]);
end;
procedure TChatBox.ModifyPluginButton(i: Integer);
begin
with UI.Chat.plugBtns do
Call('modifyPluginButton', [NativeInt(btns[i].proc), btns[i].hint, btns[i].pic]);
end;
procedure TChatBox.SetSendBtnImage(const pic: TPicName);
var
Sprite: TSprite;
begin
if pic = '' then
Exit;
Sprite := MakeSprite(pic);
Call('setSendBtnImage', [UI.RecordToVar(Sprite)]);
end;
procedure TChatBox.ClearAvatar(const uid: TUID);
begin
PageCall(uid, 'clearAvatar', [])
end;
procedure TChatBox.UpdateAvatar(const uid: TUID);
begin
PageCall(uid, 'updateAvatar', []);
end;
procedure TChatBox.SetupChatButtons;
begin
Call('setupChatButtons', [not Assigned(CurrentContact)]);
end;
procedure TChatBox.SetupSingleBtn(status: Boolean);
begin
Call('setupSingleBtn', [status]);
end;
procedure TChatBox.SetupFileBtn(status: Boolean);
begin
Call('setupFileBtn', [status]);
end;
procedure TChatBox.SetupStickersBtn(status: Boolean);
begin
Call('setupStickersBtn', [status]);
end;
procedure TChatBox.SetupBuzzBtn(status: Boolean);
begin
Call('setupBuzzBtn', [status]);
end;
procedure TChatBox.ResetHistory;
var
elc: TArray;
begin
elc := SelectAll('#pages > .page');
for var I := 0 to Length(elc) - 1 do
UI.CallOnElement(elc[i], 'resetHistory', [], True);
end;
procedure TChatBox.ApplyTheme;
var
elc: TArray;
begin
Call('applyTheme', []);
elc := SelectAll('#pages > .page');
for var I := 0 to Length(elc) - 1 do
UpdatePageSettings(Account.AccProto.GetContact(TUID(UI.CallOnElement(elc[i], 'getChatId', [], True))));
end;
procedure TChatBox.UpdateSmiles;
begin
Fire('updatesmiles', Null);
end;
procedure TChatBox.ReloadSmiles;
var
smiles: TParams;
smileData: TPair;
sm: String;
begin
SetLength(smiles, theme.SmilesCount);
if theme.SmilesCount > 0 then
for var I := 0 to theme.SmilesCount - 1 do
begin
sm := theme.GetSmileObj(I).SmlStr.Strings[0];
theme.smileArray.TryGetValue(sm, smileData);
if (smileData.Value.Width > 0) and (smileData.Value.Height > 0) then
begin
smiles[I] := VarArrayCreate([0, 3], varVariant);
smiles[I][0] := smileData.Key;
smiles[I][1] := sm;
smiles[I][2] := smileData.Value.Width;
smiles[I][3] := smileData.Value.Height;
end else
smiles[I] := False;
end;
Call('reloadSmiles', [smiles]);
end;
procedure TChatBox.ReloadEmojis;
function GetOffset(num: Integer): TPoint;
var
row, column: Integer;
begin
column := num mod EmojisInARow;
row := floor(num / EmojisInARow);
Result.X := column * EmojiSize;
Result.Y := row * EmojiSize;
end;
function GetEmoji(num: Integer): String;
var
vals: TArray;
keys: TArray>;
begin
Result := '';
vals := Emojis.Values.ToArray;
keys := Emojis.Keys.ToArray;
for var I := 0 to Length(vals) - 1 do
begin
if vals[i] = num then
begin
Result := Char.ConvertFromUtf32(keys[i].Key);
if not (keys[i].Value = 0) then
Result := Result + Char.ConvertFromUtf32(keys[i].Value);
Break;
end;
end;
end;
function GetEmojiNum(cp1, cp2: Cardinal): Integer;
var
pos: Integer;
keys: TArray>;
begin
Result := 0;
keys := Emojis.Keys.ToArray;
for var I := 0 to Length(keys) - 1 do
if (keys[I].Key = cp1) and (keys[I].Value = cp2) and Emojis.TryGetValue(keys[I], pos) then
begin
Result := pos;
Exit;
end;
end;
var
I, J: Integer;
Arr: TArray;
EData, ECat: TArray;
Em: Variant;
Emoji, EmojiNums: TParams;
P: TPoint;
HasEmojis: Boolean;
EmojiCats: TDictionary>>;
Cat, Weight: Integer;
CP1, CP2: Cardinal;
begin
if not TryStrToInt(theme.GetString('emojis.size'), EmojiSize) then
EmojiSize := 22;
if not TryStrToInt(theme.GetString('emojis.inarow'), EmojisInARow) then
EmojisInARow := 36;
HasEmojis := theme.HasOrigPic('emojis.sprite');
SetLength(Emoji, 9);
SetLength(EmojiNums, 9);
EmojiCats := TDictionary>>.Create;
if HasEmojis then
begin
Emojis.Clear;
for I := 0 to theme.EmojisCount - 1 do
begin
EData := theme.GetString('emojis.data.' + IntToStr(I), False).Split([',']);
if Length(EData) < 3 then
Continue;
CP1 := 0; CP2 := 0;
TryStrToUInt(EData[0], CP1);
TryStrToUInt(EData[1], CP2);
if (CP1 = 0) and (CP2 = 0) then
Continue;
Cat := 7; // misc by default
TryStrToInt(EData[2], Cat);
Weight := 1;
if Length(EData) = 4 then
TryStrToInt(EData[3], Weight);
Emojis.AddOrSetValue(TPair.Create(CP1, CP2), I);
if not EmojiCats.ContainsKey(Cat) then
EmojiCats.Add(Cat, TArray>.Create());
EmojiCats.TryGetValue(Cat, Arr);
Arr := Arr + [TPair.Create(Weight, I)];
EmojiCats.AddOrSetValue(Cat, Arr);
end;
SetLength(Singles, 0);
for var Key in Emojis.Keys do
if (Key.Value = 0) and (Key.Key <= $ffff) then
Singles := Singles + [Key.Key];
for I := 0 to 8 do
begin
EmojiCats.TryGetValue(I, Arr);
TArray.Sort(Arr, TComparer.Construct(
function(const Left, Right: TIntPair): Integer
begin
Result := TComparer.Default.Compare(Left.Key, Right.Key);
end)
);
Emoji[I] := VarArrayCreate([0, Length(Arr) - 1], varVariant);
for J := 0 to Length(Arr) - 1 do
begin
P := GetOffset(Arr[J].Value);
Em := VarArrayCreate([0, 2], varVariant);
Em[0] := GetEmoji(Arr[J].Value);
Em[1] := -P.X;
Em[2] := -P.Y;
Emoji[I][J] := Em;
end;
ECat := theme.GetString('emojis.cats.' + IntToStr(I + 1), False).Split([',']);
CP1 := 0; CP2 := 0;
if Length(ECat) > 0 then
TryStrToUInt(ECat[0], CP1);
if Length(ECat) > 1 then
TryStrToUInt(ECat[1], CP2);
P := GetOffset(GetEmojiNum(CP1, CP2));
EmojiNums[i] := VarArrayCreate([0, 2], varVariant);
EmojiNums[i][0] := EmojiExtHints[i];
EmojiNums[i][1] := -P.X;
EmojiNums[i][2] := -P.Y;
end;
end;
Call('reloadEmojis', [HasEmojis, EmojiSize, Emoji, EmojiNums]);
EmojiCats.Free;
end;
procedure TChatBox.PreloadPickers;
var
LinkList: String;
begin
LinkList := GetCachedPickers;
if not (LinkList = '') then
Call('preloadImages', [LinkList]);
end;
procedure TChatBox.LoadStickers;
var
StickerPacks: TStickerPacks;
StickerPacksVar: TParams;
begin
StickerPacks := SQLDB.GetStickerPacks(True);
SetLength(StickerPacksVar, Length(StickerPacks));
for var I := 0 to Length(StickerPacks) - 1 do
StickerPacksVar[I] := UI.RecordToVar(StickerPacks[I]);
Call('loadStickers', [StickerPacksVar]);
end;
procedure TChatBox.LoadSearchResults;
var
StickerPacksVar: TParams;
begin
with Account.AccProto do
begin
SetLength(StickerPacksVar, Length(LastSearchPacks));
for var I := 0 to Length(LastSearchPacks) - 1 do
StickerPacksVar[I] := UI.RecordToVar(LastSearchPacks[I]);
end;
Call('updateSearchResults', [StickerPacksVar]);
SetLength(Account.AccProto.LastSearchPacks, 0);
end;
function GetHeaderTime(Time: TDateTime; Preview: Boolean = False): String;
var
Days, Hours, TMinutes, Minutes: Int64;
begin
if not Preview and RelativeTimeInChat then
begin
TMinutes := MinutesBetween(Now, Time);
if TMinutes < 1 then
Exit(GetTranslation('Just now'));
Days := TMinutes div 60 div 24;
Minutes := TMinutes - Days * 24 * 60;
Hours := Minutes div 60;
Minutes := Minutes mod 60;
if Days > RelativeTimeInChatDays then // Absolute date/time
Result := FormatDateTime(timeformat.chat, Time)
else if IsSameDay(Time, Yesterday) then // Yesterday, absolute time
Result := Format(GetTranslation('Yesterday at') + ' %.2d:%.2d', [HourOfTheDay(Time), MinuteOfTheHour(Time)])
else if IsToday(Time) then // Today
begin
if Hours >= RelativeTimeInChatHours then // Absolute time
Result := Format(GetTranslation('Today at') + ' %.2d:%.2d', [HourOfTheDay(Time), MinuteOfTheHour(Time)])
else if Hours > 0 then // Relative time
Result := Format('%d ' + GetHoursWord(Hours) + ' %d ' + GetMinutesWord(Minutes, True) + ' ' + GetTranslation('ago'), [Hours, Minutes])
else // Relative minutes
Result := Format('%d ' + GetMinutesWord(Minutes, True) + ' ' + GetTranslation('ago'), [Minutes]);
end else // Relative days, absolute time
Result := Format('%d ' + GetDaysWord(Days) + ' ' + GetTranslation('ago at') + ' %.2d:%.2d', [Days, HourOfTheDay(Time), MinuteOfTheHour(Time)])
end else
Result := FormatDateTime(timeformat.chat, Time);
end;
class procedure TChatMethods.GetEventHeaderTime(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
When: Double;
Val: Variant;
begin
API.ValueFloatData(argv, When);
Val := GetHeaderTime(When);
V2S(Val, retval);
end;
class procedure TChatMethods.AddReaction(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
ChatID: String;
MsgID: TMsgID;
Opt: Integer;
begin
ChatID := SciterVarToString(argv);
Inc(argv);
MsgID := StrToUInt64(SciterVarToString(argv));
Inc(argv);
Opt := StrToInt(SciterVarToString(argv));
if OnlFeature(Account.AccProto) then
Account.AccProto.AddReaction(ChatID, MsgID, Opt);
end;
class procedure TChatMethods.RemoveReaction(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
ChatID: String;
MsgID: TMsgID;
begin
ChatID := SciterVarToString(argv);
Inc(argv);
MsgID := StrToUInt64(SciterVarToString(argv));
if OnlFeature(Account.AccProto) then
Account.AccProto.RemoveReaction(ChatID, MsgID);
end;
class procedure TChatMethods.GetMyReaction(argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
MsgID: TMsgID;
begin
MsgID := StrToUInt64(SciterVarToString(argv));
V2S(GetMyReactionIndex(MsgID), retval);;
end;
procedure TChatBox.AddChatItem(var Params: TParams; var MsgData: TMessageData;
Evt: Thevent; Animate: Boolean);
var
msgText, msgCls, prefixCls, val1, val2: String;
statusImg1Rect, statusImg2Rect: TGPRect;
statusImg1PicName, statusImg2PicName: TPicName;
hdr: THeader;
st: Integer;
b: Byte;
sA, img: TBytes;
inv: Boolean;
imgList: TArray;
bodyImages: TArray;
bodyBin: TBytes;
i: Integer;
hash: LongWord;
ffs: TFormatSettings;
Dims: TPair;
ExtSticker: TArray;
Patch: TPatch;
Patches: TArray;
Deleted, Updated: Boolean;
Actions: TArray;
Reactions: TReactions;
begin
if Evt = nil then
Exit;
IsLastParsedEventMine := Evt.outgoing;
hdr := Evt.GetHeaderTexts;
msgText := Evt.GetBodyText;
if TrimMsgNewLines then
msgText := msgText.Trim([#13, #10]);
msgCls := 'msgFull';
if Animate then
msgCls := msgCls + ' hidden';
if IsLastParsedEventMine then
msgCls := msgCls + ' my';
MsgData.cls := msgCls;
ffs := TFormatSettings.Create(LOCALE_USER_DEFAULT);
ffs.DecimalSeparator := '.';
MsgData.time := FloatToStr(Evt.when, ffs);
MsgData.msgid := UIntToStr(Evt.ID);
MsgData.eventImg := Evt.pic;
MsgData.encrypted := IF_Encrypt and Evt.flags > 0;
MsgData.eccencrypted := IF_Encrypt_ECC and Evt.flags > 0;
MsgData.what := hdr.what;
case Evt.kind of
EK_INCOMING, EK_STATUSCHANGE:
begin
sA := Evt.GetBodyBin;
if length(sA) >= 4 then
begin
st := str2int(sA);
if st in [Byte(Low(Account.AccProto.statuses)) .. Byte(High(Account.AccProto.statuses))] then
begin
b := binToXStatus(sA);
if (st <> Byte(SC_ONLINE)) or (b = 0) then
begin
inv := (length(sA) > 4) and boolean(sA[4]);
statusImg1PicName := status2imgName(st, inv);
end;
if (b > 0) then
statusImg2PicName := XStatusArray[b].Status;
end;
end;
end;
EK_XstatusMsg:
begin
sA := Evt.GetBodyBin;
if length(sA) >= 1 then
if (Byte(sA[0]) <= High(XStatusArray)) then
statusImg1PicName := XStatusArray[Byte(sA[0])].Status;
end;
EK_OUTGOING:
begin
statusImg1PicName := status2imgName(Byte(SC_OFFLINE), False);
end;
end;
MsgData.statusImg := statusImg1PicName;
MsgData.statusImgExt := statusImg2PicName;
MsgData.when := GetHeaderTime(Evt.when, Preview);
MsgData.prefix := hdr.prefix;
SetLength(imgList, 0);
bodyBin := Evt.GetBodyBin;
if (Evt.kind = EK_msg) then
begin
if Length(bodyBin) > 4 then
begin
GetMsgImages(bodyBin, imgList);
if Assigned(EmbeddedImgs) and (Length(imgList) > 0) then
for i := 0 to Length(imgList) - 1 do
begin
hash := CalcMurmur2(imgList[i]);
if not EmbeddedImgs.ContainsKey(hash) then
EmbeddedImgs.Add(hash, imgList[i]);
bodyImages := bodyImages + [hash];
end;
SetLength(imgList, 0);
// end else if Evt.flags and IF_sticker > 0 then
// begin
// ExtSticker := SplitString(msgText, ':');
// if (Length(ExtSticker) >= 4) then
// begin
// sA := GetSticker(ExtSticker[1], ExtSticker[3]);
// hash := CalcMurmur2(sA);
// if not embeddedImgs.ContainsKey(hash) then
// embeddedImgs.Add(hash, sA);
// bodyImages := bodyImages + [IntToStr(hash)];
// msgText := '';
// end;
end;
end;
MsgData.msg := ParseMessageBody(msgText);
if Length(bodyImages) > 0 then
begin
MsgData.cls := MsgData.cls + ' binary';
MsgData.embedded := '
';
for i := 0 to Length(bodyImages) - 1 do
begin
Dims.Key := 0;
Dims.Value := 0;
if Assigned(EmbeddedImgs) and EmbeddedImgs.TryGetValue(bodyImages[i], img) then
Dims := GetImageDimensions(img);
FitImage(Dims.Key, Dims.Value);
MsgData.embedded := MsgData.embedded + ' 0) and (Dims.Value > 0), 'style="width: ' + IntToStr(Dims.Key) + 'px; height: ' + IntToStr(Dims.Value) + 'px" ', '') + 'alt="" />';
end;
MsgData.embedded := MsgData.embedded + '';
end;
MsgData.writeHist := logpref.writehistory and (BE_save in behaviour[Evt.kind].trig);
prefixCls := 'msgMulti';
if Evt.flags and IF_Patched > 0 then
begin
Updated := False;
Deleted := False;
Patches := SQLDB.GetPatches(Evt.ID);
SetLength(MsgData.patches, Length(Patches));
for I := Low(Patches) to High(Patches) do
begin
MsgData.patches[I] := VarArrayCreate([0, 1], varVariant);
MsgData.patches[I][0] := Patches[I].PatchType;
MsgData.patches[I][1] := Patches[I].PatchedText;
if (Patches[I].PatchType = 'delete') or (Patches[I].PatchType = 'modify') then
Deleted := True
else
Updated := True;
end;
SetLength(Actions, 0);
if Updated then
Actions := Actions + [GetTranslation('modified')];
if Deleted then
Actions := Actions + [GetTranslation('deleted')];
if Length(Actions) > 0 then
begin
MsgData.prefix := Trim(MsgData.prefix + ' (' + String.Join(', ', Actions) + ')');
prefixCls := prefixCls + ' updated';
end;
end;
MsgData.prefixCls := prefixCls;
if MsgsReactions.TryGetValue(Evt.ID, Reactions) then
begin
MsgData.myReaction := Reactions.My;
SetLength(MsgData.reactions, Reactions.Data.Count);
for I := 0 to Reactions.Data.Count - 1 do
begin
MsgData.reactions[I] := VarArrayCreate([0, 1], varVariant);
if Reactions.Data[I].GetValueSafe('reaction', val1) and Reactions.Data[I].GetValueSafe('counter', val2) then
begin
MsgData.reactions[I][0] := val1;
MsgData.reactions[I][1] := val2;
end;
end;
end;
Params := Params + [UI.RecordToVar(MsgData)];
end;
function TChatBox.GetPage(const UID: TUID): HELEMENT;
var
elc: TArray;
begin
Result := nil;
if not (UID = '') then
begin
elc := SelectAll('#pages > .page');
for var I := 0 to Length(elc) - 1 do
if UI.CallOnElement(elc[I], 'getChatId', [], True) = UID then
begin
Result := elc[I];
Break;
end;
end else
Result := Select('#pages > .page:current');
end;
function TChatBox.GetPage(id: Integer): HELEMENT;
var
elc: TArray;
begin
Result := nil;
if not (id = 0) then
begin
elc := SelectAll('#pages > .page');
for var I := 0 to Length(elc) - 1 do
if UI.CallOnElement(elc[I], 'getPlugId', [], True) = id then
begin
Result := elc[I];
Break;
end;
end;
end;
function TChatBox.GetLastEventTime(const UID: TUID): TDateTime;
var
el: HELEMENT;
begin
Result := 0;
el := GetPage(UID);
if Assigned(el) and IsValid(el) then
Result := StrToFloat(UI.CallOnElement(el, 'getLastEventTime', [], True));
end;
function TChatBox.GetHistory(const UID: TUID): Thistory;
begin
if not histories.TryGetValue(UID, Result) then
Result := nil;
end;
procedure TChatBox.PageFire(const UID: TUID; const name: String; data: Variant);
var
el: HELEMENT;
begin
el := GetPage(UID);
if Assigned(el) and IsValid(el) then
UI.FireOnElement(el, name, data, True);
end;
procedure TChatBox.PageFire(id: Integer; const name: String; data: Variant);
var
el: HELEMENT;
begin
el := GetPage(id);
if Assigned(el) and IsValid(el) then
UI.FireOnElement(el, name, data, False)
end;
procedure TChatBox.PageCall(const UID: TUID; const method: AnsiString; const args: TParams);
var
el: HELEMENT;
begin
el := GetPage(UID);
if Assigned(el) and IsValid(el) then
UI.CallOnElement(el, method, args, True);
end;
procedure TChatBox.PageCall(id: Integer; const method: AnsiString; const args: TParams);
var
el: HELEMENT;
begin
el := GetPage(id);
if Assigned(el) and IsValid(el) then
UI.CallOnElement(el, method, args, True);
end;
procedure TChatBox.SendChatItems(const UID: TUID; var Params: TParams; Action: TSendAction = SA_APPEND);
var
Event: String;
begin
Event := '';
if action = SA_APPEND then
Event := 'appendevent'
else if action = SA_PREPEND then
Event := 'prependevent'
else if action = SA_UPDATE then
Event := 'updateevent';
if not (Event = '') then
PageFire(UID, Event, Params);
end;
procedure TChatBox.HideHistory(const UID: TUID);
begin
PageCall(UID, 'hideHistory', []);
end;
procedure TChatBox.ViewInWindow(const title, body: String; const when: String; const formicon: String = '');
begin
Call('viewInWindow', [title, body, when, formicon]);
end;
procedure TChatBox.AddToCurrentInput(const s: String);
begin
if not (s = '') then
PageCall('', 'addToInput', [s]);
end;
function TChatBox.FileUpload(Compress: Boolean; fn: String = ''): String;
var
url: String;
str: TStringList;
fs: TStream;
begin
Result := '';
if not Assigned(CurrentContact) then
Exit;
if (ServerToUpload = 0) and not OnlFeature(Account.AccProto) then
Exit;
if fn = '' then
fn := openSaveDlg(nil, 'Select file to transfer', True, '', '', '', Compress);
if fn = '' then
Exit;
str := TStringList.Create;
str.StrictDelimiter := True;
str.Delimiter := ';';
str.DelimitedText := fn;
if (str.Count = 1) and not FileExists(str.Strings[0]) then
begin
Application.BringToFront;
MsgDlg('File doesn''t exist', True, mtError);
Exit;
end;
if Compress then
begin
fs := CreateZip(str);
fn := 'files.zip';
end
else
begin
fs := TFileStream.Create(fn, fmOpenRead);
fn := ExtractFileName(fn);
end;
str.Free;
SetupFileBtn(False);
try
if ServerToUpload = 0 then
url := UploadFileICQ(fs, fn, UploadCallbacks)
else if ServerToUpload = 1 then
url := UploadFileMikanoshi(fs, fn, UploadCallbacks)
else
url := UploadFileRnQ(fs, fn, UploadCallbacks);
finally
SetupFileBtn(True);
FreeAndNil(fs);
end;
Result := Trim(url);
end;
procedure TChatBox.ShowServerHistoryNotif(const UID: TUID);
begin
PageFire(UID, 'showhistnotif', Null);
end;
procedure TChatBox.ShowSearchHere;
begin
Fire('showsearchhere', Null);
end;
procedure TChatBox.FinishImage(const link: String);
begin
Call('finishImagesByLink', [link]);
end;
constructor TChatBox.Create(MsgPreview: Boolean = False);
begin
Preview := MsgPreview;
if not Preview then
begin
TaskBar := TTaskBarEx.Create(Application);
TaskBar.ProgressMaxValue := 100;
TaskBar.OnThumbButtonClick := TaskBarThumbButtonClick;
end else
TaskBar := nil;
LastContact := nil;
chats := Tchats.Create;
plugBtns := TPlugButtons.Create;
histories := TObjectDictionary.Create([doOwnsValues]);
UploadCallbacks.OnSendDataCallback := procedure(const Sender: TObject; AContentLength: Int64; AWriteCount: Int64; var AAbort: Boolean)
begin
TThread.Queue(nil, procedure
begin
UploadSize := AContentLength;
UploadedSize := AWriteCount;
if Assigned(UI.Chat) and UI.Chat.Visible then
SetStatusbar('');
end);
end;
end;
destructor TChatBox.Destroy;
begin
histories.Clear;
FreeAndNil(histories);
FreeAndNil(plugBtns);
FreeAndNil(chats);
TabsIconCache.Clear;
MsgsReactions.Clear;
EmbeddedImgs.Clear;
if not Preview then
begin
FreeAndNil(TaskBar);
ReleaseThreading;
end;
inherited;
end;
function TChatBox.ChatWndProc(var Message: TMessage): Boolean;
begin
Result := False;
// Not working
if Message.Msg = WM_COMMAND then
ODS('WM_COMMAND: ' + inttostr(TWMCommand(Message).NotifyCode) + ', ' + inttostr(TWMCommand(Message).ItemID));
end;
initialization
vKeyPicElm.ThemeToken := -1;
vKeyPicElm.picName := PIC_KEY;
vKeyPicElm.Element := RQteDefault;
vKeyPicElm.pEnabled := True;
CreateMessageRegex;
UploadCallbacks := TCallbacks.Create;
TabsIconCache := TObjectDictionary.Create([doOwnsValues]);
EmbeddedImgs := TDictionary.Create;
Emojis := TDictionary, Integer>.Create;
finalization
FreeMessageRegex;
UploadCallbacks.Free;
TabsIconCache.Free;
EmbeddedImgs.Free;
Emojis.Free;
end.