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

3329 lines
102 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.Threading, System.Types, System.StrUtils, System.DateUtils,
System.NetEncoding, System.Variants, System.JSON, System.RegularExpressionsCore,
Generics.Collections, Vcl.Controls, Vcl.Graphics, Vcl.Forms,
RDGlobal, ICQCommon, ICQContacts, history, events, iniLib, SciterLib, Stickers, GR32, Sciter, SciterApi;
{$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);
ThistoryLink = record
evIdx: integer; // -1 for null links
str: String;
from, to_: integer;
kind: TlinkKind;
id: integer;
ev: Thevent;
end;
TChatItem = record
kind: TitemKind;
stringData: String;
timeData: TDateTime;
end;
// PhistoryItem = ^ThistoryItem;
// ThistoryItem = record
// kind: TitemKind; // PK_NONE for null items
// ev: Thevent;
// evIdx, ofs, l: integer;
// r: Trect;
// link: ThistoryLink;
// end;
TMessageData = record
what, when, prefix, msg, embedded, cls, time, msgid,
statusImg, statusImgExt, eventImg, cryptImg: String;
encrypted, writeHist: Boolean;
end;
TVideoFormat = record
url: String;
quality: String;
format: String;
codecs: String;
title: String;
end;
TInputCallback = procedure(Sender: TObject; selected: String = '') of object;
TChatBox = class(TSciterEx)
private
checkTask: ITask;
selectedText: String;
startSel, endSel: TDateTime;
isWholeEvents: Boolean;
histories: TDictionary;
NativeMethods: TNativeMethods;
protected
public
CurrentContact: TICQContact;
LastContact: TICQContact;
procedure Load;
procedure InitPage(cnt: TICQContact);
procedure OpenPage(cnt: TICQContact; focused: Boolean = False; msgPreview: 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 RedrawTab(c: TICQContact; hash, hashadd: LongWord); overload;
procedure RedrawTab(id: Integer; const caption: String; hash: LongWord); overload;
procedure ClearEvents(const uid: TUID = '');
procedure DeleteEvents(const uid: TUID; st, en: TDateTime);
procedure InitSettings;
procedure UpdateSmiles;
procedure ReloadSmiles;
procedure PreloadPickers;
procedure LoadStickers;
procedure LoadSearchResults;
procedure PageFire(const uid: TUID; cmd: UINT; data: Variant); overload;
procedure PageFire(id: Integer; cmd: UINT; data: Variant); overload;
procedure PageCall(const uid: TUID; const method: WideString; const args: TParams); overload;
procedure PageCall(id: Integer; const method: WideString; const args: TParams); overload;
function GetPage(const uid: TUID): IElement; overload;
function GetPage(id: Integer): IElement; overload;
function GetLastEventTime(const uid: TUID): TDateTime;
procedure AddChatItem(var params: TParams; var MsgData: TMessageData; Evt: Thevent; Animate: Boolean; Preview: Boolean = False);
procedure SendChatItems(const uid: TUID; params: TParams; prepend: Boolean = False);
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 SendMessageAction(Sender: TObject);
procedure ClearAvatar(const uid: TUID);
procedure UpdateAvatar(const uid: TUID);
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;
constructor Create(AOwner: TComponent);
destructor Destroy; override;
procedure MoveToTime(const uid: TUID; time: TDateTime; fast: Boolean = False);
procedure SetFirstUnreadEvent(const uid: TUID; time: TDateTime);
procedure CopySel2Clpb;
function getSelBin(): AnsiString;
function getSelHtml(smiles: boolean): String;
function wholeEventsAreSelected: Boolean;
procedure SetSelection(const uid: TUID; from, to_: TDateTime);
procedure ClearSelection(const uid: TUID);
procedure SelectionAll(const uid: TUID);
procedure AddEvent(const uid: TUID; ev: Thevent);
procedure ScrollEvent(d: Integer);
procedure ScrollLine(d: Integer);
procedure ScrollWheel(d: Integer);
procedure ScrollToTop(animate: Boolean);
procedure Quote(const QS: String = ''; LimitWidth: Boolean = False);
procedure UpdateMsgStatus(hev: Thevent);
procedure UpdateStatusBar;
procedure UpdateRelTimes;
procedure SetStatusBarHint(const hint: String);
procedure ScriptingCall(ASender: TObject; const Args: TElementOnScriptingCallArgs);
function ParseMessageBody(const body: String): String;
procedure ReplaceSmileMatch(Sender: TObject; var ReplaceWith: PCREString);
procedure ReplaceOtherMatch(Sender: TObject; var ReplaceWith: PCREString);
function GetReplacement(args: TStringDynArray): PCREString;
function ReplaceEmoji(const msg: String): String;
end;
var
hisBGColor, myBGColor: TColor;
renderInit: Boolean = False;
implementation
uses
Math, Clipbrd, RnQSysUtils, RnQLangs, RDFileUtil, RDUtils, RnQBinUtils, RnQGraphics32,
RQUtil, RQThemes, RnQGlobal, RnQCrypt, RnQPics, RnQNet, RnQDialogs, RnQ_Avatars,
mainDlg, chatDlg, selectcontactsDlg, outboxDlg, Protocols_all,
globalLib, pluginLib, outboxLib, langLib, groupsLib, utilLib, roasterLib, SQLiteDB,
ICQConsts, ICQSession, Base64, Murmur2, EmojiConst, SpellCheck;
const
emojiExtNumbers: array [0..7] of Integer = (984, 1110, 386, 507, 501, 822, 694, 227);
emojiExtHints: array [0..7] of String = ('People', 'Nature', 'Foods', 'Activity', 'Travel', 'Objects', 'Symbols', 'Flags');
var
vKeyPicElm: TRnQThemedElementDtls;
msgRegex, youtubeRegex, vimeoRegex: TPerlRegEx;
IsLastParsedEventMine: Boolean = False;
lastmsg: String = '';
emojiSize: Integer = 22;
emojisInARow: Integer = 36;
emojiContents: TDictionary>;
destructor TChatBox.Destroy;
begin
if Assigned(checkTask) then
checkTask.Cancel;
FreeAndNil(histories);
inherited;
end;
procedure TChatBox.CopySel2Clpb;
begin
PageCall('', 'copySelected', [])
end;
function TChatBox.getSelBin(): AnsiString;
begin
result := '';
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; // applyHtmlFont
function str2html2(const s: string): string;
begin
result := template(s, ['&', '&', '<', '<', '>', '>', CRLF, '
', #13, '
', #10, '
']);
end; // str2html
function color2html(color: TColor): AnsiString;
begin
// if not ColorToIdent(Color, Result) then
begin
color := ABCD_ADCB(ColorToRGB(color));
result := '#' + IntToHex(color, 6);
end;
end; // color2html
function TChatBox.getSelHtml(smiles: boolean): String;
const
HTMLTemplate = '' + CRLF + CRLF +
'' + CRLF +
'' + CRLF +
' %TITLE%' + CRLF +
' ' + CRLF +
' ' + CRLF +
'' + CRLF +
'' + CRLF +
'%CONTENT% ' + CRLF +
'' + CRLF +
'';
var
SOS, EOS: TDateTime;
ev: Thevent;
Content: String;
HTMLElement: String;
Host, Guest: String;
HostUIN, GuestUIN: TUID;
EvHost, EvGuest: Thevent;
function makeElement(const uin: TUID; font: TFont; 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;';
if fsItalic in font.Style then
result := result + ' text-decoration: italic;';
if fsUnderline in font.Style then
result := result + ' text-decoration: underline;';
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 + ' }';
// +CRLF;
end;
var
fnt: TFont;
tmp: String;
events: Thevents;
history: Thistory;
begin
if CurrentContact = nil then
Exit;
result := '';
fnt := TFont.Create;
fnt.Assign(Self.canvas.Font);
if (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;
Host := '';
Guest := '';
Content := '';
history := GetHistory(CurrentContact.UID);
if Assigned(history) then
events := history.getTimeRange(SOS, EOS);
for ev in events do
begin
if (Host = '') or (Guest = '') then
begin
if ev.outgoing then
begin
EvHost := ev;
Host := ev.who.displayed;
HostUIN := ev.who.UID;
end
else
begin
EvGuest := ev;
Guest := ev.who.displayed;
GuestUIN := ev.who.UID;
end;
end;
tmp := CRLF + '
';
if not (ev.kind = EK_msg) then
tmp := tmp + '[' + getTranslation(event2ShowStr[ev.kind]) + '] ';
tmp := tmp + datetimeToStr(ev.when) + ', ' +
ev.who.displayed + '' + '
' +
str2html2(ev.getBodyText) + '';
Content := Content + tmp;
end;
// %TITLE%
HTMLElement := getTranslation('History between [%s] and [%s]', [Host, Guest]);
Result := StringReplace(HTMLTemplate, '%TITLE%', HTMLElement, []);
// %BODY%
HTMLElement := ' body {' + CRLF +
' background-color: ' + color2html(theme.GetColor(ClrHistBG, clWindow)) + ';' + CRLF +
' }' + CRLF +
' div {' + CRLF +
' margin-top: 5px' + CRLF +
' }' + CRLF;
Result := StringReplace(Result, '%BODY%', HTMLElement, []);
// %HOST%
if Host > '' then
begin
fnt.Assign(Screen.MenuFont);
EvHost.applyFont(fnt);
HTMLElement := makeElement(HostUIN, fnt, True);
end else
HTMLElement := '';
Result := StringReplace(Result, '%HOST%', HTMLElement, []);
// %GUEST%
if Guest > '' then
begin
fnt.Assign(Screen.MenuFont);
EvGuest.applyFont(fnt);
HTMLElement := makeElement(GuestUIN, fnt, False)
end else
HTMLElement := '';
Result := StringReplace(Result, '%GUEST%', HTMLElement, []);
Result := StringReplace(Result, '%CONTENT%', Content, []);
Host := '';
Guest := '';
Content := '';
HTMLElement := '';
fnt.Free;
for ev in events do
ev.Free;
end; // getSelHtml
(*
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;
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.Quote(const QS: String = ''; LimitWidth: Boolean = False);
begin
PageCall('', 'quote', [QS, LimitWidth]);
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 := IntToStr(hev.ID);
EvPic := MakeSprite(hev.pic);
Status.eventimg := RecordToVar(EvPic);
PageFire(hev.chat.UID, $105, RecordToVar(Status));
end;
procedure TChatBox.UpdateStatusBar;
var
Trlt: Boolean;
EncPic1, EncPic2: TPicName;
EncHint: String;
begin
Trlt := Assigned(TranslitList) and (TranslitList.Count > 0) and Assigned(CurrentContact) and CurrentContact.SendTransl;
EncPic1 := '';
EncPic2 := '';
EncHint := GetTranslation('Encryption status for current contact');
if Assigned(CurrentContact) then
if Account.AccProto.UseCryptMsg and (
(TICQContact(CurrentContact).crypt.SupportCryptMsg) or
(TICQContact(CurrentContact).crypt.SupportEcc and Account.AccProto.UseEccCryptMsg)) then
begin
if TICQContact(CurrentContact).crypt.SupportEcc then
begin
EncPic1 := PIC_KEY;
EncPic2 := PIC_CLI_RNQ;
EncHint := EncHint + ' [ECDH & AES 256-bit]';
end else if TICQContact(CurrentContact).crypt.SupportCryptMsg then
begin
EncPic1 := PIC_KEY;
EncPic2 := PIC_CLI_RNQ;
EncHint := EncHint + ' [AES 256-bit]';
end else if CAPS_big_QIP_Secure in TICQContact(CurrentContact).CapabilitiesBig then
begin
if TICQContact(CurrentContact).crypt.qippwd > 0 then
EncPic1 := PIC_KEY;
EncPic2 := PIC_CLI_QIP;
EncHint := EncHint + ' [QIP]';
end;
end;
Call('updateStatusBar', [Account.outbox.stFor(CurrentContact), Trlt, EncPic1, EncPic2, EncHint]);
end;
procedure TChatBox.UpdateRelTimes;
begin
Call('updateRelTimes', []);
end;
procedure TChatBox.SetStatusBarHint(const hint: String);
begin
Call('setStatusBarHint', [hint]);
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 LoadHistory(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
uid: PWideChar;
strLen: Cardinal;
offset, msgs, evId, evCnt: Integer;
// topTime: Double;
params: TParams;
noMoreMessages: Boolean;
events: Thevents;
history: Thistory;
MsgDatas: array of TMessageData;
begin
if (tag = nil) or (argc = 0) then
Exit;
API.ValueStringData(argv, uid, strLen);
msgs := 1;
if argc = 3 then
begin
Inc(argv);
API.ValueIntData(argv, offset);
Inc(argv);
API.ValueIntData(argv, msgs);
end;
with TChatBox(tag) do
begin
history := GetHistory(TUID(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;
SetLength(events, 0);
if Length(params) > 0 then
SendChatItems(uid, params, True);
SetLength(MsgDatas, 0);
if noMoreMessages then
HideHistory(uid);
end;
end;
procedure UpdateSelection(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
text, sOfs, eOfs: PWideChar;
sOfsT, eOfsT: TDateTime;
strLen: UINT;
isWhole: Bool;
ffs: TFormatSettings;
tmpInt: Integer;
begin
if (tag = nil) or (argc < 4) then
Exit;
text := '';
API.ValueStringData(argv, text, strLen);
TChatBox(tag).selectedText := text;
Inc(argv);
API.ValueStringData(argv, sOfs, strLen);
Inc(argv);
API.ValueStringData(argv, eOfs, strLen);
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 TChatBox(tag) 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;
procedure UploadLastSnapshot(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
if tag = nil then
Exit;
with TChatBox(tag) do
begin
AddToCurrentInput(FileUpload(False, CacheDir + snapshotFilename));
DeleteFile(CacheDir + snapshotFilename);
end;
end;
procedure DeleteSnapshot(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
DeleteFile(CacheDir + snapshotFilename);
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=?(?.*?)\](?.+?)\[\/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;
function TChatBox.GetReplacement(args: TStringDynArray): PCREString;
procedure AppendImage(var msgResult: PCREString; const url: String; imgWidth, imgHeight: Integer; cached: Boolean);
var
dataLink, display, action: PCREString;
ratio: Real;
begin
dataLink := 'data-link="' + THTMLEncoding.HTML.Encode(url) + '" ';
action := 'check';
display := ' hidden';
if cached then
begin
action := 'download';
display := '';
end;
ratio := imgHeight / imgWidth;
if LimitMaxChatImgWidth and (MaxChatImgWidthVal > 0) then
begin
if (imgWidth > MaxChatImgWidthVal) then
begin
imgWidth := MaxChatImgWidthVal;
imgHeight := Round(MaxChatImgWidthVal * ratio);
end;
end else if (imgWidth > Self.Width) then
begin
imgWidth := Self.Width;
imgHeight := Round(Self.Width * ratio);
end;
if LimitMaxChatImgHeight and (MaxChatImgHeightVal > 0) and (imgHeight > MaxChatImgHeightVal) then
begin
imgWidth := Round(MaxChatImgHeightVal / ratio);
imgHeight := MaxChatImgHeightVal;
end;
Result := Result + '
'style="width: ' + IntToStr(imgWidth) + 'px; height: ' + IntToStr(imgHeight) + 'px;" class="linkedImg" alt="" />';
end;
var
match, mail, url, uin, srcLang, srcCode, bold, underlined, comment,
videoHref, srcCodeHTML: String;
srcCodeArr: TStringList;
IsVideoLink: Boolean;
i: Integer;
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 := '' + 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;
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(Result, url, imgCacheInfo.ReadInteger(url, 'width', 50), imgCacheInfo.ReadInteger(url, 'height', 50), True)
else
AppendImage(Result, url, 50, 50, False);
end else if not (uin = '') then
Result := '' + uin + ''
else if not (srcCode = '') then
begin
if not (Root = nil) and (Root.Select('body').StyleAttr['-syntax-highlight'] = 'on') then
begin
srcCodeArr := TStringList.Create;
if TrimMsgNewLines then
srcCodeArr.Text := srcCode.Trim([#13, #10])
else
srcCodeArr.Text := srcCode;
srcCodeHTML := '';
for 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: TStringDynArray;
i: Integer;
begin
SetLength(args, 9);
for 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(const msg: String): String;
var
i: Integer;
procedure GetReplacedEmoji(cp1, cp2: Cardinal; const emoji: String);
var
pos: Integer;
begin
if theme.HasOrigPic('emoji.sprite') and emojis.TryGetValue(TPair.Create(cp1, cp2), pos) then
Result := Result + '
IntToStr(emojiSize) + 'px; background-position: ' + IntToStr(-(pos mod emojisInARow) * emojiSize) + ' ' +
IntToStr(-floor(pos / emojisInARow) * emojiSize) + ';">' + emoji + '' + emoji + ''
else
Result := Result + emoji;
inc(i, Length(emoji));
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 := '';
while i <= Length(msg) do
if IsSurrogate(msg, i) and IsSurrogatePair(msg, i) then
begin
if (i+2 < Length(msg)) and IsSurrogate(msg, i+2) and IsSurrogatePair(msg, i+2)
and (IsHighSurrogate(msg, i+2)) and (IsLowSurrogate(msg, i+3))
and ((InRange(ord(ConvertToUtf32(msg, i)), $1f100, $1f1ff) and InRange(ord(ConvertToUtf32(msg, i+2)), $1f100, $1f1ff))
or (ord(ConvertToUtf32(msg, i+2)) = $1f5e8))
then
GetReplacedEmoji(ConvertToUtf32(msg, i), ConvertToUtf32(msg, i+2), Copy(msg, i, 4))
else if (IsHighSurrogate(msg, i)) and (IsLowSurrogate(msg, i+1)) then
GetReplacedEmoji(ConvertToUtf32(msg, i), 0, Copy(msg, i, 2))
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))
else if IsSingle(ord(msg[i])) then
GetReplacedEmoji(ord(msg[i]), 0, msg[i])
else
begin
Result := Result + msg[i];
inc(i);
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(body, '\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;
function DecodeFormat(format: String): String;
begin
Result := '';
format := DecodeURL(format);
if format.Contains('video/mp4') then
Result := 'MP4'
else if format.Contains('video/webm') then
Result := 'WEBM'
else if format.Contains('video/x-flv') then
Result := 'FLV'
else if format.Contains('video/3gpp') then
Result := '3GPP'
end;
procedure GetYoutubeLinks(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
ytlink: PWideChar;
ytpage, yturl, yttitle, anchor: RawByteString;
ytmap, ytitem: TStringList;
ytfmts: TDictionary;
ytfmt: TVideoFormat;
strLen: UINT;
fs: TMemoryStream;
i, j, p, arrsize: Integer;
ignore3GPP: Boolean;
tmp: String;
tmpVal: TSciterValue;
begin
arrsize := 0;
ytlink := '';
if argc > 0 then
API.ValueStringData(argv, ytlink, strLen);
if ytlink = '' then
Exit;
fs := TMemoryStream.Create;
LoadFromUrl(ytlink, fs);
SetLength(ytpage, fs.Size);
fs.ReadBuffer(ytpage[1], fs.Size);
fs.Free;
anchor := 'url_encoded_fmt_stream_map":"';
i := pos(anchor, ytpage);
if i = 0 then
Exit;
yturl := copy(ytpage, i + length(anchor));
yturl := copy(yturl, 1, pos('"', yturl) - 1);
anchor := 'property="og:title" content="';
yttitle := copy(ytpage, pos(anchor, ytpage) + length(anchor));
yttitle := copy(yttitle, 1, pos('"', yttitle) - 1);
yttitle := DecodeURL(UnUTF(yttitle));
ytmap := TStringList.Create;
ytmap.Delimiter := ',';
ytmap.DelimitedText := yturl;
ytmap.StrictDelimiter := true;
ytmap.Sorted := False;
ytitem := TStringList.Create;
ytitem.Delimiter := '|';
ytitem.StrictDelimiter := true;
ytitem.Sorted := False;
ytfmts := TDictionary.Create();
for i := 0 to ytmap.Count - 1 do
begin
ytitem.Clear;
ytitem.DelimitedText := ytmap.Strings[i].Replace('\u0026', '|');
ytfmt := Default(TVideoFormat);
for j := 0 to ytitem.Count - 1 do
begin
if ytitem.Strings[j].StartsWith('url=') then
ytfmt.url := copy(ytitem.Strings[j], 5)
else if ytitem.Strings[j].StartsWith('quality=') then
ytfmt.quality := copy(ytitem.Strings[j], 9)
else if ytitem.Strings[j].StartsWith('type=') then
begin
tmp := copy(ytitem.Strings[j], 6);
p := pos('%3B+', tmp);
if p > 0 then
begin
ytfmt.format := copy(tmp, 1, p - 1);
ytfmt.codecs := copy(tmp, p + 4).Replace('codecs%3D', '').Replace('%22', '');
end else
ytfmt.format := tmp;
end
end;
ytfmts.Add(i, ytfmt);
end;
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.Contains('hd')))
or ((PreferredResolution = 2) and ytfmt.quality.Contains('medium'))
or ((PreferredResolution = 3) and ytfmt.quality.Contains('small')) then
begin
if ytfmt.format.Contains('video%2F3gpp') and ignore3GPP then
Continue;
//OutputDebugString(PChar('Preferred: ' + ytfmt.quality + ', ' + ytfmt.format));
ytlink := PWideChar('{"format":"' + DecodeFormat(ytfmt.format) + '","codecs":"' + DecodeURL(ytfmt.codecs) + '","title":"' + yttitle + '","url":"' + DecodeURL(ytfmt.url) + '"}');
API.ValueInit(@tmpVal);
API.ValueStringDataSet(@tmpVal, ytlink, Length(ytlink), 0);
API.ValueNthElementValueSet(retval, arrsize, @tmpVal);
API.ValueClear(@tmpVal);
Inc(arrsize);
if ytfmt.format.Contains('video%2F3gpp') then
ignore3GPP := True;
end;
end;
if arrsize = 0 then
begin
ytfmts.TryGetValue(0, ytfmt);
//OutputDebugString(PChar('No preferred: ' + ytfmt.quality + ', ' + ytfmt.format));
ytlink := PWideChar('{"format":"' + DecodeFormat(ytfmt.format) + '","codecs":"' + DecodeURL(ytfmt.codecs) + '","title":"' + yttitle + '","url":"' + DecodeURL(ytfmt.url) + '"}');
API.ValueInit(@tmpVal);
API.ValueStringDataSet(@tmpVal, ytlink, Length(ytlink), 0);
API.ValueNthElementValueSet(retval, 0, @tmpVal);
API.ValueClear(@tmpVal);
end;
end;
procedure GetVimeoLinks(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
vmlink: PWideChar;
vmpage, vmurl, vmtitle, anchor: RawByteString;
vmmap: TJSONArray;
vmfmts: TDictionary;
vmfmt: TVideoFormat;
strLen: UINT;
fs: TMemoryStream;
i, arrsize: Integer;
JSONObject: TJSONObject;
tmpVal: TSciterValue;
begin
arrsize := 0;
vmlink := '';
if argc > 0 then
API.ValueStringData(argv, vmlink, strLen);
if vmlink = '' then
Exit;
fs := TMemoryStream.Create;
LoadFromUrl(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;
LoadFromUrl(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 := PWideChar('{"format":"' + DecodeFormat(vmfmt.format) + '","codecs":"","title":"' + vmtitle + '","url":"' + vmfmt.url + '"}');
API.ValueInit(@tmpVal);
API.ValueStringDataSet(@tmpVal, 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 := PWideChar('{"format":"' + DecodeFormat(vmfmt.format) + '","codecs":"","title":"' + vmtitle + '","url":"' + vmfmt.url + '"}');
API.ValueInit(@tmpVal);
API.ValueStringDataSet(@tmpVal, vmlink, Length(vmlink), 0);
API.ValueNthElementValueSet(retval, 0, @tmpVal);
API.ValueClear(@tmpVal);
end;
end;
procedure GetVolumeLevel(tag: Pointer; 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;
procedure SaveVolumeLevel(tag: Pointer; 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;
procedure ChatPageSelected(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
uid, txt: PWideChar;
strLen: Cardinal;
ch: TchatInfo;
begin
if (tag = nil) or (argc < 2) then
Exit;
uid := '';
API.ValueStringData(argv, uid, strLen);
Inc(argv);
API.ValueStringData(argv, txt, strLen);
with TChatBox(tag), chatFrm do
if not (uid = '') and Assigned(chats) and not (chats.count = 0) then
begin
ch := chats.byUIN(uid);
CurrentContact := ch.who;
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.UID2cmp);
end else
begin
CurrentContact := nil;
OutputDebugString(PChar('CurrentContact is nil'));
end;
end;
procedure ChatPageDeselected(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
uid: PWideChar;
strLen: Cardinal;
ch: TchatInfo;
begin
if tag = nil then
Exit;
uid := '';
API.ValueStringData(argv, uid, strLen);
with TChatBox(tag), chatFrm 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.UID2cmp);
end;
end;
end;
end;
procedure PluginPageSelected(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
id: Integer;
ch: TchatInfo;
begin
if tag = nil then
Exit;
API.ValueIntData(argv, id);
with TChatBox(tag), chatFrm 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);
plugins.castEv(PE_SELECTTAB, ch.ID);
end else
CurrentContact := nil;
end;
procedure PluginPageDeselected(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
id: Integer;
ch: TchatInfo;
begin
if tag = nil then
Exit;
API.ValueIntData(argv, id);
with TChatBox(tag), chatFrm 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;
procedure FormClose(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
if Assigned(chatFrm) then
chatFrm.Close;
end;
procedure SetTabDragging(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
tmpInt: Integer;
begin
if (argc = 0) or not Assigned(chatFrm) then
Exit;
API.ValueIntData(argv, tmpInt);
chatFrm.DraggingTab := tmpInt = 1;
end;
procedure CloseChatPage(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
uid: PWideChar;
strLen: Cardinal;
begin
if (argc = 0) or not Assigned(chatFrm) then
Exit;
API.ValueStringData(argv, uid, strLen);
chatFrm.closeChatUID(uid);
end;
procedure ClosePluginPage(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
id: Integer;
begin
if (argc = 0) or not Assigned(chatFrm) then
Exit;
API.ValueIntData(argv, id);
chatFrm.closeChatID(id);
end;
procedure AddUIN2CL(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
uid, mtag: PWideChar;
_mtag: Integer;
strLen: Cardinal;
cnt: TICQContact;
begin
if (tag = nil) or (argc < 2) then
Exit;
API.ValueStringData(argv, uid, strLen);
Inc(argv);
API.ValueStringData(argv, mtag, strLen);
cnt := TChatBox(tag).CurrentContact;
if Assigned(cnt) then
cnt := Account.AccProto.GetContact(uid);
if Assigned(cnt) and TryStrToInt(mtag, _mtag) then
utilLib.addToRoster(cnt, _mtag, cnt.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;
procedure CopyLink(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
str: PWideChar;
strLen: Cardinal;
begin
if argc = 0 then
Exit;
str := '';
API.ValueStringData(argv, str, strLen);
clipboard.asText := stripProtocol(str);
end;
procedure SavePicture(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
str: PWideChar;
strLen: Cardinal;
fs: TFileStream;
img: TBytes;
pic: TMemoryStream;
hash: LongWord;
realurl, fn, fmt: String;
begin
if (tag = nil) or (argc = 0) then
Exit;
str := '';
API.ValueStringData(argv, str, strLen);
if Assigned(TChatBox(tag).CurrentContact) then
with TChatBox(tag).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(chatFrm, '', 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(chatFrm, '', false, fmt);
if fmt > '' then
pic.SaveToFile(fmt);
pic.free;
end;
end;
end;
procedure GetEvent(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
uid, clickedTime: PWideChar;
strLen: Cardinal;
time: TDateTime;
ev: Thevent;
hdr: THeader;
MessageHeader: TMessageHeader;
// imgList: TImgBytes;
// imgcnt: Integer;
// imgs,
ffs: TFormatSettings;
history: Thistory;
begin
if (tag = nil) or (argc = 0) then
Exit;
API.ValueStringData(argv, uid, strLen);
Inc(argv);
API.ValueStringData(argv, clickedTime, strLen);
ffs := TFormatSettings.Create(LOCALE_USER_DEFAULT);
ffs.DecimalSeparator := '.';
time := StrToFloat(clickedTime, ffs);
if uid = '' then
Exit;
history := TChatBox(tag).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 := hdr.prefix + hdr.date + ', ' + hdr.what;
MessageHeader.text := ev.getBodyText;
MessageHeader.when := FloatToStr(ev.when, ffs);
MessageHeader.img := ev.pic;
V2S(TChatBox(tag).RecordToVar(MessageHeader), retval);
ev.Free;
end;
procedure SaveAs(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
format: Integer;
fn, caption, ext: String;
begin
if (tag = nil) or (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 PNG file';
ext := 'png';
end;
fn := openSavedlg(chatFrm, GetTranslation(caption), False, ext);
if not (fn = '') then
if format = 0 then
saveTextFile(fn, TChatBox(tag).selectedText)
else if format = 1 then
saveTextFile(fn, TChatBox(tag).getSelHtml(False))
else if format = 2 then
if not MoveFileEx(PWideChar(CacheDir + snapshotFilename), 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 + snapshotFilename);
end;
end;
procedure AddLinkToFav(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
str: PWideChar;
strLen: Cardinal;
begin
if argc = 0 then
Exit;
str := '';
API.ValueStringData(argv, str, strLen);
addLinkToFavorites(stripProtocol(str));
end;
procedure DeleteMessages(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
uid: PWideChar;
strLen: Cardinal;
history: Thistory;
begin
if (tag = nil) or (argc = 0) then
Exit;
if MessageDlg(GetTranslation('All messages between first and last selected will be removed permanently! Are you sure want to proceed?'), mtConfirmation, [mbYes, mbNo]) = mrNo then
Exit;
API.ValueStringData(argv, uid, strLen);
with TChatBox(tag) do
begin
if not wholeEventsAreSelected 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;
end;
procedure AddToAntispam(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
if tag = nil then
Exit;
if not (spamfilter.badwords = '') and not (spamfilter.badwords[Length(spamfilter.badwords)] = ';') then
spamfilter.badwords := spamfilter.badwords + ';';
spamfilter.badwords := spamfilter.badwords + TChatBox(tag).selectedText;
end;
procedure ViewInfo(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
time: TDateTime;
uid, clickedTime: PWideChar;
strLen: Cardinal;
ffs: TFormatSettings;
history: Thistory;
ev: Thevent;
begin
if tag = nil then
Exit;
clickedTime := '0';
API.ValueStringData(argv, uid, strLen);
Inc(argv);
API.ValueStringData(argv, clickedTime, strLen);
ffs := TFormatSettings.Create(LOCALE_USER_DEFAULT);
ffs.DecimalSeparator := '.';
time := StrToFloat(clickedTime, ffs);
try
history := TChatBox(tag).GetHistory(uid);
if Assigned(history) then
begin
ev := history.getByTime(time);
ev.who.ViewInfo;
ev.Free;
end;
except end;
end;
procedure ToggleSmiles(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
if tag = nil then
Exit;
useSmiles := not useSmiles;
TChatBox(tag).InitSettings;
TChatBox(tag).UpdateSmiles;
end;
procedure ToggleRelTimes(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
RelativeTimeInChat := not RelativeTimeInChat;
end;
procedure RealizeEvents(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
uid: PWideChar;
strLen: Cardinal;
ev: Thevent;
res: Boolean;
begin
res := False;
if argc > 0 then
begin
API.ValueStringData(argv, uid, strLen);
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;
procedure StoreSplit(tag: Pointer; 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 t = 0 then
SplitX := val
else
SplitY := val;
end;
procedure InputChangedFor(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
uid, txt: PWideChar;
strLen: Cardinal;
txtLen: Integer;
c: TICQContact;
begin
if argc < 2 then
Exit;
API.ValueStringData(argv, uid, strLen);
Inc(argv);
API.ValueStringData(argv, txt, strLen);
txtLen := Length(txt);
if EnableSpellCheck and SpellTextChanged(txt) then
begin
SetSpellText(txt);
DoSpellCheck;
end;
// Send typing notify
c := Account.AccProto.GetContact(uid);
Account.AccProto.InputChangedFor(c, txtLen = 0);
end;
procedure GetMessageByIdx(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
uid: PWideChar;
strLen: Cardinal;
he: Thevent;
history: Thistory;
pQuoteIdx: Integer;
res: TParams;
quote: String;
begin
if (tag = nil) or (argc < 2) then
Exit;
API.ValueStringData(argv, uid, strLen);
Inc(argv);
API.ValueIntData(argv, pQuoteIdx);
quote := '';
history := TChatBox(tag).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;
procedure WrapText(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
str: PWideChar;
wrappedStr: String;
strLen: Cardinal;
limit: Integer;
begin
if argc = 0 then
Exit;
API.ValueStringData(argv, str, strLen);
if argc > 1 then
begin
Inc(argv);
API.ValueIntData(argv, limit);
end else
limit := 50;
wrappedStr := System.SysUtils.WrapText(String(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(chatFrm) then
with chatFrm do
begin
SawAllHere;
OutboxAdd(OE_msg, ch.who, flags_, msg);
if ch.single then
begin
if ClosePageOnSingle then
CloseChat(ch)
else
Close;
end;
end;
end;
procedure SendChatMessage(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
uid, msg: PWideChar;
strLen: Cardinal;
opt, flag: Integer;
ch: TchatInfo;
lShouldEncr, isBin: Boolean;
s, s1: String;
Max, i: Integer;
wnd: TSelectCntsFrm;
begin
if (tag = nil) or (argc < 3) then
Exit;
API.ValueIntData(argv, opt);
Inc(argv);
API.ValueStringData(argv, uid, strLen);
msg := '';
Inc(argv);
API.ValueStringData(argv, msg, strLen);
flag := 0;
ch := chatFrm.chats.byUIN(uid);
if (ch = nil) or (ch.chatType = CT_PLUGING) or (ch.who = nil) then
Exit;
case opt of
0:
begin
isBin := (Pos(RnQImageTag, msg) > 0) or ((Pos(RnQImageExTag, msg) > 0)) or (IF_Bin and flag > 0);
lShouldEncr := (Account.AccProto.UseCryptMsg and (TICQContact(ch.who).Crypt.supportCryptMsg or
(Account.AccProto.fECCKeys.generated and Account.AccProto.UseEccCryptMsg and TICQContact(ch.who).crypt.supportEcc)))
and not isBin;
if lShouldEncr and ch.who.isOffline then
begin
if MessageDlg(GetTranslation('Encrypted messages cannot be delivered to offline contacts. Send without encryption?'), mtConfirmation, [mbYes, mbNo]) = mrYes then
begin
TICQContact(ch.who).Crypt.supportCryptMsg := False;
TICQContact(ch.who).Crypt.supportEcc := False;
end else
Exit;
end;
Max := Account.AccProto.MaxCharsFor(ch.who);
if Length(msg) > Max then
begin
if MessageDlg(getTranslation('Your message is too long. Max %d characters.\n\n Split 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);
Exit;
end
end
else
begin
if Trim(msg) = '' then
begin
if closeChatOnSend then
chatFrm.Close
end else
Send(ch, flag, msg);
end;
end;
1:
begin
if Trim(msg) = '' then
begin
msgDlg('Can''t send an empty message', True, mtWarning);
Exit;
end;
lastmsg := msg;
wnd := TselectCntsFrm.doAll(mainDlg.RnQmain, 'Send multiple', 'Send message', Account.AccProto,
Account.AccProto.ReadList(LT_ROSTER).clone.Add(notInList), TChatBox(tag).SendMessageAction, [sco_multi, sco_groups, sco_predefined], @wnd);
wnd.toggle(ch.who);
end;
2:
Send(ch, IF_sendWhenImVisible, msg);
3:
begin
if Trim(msg) = '' then
begin
msgDlg('Can''t send an empty message', True, mtWarning);
Exit;
end;
with chatFrm 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);
end;
end;
end;
procedure ClosePages(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
opt, i: Integer;
box: TChatBox;
begin
if (tag = nil) or (argc = 0) then
Exit;
opt := -1;
API.ValueIntData(argv, opt);
box := TChatBox(tag);
with chatFrm 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(box.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(box.CurrentContact);
if MessageDlg(GetTranslation('Do you want to remove %s from your contact list?', [box.CurrentContact.displayed]), mtConfirmation,
[mbYes, mbNo]) = mrYes then
removeFromRoster(box.CurrentContact);
CloseThisPage;
end;
5:
begin
if MessageDlg(GetTranslation('Are you sure 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;
procedure UploadFile(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
opt: Integer;
url: String;
begin
if (tag = nil) or (argc = 0) then
Exit;
API.ValueIntData(argv, opt);
url := TChatBox(tag).FileUpload(opt = 2);
V2S(url, retval);
end;
procedure SaveEmbeddedFile(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
pic: TMemoryStream;
strUrl: PWideChar;
strLen: Cardinal;
hash: LongWord;
img: TBytes;
fmt: String;
begin
if argc = 0 then
Exit;
strUrl := '';
API.ValueStringData(argv, strUrl, strLen);
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;
procedure ChatButtonClick(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
id: PWideChar;
strLen: Cardinal;
right: Boolean;
r, x, y, PicMaxSize: Integer;
page, fn, sU: String;
s: RawByteString;
fs: TFileStream;
isRnQPic: Boolean;
ev: THevent;
begin
if (tag = nil) or (argc = 0) then
Exit;
API.ValueStringData(argv, id, strLen);
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 TChatBox(tag) do
if id = 'searchBtn' then
begin
if right then
showForm(WF_SEARCH, '', vmFull, CurrentContact)
else
showForm(WF_SEARCH, '', vmShort, CurrentContact);
end else if id = 'prefBtn' then
begin
if right then
Page := 'Plugins'
else
Page := 'Chat';
showForm(WF_SHEET, Page, vmShort)
end else if id = 'infoBtn' then
CurrentContact.ViewInfo
else if id = 'quoteBtn' then
begin
if right then
Quote(clipboard.asText, x = 1)
else
Quote
end else if id = 'singleBtn' then
chatFrm.chats.byContact(CurrentContact).single := right
else if id = 'contactsBtn' then
openSendContacts(CurrentContact)
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, True) * 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 = 'fileBtn' then
begin
if right then
showForm(WF_SHEET, 'Other', vmShort)
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;
chatFrm.AddEvent(CurrentContact, ev);
end else
msgDlg('Wait at least 15 seconds before buzzing again', True, mtBuzz)
// Test SMS sending
// senderName := Account.AccProto.getMyInfo.fDisplay;
// if Trim(senderName) = '' then
// senderName := Account.AccProto.getMyInfo.nick;
// if Trim(senderName) = '' then
// senderName := Account.AccProto.getMyInfo.first;
//'(' + senderName + ', ICQ) ' +
// TICQSession(Account.AccProto.ProtoElem).sendSMS2('+79020000000', grabThisText, True);
end;
end;
procedure PluginButtonClick(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
title: PWideChar;
id, mbtn: Integer;
strLen: Cardinal;
pr: procedure(Button: integer);
begin
if argc = 0 then
Exit;
id := 0;
API.ValueIntData(argv, id);
mbtn := 0;
Inc(argv);
API.ValueStringData(argv, title, strLen);
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;
procedure OpenOutbox(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
if tag = nil then
Exit;
if not Assigned(outboxFrm) then
begin
outboxFrm := ToutboxFrm.Create(Application);
translateWindow(outboxFrm);
end;
outboxFrm.open(TChatBox(tag).CurrentContact);
end;
procedure ToggleTranslit(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
if tag = nil then
Exit;
with TChatBox(tag) do
if Assigned(TranslitList) and (TranslitList.Count > 0) and Assigned(CurrentContact) then
begin
CurrentContact.SendTransl := not CurrentContact.SendTransl;
UpdateStatusBar;
end;
end;
procedure QIPPwd(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
opt: Integer;
pwd: String;
begin
if (tag = nil) or (argc = 0) then
Exit;
opt := 0;
API.ValueIntData(argv, opt);
with TChatBox(tag) do
begin
if (CurrentContact = nil) or not (CurrentContact is TICQContact) then
Exit;
if opt = 1 then
begin
pwd := CommonMethods.EnterPassword(GetTranslation('Enter password for %s', [CurrentContact.displayed]), 32);
if not (pwd = '') then
TICQContact(CurrentContact).crypt.qippwd := qip_str2pass(pwd);
end else if opt = 2 then
TICQContact(CurrentContact).crypt.qippwd := 0;
chatFrm.UpdateContactStatus(CurrentContact);
end;
end;
procedure SendStickerToCurrent(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
ext, sticker: PWideChar;
strLen: Cardinal;
ev: Thevent;
id, flag: Integer;
begin
if (tag = nil) or (argc < 2) then
Exit;
API.ValueStringData(argv, ext, strLen);
Inc(argv);
API.ValueStringData(argv, sticker, strLen);
with TChatBox(tag) do
if OnlFeature(Account.AccProto) then
begin
chatFrm.SawAllHere;
OutboxAdd(OE_msg, CurrentContact, IF_sticker, 'ext:' + ext + ':sticker:' + sticker);
end;
end;
procedure IsWindowActive(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
res: Boolean;
begin
res := False;
if Assigned(chatFrm) then
res := chatFrm.Active;
API.ValueIntDataSet(retval, RDUtils.IfThen(res, 1), T_BOOL, 0);
end;
procedure GetStoreStickerPacks(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
begin
Account.AccProto.GetStoreStickerPacks;
end;
procedure GetStickerPacks(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
StickerPacks: TStickerPacks;
StickerPacksVar: TParams;
Args: TParams;
i: Integer;
begin
if tag = nil then
Exit;
StickerPacks := SQLDB.GetStickerPacks;
SetLength(StickerPacksVar, Length(StickerPacks));
for i := 0 to Length(StickerPacks) - 1 do
StickerPacksVar[i] := TSciterEx(tag).RecordToVar(StickerPacks[i]);
V2S(StickerPacksVar, retval);
SetLength(StickerPacks, 0);
end;
procedure SearchStickerPacks(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
Qry: PWideChar;
QryStr: String;
StrLen: Cardinal;
begin
if argc = 0 then
Exit;
Qry := '';
API.ValueStringData(argv, Qry, StrLen);
QryStr := Trim(Qry);
if QryStr = '' then
Exit;
if QryStr.StartsWith('storeid:') then
Account.AccProto.SearchStoreStickerPack(QryStr.Replace('storeid:', ''))
else
Account.AccProto.SearchStoreStickerPacks(QryStr);
end;
procedure BuyStickerPack(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
id: PWideChar;
strLen: Cardinal;
begin
if argc = 0 then
Exit;
id := '';
API.ValueStringData(argv, id, strLen);
Account.AccProto.BuyStickerPack(id);
end;
procedure RemoveStickerPack(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
id: PWideChar;
strLen: Cardinal;
begin
if argc = 0 then
Exit;
id := '';
API.ValueStringData(argv, id, strLen);
Account.AccProto.RemoveStickerPack(id);
end;
procedure GetSpellingSuggestions(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
txt: PWideChar;
strLen: Cardinal;
begin
if not EnableSpellCheck or (argc = 0) then
Exit;
API.ValueStringData(argv, txt, strLen);
V2S(GetSuggestions(txt), retval);
end;
procedure TChatBox.SendMessageAction(Sender: TObject);
var
wnd: TselectCntsFrm;
cl: TRnQCList;
cnt: TICQContact;
begin
wnd := (Sender as TControl).Parent as TselectCntsFrm;
cl := wnd.selectedList;
wnd.extra.Free;
wnd.Close;
for cnt in cl do
OutboxAdd(OE_msg, cnt, IF_multiple, lastmsg);
cl.Free;
end;
procedure TChatBox.Load;
begin
LoadTemplate('template', 'chat.htm');
end;
procedure TChatBox.InitPage(cnt: TICQContact);
var
uidBG, grpBG: TPicName;
IsUseCntThemes: boolean;
args: TParams;
begin
IsUseCntThemes := UseContactThemes and Assigned(ContactsTheme) and Assigned(cnt);
if IsUseCntThemes then
begin
uidBG := TPicName(LowerCase(cnt.UID2cmp)) + '.' + PIC_CHAT_BG;
grpBG := TPicName('group.') + TPicName(AnsiLowerCase(groups.id2name(cnt.group))) + '.' + PIC_CHAT_BG;
end;
SetLength(args, 4);
args[0] := SplitX;
args[1] := SplitY;
// Tiled background image
args[2] := '';
if IsUseCntThemes and (ContactsTheme.GetPicSize(RQteDefault, uidBG + '5').cx > 0) then
args[2] := 'contactpic:' + uidBG + '5'
else if IsUseCntThemes and (ContactsTheme.GetPicSize(RQteDefault, grpBG + '5').cx > 0) then
args[2] := 'contactpic:' + grpBG + '5'
else if theme.GetPicSize(RQteDefault, PIC_CHAT_BG + '5').cx > 0 then
args[2] := 'themepic:' + PIC_CHAT_BG + '5';
// Positioned background image
args[3] := '';
if IsUseCntThemes then
if ContactsTheme.GetPicSize(RQteDefault, uidBG + '1').cx > 0 then
args[3] := 'contactpic:' + uidBG + '1'
else if ContactsTheme.GetPicSize(RQteDefault, uidBG + '2').cx > 0 then
args[3] := 'contactpic:' + uidBG + '2'
else if ContactsTheme.GetPicSize(RQteDefault, uidBG + '3').cx > 0 then
args[3] := 'contactpic:' + uidBG + '3'
else if ContactsTheme.GetPicSize(RQteDefault, uidBG + '4').cx > 0 then
args[3] := 'contactpic:' + uidBG + '4';
if IsUseCntThemes and (args[3] = '') then
if ContactsTheme.GetPicSize(RQteDefault, grpBG + '1').cx > 0 then
args[3] := 'contactpic:' + grpBG + '1'
else if ContactsTheme.GetPicSize(RQteDefault, grpBG + '2').cx > 0 then
args[3] := 'contactpic:' + grpBG + '2'
else if ContactsTheme.GetPicSize(RQteDefault, grpBG + '3').cx > 0 then
args[3] := 'contactpic:' + grpBG + '3'
else if ContactsTheme.GetPicSize(RQteDefault, grpBG + '4').cx > 0 then
args[3] := 'contactpic:' + grpBG + '4';
if args[3] = '' then
if theme.GetPicSize(RQteDefault, PIC_CHAT_BG + '1').cx > 0 then
args[3] := 'themepic:' + PIC_CHAT_BG + '1'
else if theme.GetPicSize(RQteDefault, PIC_CHAT_BG + '2').cx > 0 then
args[3] := 'themepic:' + PIC_CHAT_BG + '2'
else if theme.GetPicSize(RQteDefault, PIC_CHAT_BG + '3').cx > 0 then
args[3] := 'themepic:' + PIC_CHAT_BG + '3'
else if theme.GetPicSize(RQteDefault, PIC_CHAT_BG + '4').cx > 0 then
args[3] := 'themepic:' + PIC_CHAT_BG + '4';
try
PageCall(cnt.UID, 'init', [args]);
except
on e: ESciterCallException do
msgDlg('Error in init: ' + e.Message, false, mtError);
end;
end;
procedure TChatBox.OpenPage(cnt: TICQContact; focused: Boolean = False; msgPreview: Boolean = False);
begin
if not Assigned(cnt) then
Exit;
if not histories.ContainsKey(cnt.UID) then
histories.Add(cnt.UID, Thistory.Create(cnt.UID2cmp));
if msgPreview then
try
Call('initSettingsMsgPreview', [GetTranslation('Select message to render its full version here')]);
except
on e: ESciterCallException do
msgDlg('Error in InitSettingsMsgPreview: ' + e.Message, false, mtError);
end;
Call('openChatPage', [cnt.UID, cnt.displayed, focused]);
InitPage(cnt);
end;
procedure TChatBox.OpenPage(ID: Integer; const Caption: String);
begin
Call('openPluginPage', [ID, Caption]);
end;
procedure TChatBox.ClosePage(uid: TUID = '');
var
h: Thistory;
el: IElement;
begin
el := GetPage(uid);
if Assigned(el) and el.IsValid then
begin
if uid = '' then
uid := el.Call('getChatId', []);
if histories.TryGetValue(uid, h) then
begin
FreeAndNil(h);
histories.Remove(uid);
end;
el.Call('close', []);
end;
end;
procedure TChatBox.ClosePage(id: Integer);
begin
PageCall(id, 'close', []);
end;
procedure TChatBox.SwitchToPage(const uid: TUID);
begin
PageFire(uid, $106, Null);
end;
procedure TChatBox.SwitchToPage(id: Integer);
begin
PageFire(id, $106, 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('', $107, data);
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.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
args: TParams;
begin
SetLength(args, 24);
args[0] := autocopyHist;
args[1] := useSmiles;
args[2] := wheelVelocity;
args[3] := ChatImageQuality;
args[4] := ChatSmoothFontRendering;
if LimitMaxChatImgWidth then
args[5] := MaxChatImgWidthVal
else
args[5] := 0;
if LimitMaxChatImgHeight then
args[6] := MaxChatImgHeightVal
else
args[6] := 0;
args[7] := FontStyleCodes.Enabled;
args[8] := RelativeTimeInChat;
args[9] := ''; // reserved
args[10] := FilePathToURL(CacheDir);
args[11] := PreferredResolution;
args[12] := bViewTextWrap;
args[13] := AnimatedScroll;
args[14] := sendOnEnter;
args[15] := quoting.quoteselected;
args[16] := quoting.cursorBelow;
args[17] := avatarShowInChat;
args[18] := ShowSmileCaption;
args[19] := spellErrorStyle;
args[20] := color2html(spellErrorColor);
// args[20] := VarArrayCreate([0, 2], varVariant);
// args[20][0] := GetRValue(spellErrorColor);
// args[20][1] := GetGValue(spellErrorColor);
// args[20][2] := GetBValue(spellErrorColor);
args[21] := ChatCSS;
args[22] := ChatLoadBuffer;
args[23] := ShowHintsInChat;
try
Call('initSettings', [args]);
except
on e: ESciterCallException do
msgDlg('Error in InitSettings: ' + 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.Width := bounds[2];
Result.Height := bounds[3];
end;
procedure TChatBox.AddPluginButton(i: Integer);
begin
with chatFrm.plugBtns do
Call('addPluginButton', [NativeInt(btns[i].proc)]);
end;
procedure TChatBox.DelPluginButton(i: Integer);
begin
with chatFrm.plugBtns do
Call('delPluginButton', [NativeInt(btns[i].proc)]);
end;
procedure TChatBox.ModifyPluginButton(i: Integer);
begin
with chatFrm.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', [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
i: Integer;
elc: IElementCollection;
begin
elc := SelectAll('#pages > .page');
for i := 0 to elc.Count - 1 do
elc[i].Call('resetHistory', []);
end;
procedure TChatBox.ApplyTheme;
var
i: Integer;
elc: IElementCollection;
begin
theme.pic2ico(RQteFormIcon, PIC_MSG, chatFrm.Icon);
Call('applyTheme', []);
elc := SelectAll('#pages > .page');
for i := 0 to elc.Count - 1 do
InitPage(Account.AccProto.GetContact(TUID(elc[i].Call('getChatId', []))));
end;
procedure TChatBox.UpdateSmiles;
begin
FireRoot($101);
end;
procedure TChatBox.ReloadSmiles;
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
i: Integer;
vals: TArray;
keys: TArray>;
begin
Result := '';
vals := emojis.Values.ToArray;
keys := emojis.Keys.ToArray;
for 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;
var
i, j: Integer;
arr: TArray;
em: Variant;
smiles, emoji, emojiNums: TParams;
smileData: TPair;
sm: String;
p: TPoint;
hasEmoji: Boolean;
begin
if not TryStrToInt(theme.GetString('emoji.size'), emojiSize) then
emojiSize := 22;
if not TryStrToInt(theme.GetString('emoji.inarow'), emojisInARow) then
emojisInARow := 36;
SetLength(smiles, theme.SmilesCount);
if theme.SmilesCount > 0 then
for 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;
hasEmoji := theme.HasOrigPic('emoji.sprite');
SetLength(emoji, 8);
SetLength(emojiNums, 8);
if hasEmoji then
for i := 0 to 7 do
begin
emojiContents.TryGetValue(i, arr);
emoji[i] := VarArrayCreate([0, Length(arr) - 1], varVariant);
for j := 0 to Length(arr) - 1 do
begin
p := GetOffset(arr[j]);
em := VarArrayCreate([0, 2], varVariant);
em[0] := GetEmoji(arr[j]);
em[1] := -p.X;
em[2] := -p.Y;
emoji[i][j] := em;
end;
p := GetOffset(emojiExtNumbers[i]);
emojiNums[i] := VarArrayCreate([0, 2], varVariant);
emojiNums[i][0] := emojiExtHints[i];
emojiNums[i][1] := -p.X;
emojiNums[i][2] := -p.Y;
end;
Call('reloadSmiles', [smiles, hasEmoji, emojiSize, emoji, emojiNums]);
end;
procedure TChatBox.PreloadPickers;
var
LinkList: String;
begin
LinkList := GetCachedPickers;
if not (LinkList = '') then
Call('preloadImages', [LinkList]);
end;
procedure TChatBox.LoadStickers;
var
StickerPacks: TStickerPacks;
Args: TParams;
i: Integer;
begin
StickerPacks := SQLDB.GetStickerPacks(True);
SetLength(args, Length(StickerPacks));
for i := 0 to Length(StickerPacks) - 1 do
begin
Args[i] := VarArrayCreate([0, 2], varVariant);
Args[i][0] := StickerPacks[i].Id;
Args[i][1] := StickerPacks[i].Name;
Args[i][2] := StickerPacks[i].Count;
end;
Call('loadStickers', [Args]);
end;
procedure TChatBox.LoadSearchResults;
var
StickerPacksVar: TParams;
i: Integer;
begin
with Account.AccProto do
begin
SetLength(StickerPacksVar, Length(LastSearchPacks));
for i := 0 to Length(LastSearchPacks) - 1 do
StickerPacksVar[i] := 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;
Val: Variant;
begin
if not Preview and RelativeTimeInChat then
begin
// Exit(datetocoolstr(Time));
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;
procedure GetEventHeaderTime(tag: Pointer; argc: UINT; argv: PSciterValue; retval: PSciterValue); cdecl;
var
When: Double;
Days, Minutes: Int64;
Val: Variant;
begin
API.ValueFloatData(argv, When);
Val := GetHeaderTime(When);
V2S(Val, retval);
end;
procedure TChatBox.AddChatItem(var Params: TParams; var MsgData: TMessageData; Evt: Thevent; Animate: Boolean; Preview: Boolean = False);
var
msgText, msgCls: String;
evPicRect,
cryptPicRect,
statusImg1Rect, statusImg2Rect: TGPRect;
statusImg1PicName, statusImg2PicName: TPicName;
hdr: THeader;
st: Integer;
b: Byte;
sA: TBytes;
inv: Boolean;
imgList: TImgBytes;
bodyImages: TStringDynArray;
bodyBin: TBytes;
i, Last: Integer;
hash: LongWord;
ffs: TFormatSettings;
ExtSticker: TStringDynArray;
begin
if Evt = nil then
Exit;
IsLastParsedEventMine := Evt.outgoing;
hdr := Evt.GetHeaderTexts;
msgText := Evt.GetBodyText;
if TrimMsgNewLines then
msgText := msgText.Trim([#13, #10]);
evPicRect := theme.GetPicRect(RQteDefault, Evt.pic);
cryptPicRect := theme.GetPicRect(RQteDefault, vKeyPicElm.picName);
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 := IntToStr(Evt.ID);
MsgData.eventImg := Evt.pic;
MsgData.encrypted := IF_Encrypt 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].picName;
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])].picName;
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
SetLength(bodyImages, Length(bodyImages) + 1);
hash := CalcMurmur2(imgList[i]);
if not embeddedImgs.ContainsKey(hash) then
embeddedImgs.Add(hash, imgList[i]);
bodyImages[Length(bodyImages) - 1] := IntToStr(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]);
SetLength(bodyImages, Length(bodyImages) + 1);
hash := CalcMurmur2(sA);
if not embeddedImgs.ContainsKey(hash) then
embeddedImgs.Add(hash, sA);
bodyImages[Length(bodyImages) - 1] := IntToStr(hash);
msgText := '';
end;
end;
end;
MsgData.msg := ParseMessageBody(msgText);
if Length(bodyImages) > 0 then
begin
MsgData.embedded := '
';
for i := 0 to Length(bodyImages) - 1 do
MsgData.embedded := MsgData.embedded + '';
MsgData.embedded := MsgData.embedded + '';
end;
MsgData.writeHist := logpref.writehistory and (BE_save in behaviour[Evt.kind].trig);
SetLength(Params, Length(Params) + 1);
Last := Length(Params) - 1;
Params[Last] := RecordToVar(MsgData);
end;
function TChatBox.GetPage(const uid: TUID): IElement;
var
i: Integer;
elc: IElementCollection;
begin
Result := nil;
if not (uid = '') then
begin
elc := SelectAll('#pages > .page');
for i := 0 to elc.Count - 1 do
if elc[i].Call('getChatId', []) = uid then
begin
Result := elc[i];
Break;
end;
end else
Result := Select('#pages > .page:current');
end;
function TChatBox.GetPage(id: Integer): IElement;
var
i: Integer;
elc: IElementCollection;
begin
Result := nil;
if not (id = 0) then
begin
elc := SelectAll('#pages > .page');
for i := 0 to elc.Count - 1 do
if elc[i].Call('getPlugId', []) = id then
begin
Result := elc[i];
Break;
end;
end;
end;
function TChatBox.GetLastEventTime(const uid: TUID): TDateTime;
var
el: IElement;
begin
Result := 0;
el := GetPage(uid);
if Assigned(el) and el.IsValid then
Result := StrToFloat(el.Call('getLastEventTime', []));
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; cmd: UINT; data: Variant);
var
el: IElement;
begin
el := GetPage(uid);
if Assigned(el) and el.IsValid then
Fire(el.Handle, cmd, data, false); // TODO: Sciter post bug? Wait for a fix
end;
procedure TChatBox.PageFire(id: Integer; cmd: UINT; data: Variant);
var
el: IElement;
begin
el := GetPage(id);
if Assigned(el) and el.IsValid then
Fire(el.Handle, cmd, data)
end;
procedure TChatBox.PageCall(const uid: TUID; const method: WideString; const args: TParams);
var
el: IElement;
begin
el := GetPage(uid);
if Assigned(el) and el.IsValid then
el.Call(method, args)
end;
procedure TChatBox.PageCall(id: Integer; const method: WideString; const args: TParams);
var
el: IElement;
begin
el := GetPage(id);
if Assigned(el) and el.IsValid then
el.Call(method, args);
end;
procedure TChatBox.SendChatItems(const uid: TUID; params: TParams; prepend: Boolean = False);
begin
PageFire(UID, RDUtils.IfThen(prepend, $99, $100), 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
PageCall('', 'addToInput', [s]);
end;
function TChatBox.FileUpload(Compress: Boolean; fn: String = ''): String;
var
url: String;
str: TStringList;
fs: TStream;
begin
Result := '';
if fn = '' then
fn := openSaveDlg(chatFrm, 'Select file to transfer', True, '', '', '', Compress);
if not (fn = '') then
begin
if Assigned(CurrentContact) then
begin
str := TStringList.Create;
str.StrictDelimiter := True;
str.Delimiter := ';';
str.DelimitedText := fn;
if (str.Count = 1) and not FileExists(str.Strings[0]) then
begin
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 := UploadFileRGhost(fs, fn, UploadCallbacks)
else if ServerToUpload = 1 then
url := UploadFileMikanoshi(fs, fn, UploadCallbacks)
else
url := UploadFileRnQ(fs, fn, UploadCallbacks);
finally
SetupFileBtn(True);
if Assigned(fs) then FreeAndNil(fs);
end;
Result := Trim(url);
end;
end;
end;
(*
function WTFisit(vm: HVM; val: tiscript_value): String;
begin
if NI.is_int(val) then Result := Result + ' is_int';
if NI.is_float(val) then Result := Result + ' is_float';
if NI.is_symbol(val) then Result := Result + ' is_symbol';
if NI.is_string(val) then Result := Result + ' is_string';
if NI.is_array(val) then Result := Result + ' is_array';
if NI.is_object(val) then Result := Result + ' is_object';
if NI.is_native_object(val) then Result := Result + ' is_native_object';
if NI.is_function(val) then Result := Result + ' is_function';
if NI.is_native_function(val) then Result := Result + ' is_native_function';
// if NI.is_instance_of : function(v: tiscript_value; cls: tiscript_value): Boolean; cdecl;
if NI.is_undefined(val) then Result := Result + ' is_undefined';
if NI.is_nothing(val) then Result := Result + ' is_nothing';
if NI.is_null(val) then Result := Result + ' is_null';
if NI.is_true(val) then Result := Result + ' is_true';
if NI.is_false(val) then Result := Result + ' is_false';
if NI.is_class(vm, val) then Result := Result + ' is_class';
if NI.is_error(val) then Result := Result + ' is_error';
if NI.is_bytes(val) then Result := Result + ' is_bytes';
if NI.is_datetime(vm, val) then Result := Result + ' is_datetime';
end;
*)
procedure TChatBox.ShowServerHistoryNotif(const UID: TUID);
begin
PageFire(UID, $102, Null);
end;
procedure TChatBox.ShowSearchHere;
begin
FireRoot($104);
end;
procedure TChatBox.FinishImage(const link: String);
begin
Call('finishImagesByLink', [link]);
end;
constructor TChatBox.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetParentComponent(AOwner);
histories := TDictionary.Create;
NativeMethods := TNativeMethods.Create(Self);
TabStop := True;
Color := $00F6F6F6;
LastContact := nil;
OnScriptingCall := ScriptingCall;
// Reduced stuttering, but black flickering
// API.SciterSetOption(0, SCITER_SET_GFX_LAYER, UINT_PTR(GFX_LAYER_WARP));
end;
procedure TChatBox.ScriptingCall(ASender: TObject; const Args: TElementOnScriptingCallArgs);
begin
if Args.Method = 'getNativeApi' then
with NativeMethods do
begin
AddMethod('SaveEmbeddedFile', SaveEmbeddedFile);
AddMethod('LoadHistory', LoadHistory, True);
AddMethod('UpdateSelection', UpdateSelection, True);
AddMethod('GetYoutubeLinks', GetYoutubeLinks);
AddMethod('GetVimeoLinks', GetVimeoLinks);
AddMethod('GetVolumeLevel', GetVolumeLevel);
AddMethod('SaveVolumeLevel', SaveVolumeLevel);
AddMethod('UploadLastSnapshot', UploadLastSnapshot, True);
AddMethod('DeleteSnapshot', DeleteSnapshot);
AddMethod('ChatPageSelected', ChatPageSelected, True);
AddMethod('ChatPageDeselected', ChatPageDeselected, True);
AddMethod('PluginPageSelected', PluginPageSelected, True);
AddMethod('PluginPageDeselected', PluginPageDeselected, True);
AddMethod('FormClose', FormClose);
AddMethod('SetTabDragging', SetTabDragging);
AddMethod('CloseChatPage', CloseChatPage, True);
AddMethod('ClosePluginPage', ClosePluginPage, True);
AddMethod('AddUIN2CL', AddUIN2CL, True);
AddMethod('GetSpriteData', GetSpriteData);
AddMethod('CopyLink', CopyLink);
AddMethod('SavePicture', SavePicture, True);
AddMethod('GetEvent', GetEvent, True);
AddMethod('SaveAs', SaveAs, True);
AddMethod('AddLinkToFav', AddLinkToFav);
AddMethod('DeleteMessages', DeleteMessages, True);
AddMethod('AddToAntispam', AddToAntispam, True);
AddMethod('ViewInfo', ViewInfo, True);
AddMethod('ToggleSmiles', ToggleSmiles, True);
AddMethod('ToggleRelTimes', ToggleRelTimes);
AddMethod('RealizeEvents', RealizeEvents);
AddMethod('StoreSplit', StoreSplit);
AddMethod('InputChangedFor', InputChangedFor);
AddMethod('GetMessageByIdx', GetMessageByIdx, True);
AddMethod('WrapText', WrapText);
AddMethod('SendChatMessage', SendChatMessage, True);
AddMethod('ClosePages', ClosePages, True);
AddMethod('UploadFile', UploadFile, True);
AddMethod('ChatButtonClick', ChatButtonClick, True);
AddMethod('PluginButtonClick', PluginButtonClick);
AddMethod('OpenOutbox', OpenOutbox, True);
AddMethod('ToggleTranslit', ToggleTranslit, True);
AddMethod('QIPPwd', QIPPwd, True);
AddMethod('SendStickerToCurrent', SendStickerToCurrent, True);
AddMethod('GetSpellingSuggestions', GetSpellingSuggestions);
AddMethod('IsWindowActive', IsWindowActive);
AddMethod('GetStickerPacks', GetStickerPacks, True);
AddMethod('GetStoreStickerPacks', GetStoreStickerPacks);
AddMethod('SearchStickerPacks', SearchStickerPacks);
AddMethod('BuyStickerPack', BuyStickerPack);
AddMethod('RemoveStickerPack', RemoveStickerPack);
AddMethod('GetEventHeaderTime', GetEventHeaderTime);
RegisterMethods(Args.ReturnSciterValue);
Args.Handled := True;
end;
end;
initialization
vKeyPicElm.ThemeToken := -1;
vKeyPicElm.picName := PIC_KEY;
vKeyPicElm.Element := RQteDefault;
vKeyPicElm.pEnabled := true;
CreateMessageRegex;
emojiContents := TDictionary>.Create;
emojiContents.Add(0, TArray.Create(933, 977, 934, 935, 936, 937, 938, 939, 940, 942, 943, 999, 1000, 1173, 944, 945, 946, 957, 956, 958, 959, 961, 962, 960, 1101, 1103, 947, 1107, 948, 987, 949, 950, 951, 1001, 1104, 984, 963, 964, 965, 966, 953, 954, 998, 1172, 968, 955, 976, 974, 969, 979, 982, 973, 981, 980, 971, 972, 967, 970, 975, 952, 978, 986, 983, 1100, 988, 1102, 1105, 985, 709, 714, 941, 672, 666, 667, 673, 668, 670, 1106, 991, 989, 990, 992, 993, 994, 997, 996, 995, 1009, 624, 620, 622, 623, 619, 1234, 1236, 621, 1235, 625, 715, 1012, 1163, 615, 616, 617, 618, 907, 906, 1108, 908, 1237, 678, 613, 614, 611, 612, 609, 608, 645, 646, 924, 663, 647, 648, 649, 650, 658, 661, 662, 659, 660, 655, 664, 675, 896, 434, 669, 665, 657, 1067, 491, 676, 656, 652, 653, 654, 1004, 674, 1002, 1003, 1008, 1011, 1010, 680, 679, 690, 688, 651, 635, 630, 631, 629, 632, 634, 633, 677, 684, 644, 641, 642, 643, 639, 640, 627, 465, 1214, 448, 626, 447, 638, 636, 637, 733, 628, 897, 686, 305));
emojiContents.Add(1, TArray.Create(598, 593, 589, 601, 592, 603, 604, 584, 591, 1110, 590, 599, 605, 600, 569, 597, 1005, 1006, 1007, 562, 564, 583, 582, 580, 579, 581, 602, 567, 596, 1113, 573, 571, 556, 574, 572, 898, 1111, 1109, 557, 578, 576, 575, 577, 588, 595, 555, 554, 550, 549, 547, 546, 548, 586, 587, 568, 560, 559, 561, 558, 566, 544, 545, 563, 1112, 863, 565, 585, 552, 551, 607, 606, 553, 594, 354, 433, 351, 352, 353, 350, 364, 1162, 365, 442, 440, 368, 367, 366, 363, 359, 360, 358, 356, 361, 357, 689, 369, 349, 432, 570, 899, 317, 316, 318, 324, 325, 326, 327, 320, 321, 322, 323, 329, 332, 330, 331, 333, 328, 1270, 334, 716, 1244, 1157, 1153, 337, 1210, 338, 339, 1154, 340, 1211, 342, 1202, 837, 710, 1247, 341, 1156, 1209, 345, 713, 343, 344, 1155, 1160, 712, 711, 313));
emojiContents.Add(2, TArray.Create(380, 379, 381, 375, 376, 377, 374, 372, 384, 373, 383, 382, 378, 370, 371, 355, 362, 397, 412, 395, 1114, 388, 387, 401, 416, 385, 396, 346, 386, 394, 347, 348, 393, 415, 402, 400, 414, 392, 390, 391, 389, 399, 398, 404, 405, 403, 413, 431, 411, 409, 410, 408, 428, 406, 407, 423, 424, 420, 421, 422, 427, 419, 418, 1161, 425, 417, 426));
emojiContents.Add(3, TArray.Create(1207, 488, 496, 1208, 486, 504, 497, 473, 1222, 500, 507, 541, 506, 505, 503, 487, 1225, 490, 1226, 542, 459, 1048, 498, 492, 1077, 1227, 499, 1065, 1066, 495, 895, 494, 485, 493, 449, 450, 539, 467, 455, 469, 464, 466, 460, 463, 484, 481, 479, 482, 480, 483, 468, 470, 671, 471, 474, 472, 475));
emojiContents.Add(4, TArray.Create(1036, 1034, 1038, 1025, 1027, 502, 1032, 1030, 1031, 1029, 1039, 1040, 1041, 501, 1063, 1053, 1033, 1026, 1037, 1035, 1046, 1045, 1044, 1016, 1024, 1042, 1017, 1018, 1021, 1043, 1015, 1019, 1020, 1023, 1022, 1014, 1095, 1232, 1096, 1097, 1224, 1094, 1049, 1223, 1099, 1013, 1098, 731, 1194, 1052, 1229, 1028, 1051, 1050, 489, 1047, 457, 458, 456, 511, 304, 929, 533, 1221, 446, 1219, 508, 928, 314, 931, 509, 1228, 518, 1092, 1093, 308, 307, 516, 510, 517, 310, 309, 513, 306, 312, 315, 335, 436, 435, 311, 512, 536, 535, 519, 930, 520, 521, 514, 522, 532, 523, 524, 525, 526, 528, 530, 531, 529, 691, 515, 1218, 865, 866, 864, 1217));
emojiContents.Add(5, TArray.Create(1127, 786, 787, 732, 1129, 909, 910, 911, 912, 900, 920, 734, 735, 736, 737, 797, 792, 793, 794, 461, 798, 454, 767, 1158, 768, 769, 795, 796, 451, 452, 453, 1138, 1139, 1137, 893, 1140, 1128, 770, 811, 812, 706, 838, 892, 917, 1091, 729, 726, 725, 727, 728, 721, 724, 687, 1196, 839, 840, 1193, 1089, 1213, 841, 1198, 1215, 843, 708, 842, 923, 1195, 1090, 1057, 1164, 1205, 1206, 543, 846, 799, 681, 1197, 845, 844, 894, 683, 682, 336, 540, 822, 1074, 1076, 1078, 817, 921, 1083, 1084, 1087, 1055, 1086, 913, 927, 1220, 932, 1085, 437, 444, 429, 430, 439, 438, 443, 445, 441, 534, 1233, 778, 777, 776, 685, 783, 779, 780, 781, 782, 775, 784, 774, 773, 765, 740, 754, 747, 745, 746, 741, 742, 743, 919, 744, 915, 926, 916, 748, 918, 738, 739, 914, 922, 785, 756, 758, 760, 761, 762, 757, 755, 763, 759, 823, 751, 901, 1230, 753, 752, 749, 750, 1054, 537, 538, 816, 818, 819, 815, 902, 903, 1239, 766, 1238, 905, 904, 813, 814));
emojiContents.Add(6, TArray.Create(1256, 700, 699, 698, 701, 693, 1255, 694, 703, 692, 696, 695, 697, 702, 704, 1169, 1242, 1168, 862, 1171, 1243, 847, 867, 1170, 1167, 1088, 1212, 1174, 1175, 1176, 1177, 1178, 1179, 1180, 1181, 1182, 1183, 1184, 1185, 24, 1199, 293, 299, 1165, 1166, 789, 788, 296, 290, 298, 300, 297, 1246, 30, 302, 719, 301, 1275, 1274, 294, 295, 292, 16, 17, 20, 21, 18, 28, 1216, 764, 1056, 1249, 1271, 707, 1190, 1068, 1060, 1064, 1062, 830, 790, 1254, 1253, 1251, 1252, 1115, 1116, 720, 805, 806, 849, 1200, 1273, 1201, 1069, 848, 1191, 291, 730, 1248, 1245, 1250, 1231, 705, 303, 1262, 319, 1144, 527, 289, 1079, 1080, 1081, 1082, 1192, 1058, 1075, 19, 1061, 1070, 1071, 1073, 1072, 1059, 462, 791, 288, 26, 27, 29, 22, 25, 23, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 831, 834, 1147, 1141, 1136, 1142, 1143, 1134, 1135, 1130, 1131, 800, 801, 802, 1148, 860, 861, 1132, 1133, 1260, 1265, 1266, 1267, 1122, 1123, 1124, 1121, 1120, 1119, 804, 1126, 1125, 1263, 1264, 0, 1, 1118, 836,
833, 832, 835, 477, 478, 1272, 1261, 1240, 803, 1257, 1258, 1259, 1241, 723, 722, 12, 13, 1117, 826, 825, 827, 829, 828, 1159, 824, 1203, 1204, 852, 853, 856, 857, 854, 855, 858, 1145, 1146, 1268, 1269, 859, 1150, 1149, 1152, 1151, 850, 851, 808, 809, 810, 807, 772, 771, 820, 821, 15, 14, 1186, 1187, 1188, 1189, 476, 718, 925, 717, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 610));
emojiContents.Add(7, TArray.Create(31, 34, 37, 95, 32, 39, 36, 35, 41, 38, 45, 44, 43, 47, 63, 54, 50, 49, 67, 51, 68, 56, 58, 64, 60, 48, 66, 62, 59, 53, 52, 55, 84, 153, 78, 69, 160, 72, 252, 77, 79, 80, 155, 73, 71, 82, 133, 83, 87, 88, 92, 91, 93, 94, 97, 99, 246, 123, 101, 98, 103, 107, 109, 106, 105, 110, 211, 111, 120, 114, 89, 117, 118, 124, 119, 113, 127, 126, 121, 128, 129, 134, 132, 130, 135, 145, 141, 137, 144, 143, 138, 139, 146, 75, 148, 150, 147, 149, 161, 151, 154, 282, 159, 152, 162, 171, 163, 168, 167, 172, 165, 169, 170, 184, 180, 178, 192, 194, 191, 181, 189, 179, 187, 190, 193, 108, 175, 174, 183, 176, 188, 173, 195, 182, 196, 205, 204, 202, 197, 207, 201, 198, 200, 206, 157, 203, 208, 214, 221, 219, 209, 212, 222, 210, 213, 215, 220, 218, 223, 225, 227, 228, 235, 156, 164, 274, 281, 240, 245, 229, 241, 226, 231, 239, 234, 238, 236, 230, 242, 285, 158, 102, 166, 232, 243, 249, 233, 74, 248, 265, 256, 266, 255, 258, 254, 261, 263, 260, 262, 259, 264, 268, 267, 33,
112, 270, 277, 271, 272, 279, 273, 275, 278, 280, 100, 283, 286, 287, 224, 46, 250, 142, 61, 86, 70, 116, 140, 284, 199, 217, 57, 216, 125, 257, 65, 131, 237, 269, 136, 96, 81, 90, 42, 40, 276, 76, 85, 104, 115, 253, 122, 186, 185, 247, 244, 251, 177));
finalization
FreeMessageRegex;
emojiContents.Free;
end.