Autocomplete bot commands
Search by hashtags
This commit is contained in:
parent
072494d668
commit
d8be5fc0ba
@ -72,7 +72,7 @@ export default class AppSearch {
|
|||||||
|
|
||||||
private query = '';
|
private query = '';
|
||||||
|
|
||||||
public listsContainer: HTMLDivElement = null;
|
private listsContainer: HTMLDivElement = null;
|
||||||
|
|
||||||
private peerId = 0; // 0 - means global
|
private peerId = 0; // 0 - means global
|
||||||
private threadId = 0;
|
private threadId = 0;
|
||||||
|
@ -6,10 +6,12 @@
|
|||||||
|
|
||||||
import attachListNavigation from "../../helpers/dom/attachlistNavigation";
|
import attachListNavigation from "../../helpers/dom/attachlistNavigation";
|
||||||
import EventListenerBase from "../../helpers/eventListenerBase";
|
import EventListenerBase from "../../helpers/eventListenerBase";
|
||||||
|
import { safeAssign } from "../../helpers/object";
|
||||||
import { isMobile } from "../../helpers/userAgent";
|
import { isMobile } from "../../helpers/userAgent";
|
||||||
import rootScope from "../../lib/rootScope";
|
import rootScope from "../../lib/rootScope";
|
||||||
import appNavigationController, { NavigationItem } from "../appNavigationController";
|
import appNavigationController, { NavigationItem } from "../appNavigationController";
|
||||||
import SetTransition from "../singleTransition";
|
import SetTransition from "../singleTransition";
|
||||||
|
import AutocompleteHelperController from "./autocompleteHelperController";
|
||||||
|
|
||||||
export default class AutocompleteHelper extends EventListenerBase<{
|
export default class AutocompleteHelper extends EventListenerBase<{
|
||||||
hidden: () => void,
|
hidden: () => void,
|
||||||
@ -21,19 +23,30 @@ export default class AutocompleteHelper extends EventListenerBase<{
|
|||||||
protected resetTarget: () => void;
|
protected resetTarget: () => void;
|
||||||
protected init?(): void;
|
protected init?(): void;
|
||||||
|
|
||||||
constructor(appendTo: HTMLElement,
|
protected controller: AutocompleteHelperController;
|
||||||
protected listType: 'xy' | 'x' | 'y',
|
protected listType: 'xy' | 'x' | 'y';
|
||||||
protected onSelect: (target: Element) => boolean | void,
|
protected onSelect: (target: Element) => boolean | void;
|
||||||
protected waitForKey?: string
|
protected waitForKey?: string;
|
||||||
) {
|
|
||||||
|
constructor(options: {
|
||||||
|
appendTo: HTMLElement,
|
||||||
|
controller: AutocompleteHelper['controller'],
|
||||||
|
listType: AutocompleteHelper['listType'],
|
||||||
|
onSelect: AutocompleteHelper['onSelect'],
|
||||||
|
waitForKey?: AutocompleteHelper['waitForKey']
|
||||||
|
}) {
|
||||||
super(false);
|
super(false);
|
||||||
|
|
||||||
|
safeAssign(this, options);
|
||||||
|
|
||||||
this.container = document.createElement('div');
|
this.container = document.createElement('div');
|
||||||
this.container.classList.add('autocomplete-helper', 'z-depth-1');
|
this.container.classList.add('autocomplete-helper', 'z-depth-1');
|
||||||
|
|
||||||
appendTo.append(this.container);
|
options.appendTo.append(this.container);
|
||||||
|
|
||||||
this.attachNavigation();
|
this.attachNavigation();
|
||||||
|
|
||||||
|
this.controller.addHelper(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onVisible = () => {
|
protected onVisible = () => {
|
||||||
@ -89,7 +102,14 @@ export default class AutocompleteHelper extends EventListenerBase<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.hidden = hide;
|
this.hidden = hide;
|
||||||
!this.hidden && this.dispatchEvent('visible'); // fire it before so target will be set
|
|
||||||
|
if(!this.hidden) {
|
||||||
|
this.controller.hideOtherHelpers(this);
|
||||||
|
this.dispatchEvent('visible'); // fire it before so target will be set
|
||||||
|
} else {
|
||||||
|
this.controller.hideOtherHelpers();
|
||||||
|
}
|
||||||
|
|
||||||
SetTransition(this.container, 'is-visible', !hide, rootScope.settings.animationsEnabled ? 200 : 0, () => {
|
SetTransition(this.container, 'is-visible', !hide, rootScope.settings.animationsEnabled ? 200 : 0, () => {
|
||||||
this.hidden && this.dispatchEvent('hidden');
|
this.hidden && this.dispatchEvent('hidden');
|
||||||
});
|
});
|
||||||
|
@ -1,21 +1,33 @@
|
|||||||
|
import { getMiddleware } from "../../helpers/middleware";
|
||||||
import AutocompleteHelper from "./autocompleteHelper";
|
import AutocompleteHelper from "./autocompleteHelper";
|
||||||
|
|
||||||
export default class AutocompleteHelperController {
|
export default class AutocompleteHelperController {
|
||||||
private helpers: Set<AutocompleteHelper> = new Set();
|
private helpers: Set<AutocompleteHelper> = new Set();
|
||||||
|
private middleware = getMiddleware();
|
||||||
|
/* private tempId = 0;
|
||||||
|
|
||||||
public addHelpers(helpers: AutocompleteHelper[]) {
|
public incrementToggleCount() {
|
||||||
for(const helper of helpers) {
|
return ++this.tempId;
|
||||||
this.helpers.add(helper);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public toggleHelper(helper: AutocompleteHelper, hide?: boolean) {
|
public getToggleCount() {
|
||||||
|
return this.tempId;
|
||||||
|
} */
|
||||||
|
|
||||||
|
public getMiddleware() {
|
||||||
|
this.middleware.clean();
|
||||||
|
return this.middleware.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public addHelper(helper: AutocompleteHelper) {
|
||||||
|
this.helpers.add(helper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public hideOtherHelpers(helper?: AutocompleteHelper) {
|
||||||
this.helpers.forEach(h => {
|
this.helpers.forEach(h => {
|
||||||
if(h !== helper) {
|
if(h !== helper) {
|
||||||
helper.toggle(true);
|
h.toggle(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.toggle(hide);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,9 +78,9 @@ let TEST_SCROLL = TEST_SCROLL_TIMES;
|
|||||||
let queueId = 0;
|
let queueId = 0;
|
||||||
|
|
||||||
export default class ChatBubbles {
|
export default class ChatBubbles {
|
||||||
bubblesContainer: HTMLDivElement;
|
public bubblesContainer: HTMLDivElement;
|
||||||
chatInner: HTMLDivElement;
|
private chatInner: HTMLDivElement;
|
||||||
scrollable: Scrollable;
|
public scrollable: Scrollable;
|
||||||
|
|
||||||
private getHistoryTopPromise: Promise<boolean>;
|
private getHistoryTopPromise: Promise<boolean>;
|
||||||
private getHistoryBottomPromise: Promise<boolean>;
|
private getHistoryBottomPromise: Promise<boolean>;
|
||||||
@ -88,11 +88,11 @@ export default class ChatBubbles {
|
|||||||
public peerId = 0;
|
public peerId = 0;
|
||||||
//public messagesCount: number = -1;
|
//public messagesCount: number = -1;
|
||||||
|
|
||||||
public unreadOut = new Set<number>();
|
private unreadOut = new Set<number>();
|
||||||
public needUpdate: {replyToPeerId: number, replyMid: number, mid: number}[] = []; // if need wrapSingleMessage
|
public needUpdate: {replyToPeerId: number, replyMid: number, mid: number}[] = []; // if need wrapSingleMessage
|
||||||
|
|
||||||
public bubbles: {[mid: string]: HTMLDivElement} = {};
|
public bubbles: {[mid: string]: HTMLDivElement} = {};
|
||||||
public dateMessages: {[timestamp: number]: {
|
private dateMessages: {[timestamp: number]: {
|
||||||
div: HTMLDivElement,
|
div: HTMLDivElement,
|
||||||
firstTimestamp: number,
|
firstTimestamp: number,
|
||||||
container: HTMLDivElement,
|
container: HTMLDivElement,
|
||||||
@ -109,14 +109,14 @@ export default class ChatBubbles {
|
|||||||
private unreadedSeen: Set<number> = new Set();
|
private unreadedSeen: Set<number> = new Set();
|
||||||
private readPromise: Promise<void>;
|
private readPromise: Promise<void>;
|
||||||
|
|
||||||
public bubbleGroups: BubbleGroups;
|
private bubbleGroups: BubbleGroups;
|
||||||
|
|
||||||
private preloader: ProgressivePreloader = null;
|
private preloader: ProgressivePreloader = null;
|
||||||
|
|
||||||
private loadedTopTimes = 0;
|
private loadedTopTimes = 0;
|
||||||
private loadedBottomTimes = 0;
|
private loadedBottomTimes = 0;
|
||||||
|
|
||||||
public messagesQueuePromise: Promise<void> = null;
|
private messagesQueuePromise: Promise<void> = null;
|
||||||
private messagesQueue: {message: any, bubble: HTMLDivElement, reverse: boolean, promises: Promise<void>[]}[] = [];
|
private messagesQueue: {message: any, bubble: HTMLDivElement, reverse: boolean, promises: Promise<void>[]}[] = [];
|
||||||
private messagesQueueOnRender: () => void = null;
|
private messagesQueueOnRender: () => void = null;
|
||||||
private messagesQueueOnRenderAdditional: () => void = null;
|
private messagesQueueOnRenderAdditional: () => void = null;
|
||||||
@ -132,12 +132,12 @@ export default class ChatBubbles {
|
|||||||
|
|
||||||
public listenerSetter: ListenerSetter;
|
public listenerSetter: ListenerSetter;
|
||||||
|
|
||||||
public replyFollowHistory: number[] = [];
|
private replyFollowHistory: number[] = [];
|
||||||
|
|
||||||
public isHeavyAnimationInProgress = false;
|
private isHeavyAnimationInProgress = false;
|
||||||
public scrollingToNewBubble: HTMLElement;
|
private scrollingToNewBubble: HTMLElement;
|
||||||
|
|
||||||
public isFirstLoad = true;
|
private isFirstLoad = true;
|
||||||
private needReflowScroll: boolean;
|
private needReflowScroll: boolean;
|
||||||
|
|
||||||
private fetchNewPromise: Promise<void>;
|
private fetchNewPromise: Promise<void>;
|
||||||
|
@ -37,6 +37,8 @@ import { fastRaf } from "../../helpers/schedulers";
|
|||||||
import AppPrivateSearchTab from "../sidebarRight/tabs/search";
|
import AppPrivateSearchTab from "../sidebarRight/tabs/search";
|
||||||
import type { State } from "../../lib/appManagers/appStateManager";
|
import type { State } from "../../lib/appManagers/appStateManager";
|
||||||
import renderImageFromUrl from "../../helpers/dom/renderImageFromUrl";
|
import renderImageFromUrl from "../../helpers/dom/renderImageFromUrl";
|
||||||
|
import mediaSizes from "../../helpers/mediaSizes";
|
||||||
|
import ChatSearch from "./search";
|
||||||
|
|
||||||
export type ChatType = 'chat' | 'pinned' | 'replies' | 'discussion' | 'scheduled';
|
export type ChatType = 'chat' | 'pinned' | 'replies' | 'discussion' | 'scheduled';
|
||||||
|
|
||||||
@ -371,4 +373,19 @@ export default class Chat extends EventListenerBase<{
|
|||||||
public isAnyGroup() {
|
public isAnyGroup() {
|
||||||
return this.peerId === rootScope.myId || this.peerId === REPLIES_PEER_ID || this.appPeersManager.isAnyGroup(this.peerId);
|
return this.peerId === rootScope.myId || this.peerId === REPLIES_PEER_ID || this.appPeersManager.isAnyGroup(this.peerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public initSearch(query?: string) {
|
||||||
|
if(!this.peerId) return;
|
||||||
|
|
||||||
|
if(mediaSizes.isMobile) {
|
||||||
|
new ChatSearch(this.topbar, this, query);
|
||||||
|
} else {
|
||||||
|
let tab = appSidebarRight.getTab(AppPrivateSearchTab);
|
||||||
|
if(!tab) {
|
||||||
|
tab = new AppPrivateSearchTab(appSidebarRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.open(this.peerId, this.threadId, this.bubbles.onDatePick, query);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,24 @@
|
|||||||
|
import type ChatInput from "./input";
|
||||||
import { BotCommand } from "../../layer";
|
import { BotCommand } from "../../layer";
|
||||||
import RichTextProcessor from "../../lib/richtextprocessor";
|
import RichTextProcessor from "../../lib/richtextprocessor";
|
||||||
import AvatarElement from "../avatar";
|
import AvatarElement from "../avatar";
|
||||||
import Scrollable from "../scrollable";
|
import Scrollable from "../scrollable";
|
||||||
import AutocompleteHelper from "./autocompleteHelper";
|
import AutocompleteHelper from "./autocompleteHelper";
|
||||||
|
import AutocompleteHelperController from "./autocompleteHelperController";
|
||||||
|
|
||||||
export default class CommandsHelper extends AutocompleteHelper {
|
export default class CommandsHelper extends AutocompleteHelper {
|
||||||
private scrollable: Scrollable;
|
private scrollable: Scrollable;
|
||||||
|
|
||||||
constructor(appendTo: HTMLElement) {
|
constructor(appendTo: HTMLElement, controller: AutocompleteHelperController, private chatInput: ChatInput) {
|
||||||
super(appendTo, 'y', (target) => {
|
super({
|
||||||
|
appendTo,
|
||||||
|
controller,
|
||||||
|
listType: 'y',
|
||||||
|
onSelect: (target) => {
|
||||||
|
const command = target.querySelector('.commands-helper-command-name').innerHTML;
|
||||||
|
chatInput.messageInput.innerHTML = command;
|
||||||
|
chatInput.sendMessage();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.classList.add('commands-helper');
|
this.container.classList.add('commands-helper');
|
||||||
|
@ -2,13 +2,19 @@ import type ChatInput from "./input";
|
|||||||
import { appendEmoji, getEmojiFromElement } from "../emoticonsDropdown/tabs/emoji";
|
import { appendEmoji, getEmojiFromElement } from "../emoticonsDropdown/tabs/emoji";
|
||||||
import { ScrollableX } from "../scrollable";
|
import { ScrollableX } from "../scrollable";
|
||||||
import AutocompleteHelper from "./autocompleteHelper";
|
import AutocompleteHelper from "./autocompleteHelper";
|
||||||
|
import AutocompleteHelperController from "./autocompleteHelperController";
|
||||||
|
|
||||||
export default class EmojiHelper extends AutocompleteHelper {
|
export default class EmojiHelper extends AutocompleteHelper {
|
||||||
private scrollable: ScrollableX;
|
private scrollable: ScrollableX;
|
||||||
|
|
||||||
constructor(appendTo: HTMLElement, private chatInput: ChatInput) {
|
constructor(appendTo: HTMLElement, controller: AutocompleteHelperController, private chatInput: ChatInput) {
|
||||||
super(appendTo, 'x', (target) => {
|
super({
|
||||||
this.chatInput.onEmojiSelected(getEmojiFromElement(target as any), true);
|
appendTo,
|
||||||
|
controller,
|
||||||
|
listType: 'x',
|
||||||
|
onSelect: (target) => {
|
||||||
|
this.chatInput.onEmojiSelected(getEmojiFromElement(target as any), true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.classList.add('emoji-helper');
|
this.container.classList.add('emoji-helper');
|
||||||
|
@ -63,6 +63,8 @@ import setRichFocus from '../../helpers/dom/setRichFocus';
|
|||||||
import SearchIndex from '../../lib/searchIndex';
|
import SearchIndex from '../../lib/searchIndex';
|
||||||
import CommandsHelper from './commandsHelper';
|
import CommandsHelper from './commandsHelper';
|
||||||
import AutocompleteHelperController from './autocompleteHelperController';
|
import AutocompleteHelperController from './autocompleteHelperController';
|
||||||
|
import AutocompleteHelper from './autocompleteHelper';
|
||||||
|
import appUsersManager from '../../lib/appManagers/appUsersManager';
|
||||||
|
|
||||||
const RECORD_MIN_TIME = 500;
|
const RECORD_MIN_TIME = 500;
|
||||||
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
|
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
|
||||||
@ -369,11 +371,9 @@ export default class ChatInput {
|
|||||||
|
|
||||||
this.rowsWrapper.append(this.replyElements.container);
|
this.rowsWrapper.append(this.replyElements.container);
|
||||||
this.autocompleteHelperController = new AutocompleteHelperController();
|
this.autocompleteHelperController = new AutocompleteHelperController();
|
||||||
this.autocompleteHelperController.addHelpers([
|
this.stickersHelper = new StickersHelper(this.rowsWrapper, this.autocompleteHelperController);
|
||||||
this.commandsHelper = new CommandsHelper(this.rowsWrapper),
|
this.emojiHelper = new EmojiHelper(this.rowsWrapper, this.autocompleteHelperController, this);
|
||||||
this.emojiHelper = new EmojiHelper(this.rowsWrapper, this),
|
this.commandsHelper = new CommandsHelper(this.rowsWrapper, this.autocompleteHelperController, this);
|
||||||
this.stickersHelper = new StickersHelper(this.rowsWrapper)
|
|
||||||
]);
|
|
||||||
this.rowsWrapper.append(this.newMessageWrapper);
|
this.rowsWrapper.append(this.newMessageWrapper);
|
||||||
|
|
||||||
this.btnCancelRecord = ButtonIcon('delete danger btn-circle z-depth-1 btn-record-cancel');
|
this.btnCancelRecord = ButtonIcon('delete danger btn-circle z-depth-1 btn-record-cancel');
|
||||||
@ -433,11 +433,12 @@ export default class ChatInput {
|
|||||||
|
|
||||||
this.listenerSetter.add(rootScope, 'settings_updated', () => {
|
this.listenerSetter.add(rootScope, 'settings_updated', () => {
|
||||||
if(this.stickersHelper) {
|
if(this.stickersHelper) {
|
||||||
if(!rootScope.settings.stickers.suggest) {
|
this.checkAutocomplete();
|
||||||
|
/* if(!rootScope.settings.stickers.suggest) {
|
||||||
this.stickersHelper.checkEmoticon('');
|
this.stickersHelper.checkEmoticon('');
|
||||||
} else {
|
} else {
|
||||||
this.onMessageInput();
|
this.onMessageInput();
|
||||||
}
|
} */
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.messageInputField) {
|
if(this.messageInputField) {
|
||||||
@ -1063,18 +1064,6 @@ export default class ChatInput {
|
|||||||
|
|
||||||
//this.chat.log('messageInput entities', richValue, value, markdownEntities, caretPos);
|
//this.chat.log('messageInput entities', richValue, value, markdownEntities, caretPos);
|
||||||
|
|
||||||
if(this.stickersHelper &&
|
|
||||||
rootScope.settings.stickers.suggest &&
|
|
||||||
(this.chat.peerId > 0 || this.appChatsManager.hasRights(this.chat.peerId, 'send_stickers'))) {
|
|
||||||
let emoticon = '';
|
|
||||||
const entity = entities[0];
|
|
||||||
if(entity?._ === 'messageEntityEmoji' && entity.length === richValue.length && !entity.offset) {
|
|
||||||
emoticon = richValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.stickersHelper.checkEmoticon(emoticon);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.canRedoFromHTML && !this.lockRedo && this.messageInput.innerHTML !== this.canRedoFromHTML) {
|
if(this.canRedoFromHTML && !this.lockRedo && this.messageInput.innerHTML !== this.canRedoFromHTML) {
|
||||||
this.canRedoFromHTML = '';
|
this.canRedoFromHTML = '';
|
||||||
this.undoHistory.length = 0;
|
this.undoHistory.length = 0;
|
||||||
@ -1148,7 +1137,7 @@ export default class ChatInput {
|
|||||||
this.saveDraftDebounced();
|
this.saveDraftDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.checkAutocomplete(richValue, caretPos);
|
this.checkAutocomplete(richValue, caretPos, entities);
|
||||||
|
|
||||||
this.updateSendBtn();
|
this.updateSendBtn();
|
||||||
};
|
};
|
||||||
@ -1221,25 +1210,31 @@ export default class ChatInput {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private checkAutocomplete(value?: string, caretPos?: number) {
|
private checkAutocomplete(value?: string, caretPos?: number, entities?: MessageEntity[]) {
|
||||||
//return;
|
//return;
|
||||||
|
|
||||||
if(value === undefined) {
|
if(value === undefined) {
|
||||||
const r = getRichValueWithCaret(this.messageInputField.input, false);
|
const r = getRichValueWithCaret(this.messageInputField.input, true);
|
||||||
value = r.value;
|
value = r.value;
|
||||||
caretPos = r.caretPos;
|
caretPos = r.caretPos;
|
||||||
|
entities = r.entities;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(caretPos === -1) {
|
if(caretPos === -1) {
|
||||||
caretPos = value.length;
|
caretPos = value.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(entities === undefined) {
|
||||||
|
const _value = RichTextProcessor.parseMarkdown(value, entities, true);
|
||||||
|
entities = RichTextProcessor.mergeEntities(entities, RichTextProcessor.parseEntities(_value));
|
||||||
|
}
|
||||||
|
|
||||||
value = value.substr(0, caretPos);
|
value = value.substr(0, caretPos);
|
||||||
|
|
||||||
const matches = value.match(ChatInput.AUTO_COMPLETE_REG_EXP);
|
const matches = value.match(ChatInput.AUTO_COMPLETE_REG_EXP);
|
||||||
if(!matches) {
|
if(!matches) {
|
||||||
delete this.previousQuery;
|
delete this.previousQuery;
|
||||||
//this.hideSuggestions();
|
this.autocompleteHelperController.hideOtherHelpers();
|
||||||
this.emojiHelper.toggle(true);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1248,6 +1243,17 @@ export default class ChatInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.previousQuery = matches[0];
|
this.previousQuery = matches[0];
|
||||||
|
|
||||||
|
let foundHelper: AutocompleteHelper;
|
||||||
|
const entity = entities[0];
|
||||||
|
|
||||||
|
if(this.stickersHelper &&
|
||||||
|
rootScope.settings.stickers.suggest &&
|
||||||
|
(this.chat.peerId > 0 || this.appChatsManager.hasRights(this.chat.peerId, 'send_stickers')) &&
|
||||||
|
entity?._ === 'messageEntityEmoji' && entity.length === value.length && !entity.offset) {
|
||||||
|
foundHelper = this.stickersHelper;
|
||||||
|
this.stickersHelper.checkEmoticon(value);
|
||||||
|
} else
|
||||||
//let query = cleanSearchText(matches[2]);
|
//let query = cleanSearchText(matches[2]);
|
||||||
//const firstChar = matches[2][0];
|
//const firstChar = matches[2][0];
|
||||||
|
|
||||||
@ -1277,7 +1283,8 @@ export default class ChatInput {
|
|||||||
this.hideSuggestions()
|
this.hideSuggestions()
|
||||||
}
|
}
|
||||||
} else */ if(!matches[1] && matches[2][0] === '/') { // commands
|
} else */ if(!matches[1] && matches[2][0] === '/') { // commands
|
||||||
if(this.chat.peerId > 0) {
|
if(appUsersManager.isBot(this.chat.peerId)) {
|
||||||
|
foundHelper = this.commandsHelper;
|
||||||
this.chat.appProfileManager.getProfileByPeerId(this.chat.peerId).then(full => {
|
this.chat.appProfileManager.getProfileByPeerId(this.chat.peerId).then(full => {
|
||||||
const botInfos: BotInfo.botInfo[] = [].concat(full.bot_info);
|
const botInfos: BotInfo.botInfo[] = [].concat(full.bot_info);
|
||||||
const index = new SearchIndex<string>(false, false);
|
const index = new SearchIndex<string>(false, false);
|
||||||
@ -1293,22 +1300,22 @@ export default class ChatInput {
|
|||||||
const found = index.search(matches[2]);
|
const found = index.search(matches[2]);
|
||||||
const filtered = Array.from(found).map(command => commands.get(command));
|
const filtered = Array.from(found).map(command => commands.get(command));
|
||||||
this.commandsHelper.render(filtered);
|
this.commandsHelper.render(filtered);
|
||||||
console.log('found commands', found, filtered);
|
// console.log('found commands', found, filtered);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else { // emoji
|
} else { // emoji
|
||||||
if(value.match(/^\s*:(.+):\s*$/)) {
|
if(!value.match(/^\s*:(.+):\s*$/)) {
|
||||||
this.emojiHelper.toggle(true);
|
foundHelper = this.emojiHelper;
|
||||||
return;
|
this.appEmojiManager.getBothEmojiKeywords().then(() => {
|
||||||
|
const q = matches[2].replace(/^:/, '');
|
||||||
|
const emojis = this.appEmojiManager.searchEmojis(q);
|
||||||
|
this.emojiHelper.render(emojis, matches[2][0] !== ':');
|
||||||
|
//console.log(emojis);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.appEmojiManager.getBothEmojiKeywords().then(() => {
|
|
||||||
const q = matches[2].replace(/^:/, '');
|
|
||||||
const emojis = this.appEmojiManager.searchEmojis(q);
|
|
||||||
this.emojiHelper.render(emojis, matches[2][0] !== ':');
|
|
||||||
//console.log(emojis);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.autocompleteHelperController.hideOtherHelpers(foundHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onBtnSendClick = (e: Event) => {
|
private onBtnSendClick = (e: Event) => {
|
||||||
|
@ -37,7 +37,7 @@ export default class ChatSearch {
|
|||||||
private selectedIndex = 0;
|
private selectedIndex = 0;
|
||||||
private setPeerPromise: Promise<any>;
|
private setPeerPromise: Promise<any>;
|
||||||
|
|
||||||
constructor(private topbar: ChatTopbar, private chat: Chat) {
|
constructor(private topbar: ChatTopbar, private chat: Chat, private query?: string) {
|
||||||
this.element = document.createElement('div');
|
this.element = document.createElement('div');
|
||||||
this.element.classList.add('sidebar-header', 'chat-search', 'chatlist-container');
|
this.element.classList.add('sidebar-header', 'chat-search', 'chatlist-container');
|
||||||
|
|
||||||
@ -128,6 +128,8 @@ export default class ChatSearch {
|
|||||||
this.topbar.container.parentElement.append(this.element);
|
this.topbar.container.parentElement.append(this.element);
|
||||||
|
|
||||||
this.inputSearch.input.focus();
|
this.inputSearch.input.focus();
|
||||||
|
|
||||||
|
query && (this.inputSearch.inputField.value = query);
|
||||||
}
|
}
|
||||||
|
|
||||||
onDateClick = (e: MouseEvent) => {
|
onDateClick = (e: MouseEvent) => {
|
||||||
|
@ -12,17 +12,23 @@ import { SuperStickerRenderer } from "../emoticonsDropdown/tabs/stickers";
|
|||||||
import LazyLoadQueue from "../lazyLoadQueue";
|
import LazyLoadQueue from "../lazyLoadQueue";
|
||||||
import Scrollable from "../scrollable";
|
import Scrollable from "../scrollable";
|
||||||
import AutocompleteHelper from "./autocompleteHelper";
|
import AutocompleteHelper from "./autocompleteHelper";
|
||||||
|
import AutocompleteHelperController from "./autocompleteHelperController";
|
||||||
|
|
||||||
export default class StickersHelper extends AutocompleteHelper {
|
export default class StickersHelper extends AutocompleteHelper {
|
||||||
private scrollable: Scrollable;
|
private scrollable: Scrollable;
|
||||||
private superStickerRenderer: SuperStickerRenderer;
|
private superStickerRenderer: SuperStickerRenderer;
|
||||||
private lazyLoadQueue: LazyLoadQueue;
|
private lazyLoadQueue: LazyLoadQueue;
|
||||||
private lastEmoticon = '';
|
|
||||||
|
|
||||||
constructor(appendTo: HTMLElement) {
|
constructor(appendTo: HTMLElement, controller: AutocompleteHelperController) {
|
||||||
super(appendTo, 'xy', (target) => {
|
super({
|
||||||
EmoticonsDropdown.onMediaClick({target}, true);
|
appendTo,
|
||||||
}, 'ArrowUp');
|
controller,
|
||||||
|
listType: 'xy',
|
||||||
|
onSelect: (target) => {
|
||||||
|
EmoticonsDropdown.onMediaClick({target}, true);
|
||||||
|
},
|
||||||
|
waitForKey: 'ArrowUp'
|
||||||
|
});
|
||||||
|
|
||||||
this.container.classList.add('stickers-helper');
|
this.container.classList.add('stickers-helper');
|
||||||
|
|
||||||
@ -34,26 +40,15 @@ export default class StickersHelper extends AutocompleteHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public checkEmoticon(emoticon: string) {
|
public checkEmoticon(emoticon: string) {
|
||||||
if(this.lastEmoticon === emoticon) return;
|
const middleware = this.controller.getMiddleware();
|
||||||
|
|
||||||
if(this.lastEmoticon && !emoticon) {
|
|
||||||
if(this.container) {
|
|
||||||
this.toggle(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastEmoticon = emoticon;
|
|
||||||
if(this.lazyLoadQueue) {
|
if(this.lazyLoadQueue) {
|
||||||
this.lazyLoadQueue.clear();
|
this.lazyLoadQueue.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!emoticon) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
appStickersManager.getStickersByEmoticon(emoticon)
|
appStickersManager.getStickersByEmoticon(emoticon)
|
||||||
.then((stickers) => {
|
.then((stickers) => {
|
||||||
if(this.lastEmoticon !== emoticon) {
|
if(!middleware()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,14 +19,12 @@ import ButtonIcon from "../buttonIcon";
|
|||||||
import ButtonMenuToggle from "../buttonMenuToggle";
|
import ButtonMenuToggle from "../buttonMenuToggle";
|
||||||
import ChatAudio from "./audio";
|
import ChatAudio from "./audio";
|
||||||
import ChatPinnedMessage from "./pinnedMessage";
|
import ChatPinnedMessage from "./pinnedMessage";
|
||||||
import ChatSearch from "./search";
|
|
||||||
import { ButtonMenuItemOptions } from "../buttonMenu";
|
import { ButtonMenuItemOptions } from "../buttonMenu";
|
||||||
import ListenerSetter from "../../helpers/listenerSetter";
|
import ListenerSetter from "../../helpers/listenerSetter";
|
||||||
import appStateManager from "../../lib/appManagers/appStateManager";
|
import appStateManager from "../../lib/appManagers/appStateManager";
|
||||||
import PopupDeleteDialog from "../popups/deleteDialog";
|
import PopupDeleteDialog from "../popups/deleteDialog";
|
||||||
import appNavigationController from "../appNavigationController";
|
import appNavigationController from "../appNavigationController";
|
||||||
import { LEFT_COLUMN_ACTIVE_CLASSNAME } from "../sidebarLeft";
|
import { LEFT_COLUMN_ACTIVE_CLASSNAME } from "../sidebarLeft";
|
||||||
import AppPrivateSearchTab from "../sidebarRight/tabs/search";
|
|
||||||
import PeerTitle from "../peerTitle";
|
import PeerTitle from "../peerTitle";
|
||||||
import { i18n } from "../../lib/langPack";
|
import { i18n } from "../../lib/langPack";
|
||||||
import findUpClassName from "../../helpers/dom/findUpClassName";
|
import findUpClassName from "../../helpers/dom/findUpClassName";
|
||||||
@ -35,30 +33,30 @@ import { cancelEvent } from "../../helpers/dom/cancelEvent";
|
|||||||
import { attachClickEvent } from "../../helpers/dom/clickEvent";
|
import { attachClickEvent } from "../../helpers/dom/clickEvent";
|
||||||
|
|
||||||
export default class ChatTopbar {
|
export default class ChatTopbar {
|
||||||
container: HTMLDivElement;
|
public container: HTMLDivElement;
|
||||||
btnBack: HTMLButtonElement;
|
private btnBack: HTMLButtonElement;
|
||||||
chatInfo: HTMLDivElement;
|
private chatInfo: HTMLDivElement;
|
||||||
avatarElement: AvatarElement;
|
private avatarElement: AvatarElement;
|
||||||
title: HTMLDivElement;
|
private title: HTMLDivElement;
|
||||||
subtitle: HTMLDivElement;
|
private subtitle: HTMLDivElement;
|
||||||
chatUtils: HTMLDivElement;
|
private chatUtils: HTMLDivElement;
|
||||||
btnJoin: HTMLButtonElement;
|
private btnJoin: HTMLButtonElement;
|
||||||
btnPinned: HTMLButtonElement;
|
private btnPinned: HTMLButtonElement;
|
||||||
btnMute: HTMLButtonElement;
|
private btnMute: HTMLButtonElement;
|
||||||
btnSearch: HTMLButtonElement;
|
private btnSearch: HTMLButtonElement;
|
||||||
btnMore: HTMLButtonElement;
|
private btnMore: HTMLButtonElement;
|
||||||
|
|
||||||
public chatAudio: ChatAudio;
|
private chatAudio: ChatAudio;
|
||||||
public pinnedMessage: ChatPinnedMessage;
|
public pinnedMessage: ChatPinnedMessage;
|
||||||
|
|
||||||
private setUtilsRAF: number;
|
private setUtilsRAF: number;
|
||||||
public peerId: number;
|
public peerId: number;
|
||||||
public wasPeerId: number;
|
private wasPeerId: number;
|
||||||
private setPeerStatusInterval: number;
|
private setPeerStatusInterval: number;
|
||||||
|
|
||||||
public listenerSetter: ListenerSetter;
|
public listenerSetter: ListenerSetter;
|
||||||
|
|
||||||
public menuButtons: (ButtonMenuItemOptions & {verify: () => boolean})[] = [];
|
private menuButtons: (ButtonMenuItemOptions & {verify: () => boolean})[] = [];
|
||||||
|
|
||||||
constructor(private chat: Chat, private appSidebarRight: AppSidebarRight, private appMessagesManager: AppMessagesManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager, private appNotificationsManager: AppNotificationsManager) {
|
constructor(private chat: Chat, private appSidebarRight: AppSidebarRight, private appMessagesManager: AppMessagesManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager, private appNotificationsManager: AppNotificationsManager) {
|
||||||
this.listenerSetter = new ListenerSetter();
|
this.listenerSetter = new ListenerSetter();
|
||||||
@ -189,7 +187,7 @@ export default class ChatTopbar {
|
|||||||
icon: 'search',
|
icon: 'search',
|
||||||
text: 'Search',
|
text: 'Search',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
new ChatSearch(this, this.chat);
|
this.chat.initSearch()
|
||||||
},
|
},
|
||||||
verify: () => mediaSizes.isMobile
|
verify: () => mediaSizes.isMobile
|
||||||
}, /* {
|
}, /* {
|
||||||
@ -237,14 +235,7 @@ export default class ChatTopbar {
|
|||||||
this.btnSearch = ButtonIcon('search');
|
this.btnSearch = ButtonIcon('search');
|
||||||
attachClickEvent(this.btnSearch, (e) => {
|
attachClickEvent(this.btnSearch, (e) => {
|
||||||
cancelEvent(e);
|
cancelEvent(e);
|
||||||
if(this.peerId) {
|
this.chat.initSearch();
|
||||||
let tab = this.appSidebarRight.getTab(AppPrivateSearchTab);
|
|
||||||
if(!tab) {
|
|
||||||
tab = new AppPrivateSearchTab(this.appSidebarRight);
|
|
||||||
}
|
|
||||||
|
|
||||||
tab.open(this.peerId, this.chat.threadId, this.chat.bubbles.onDatePick);
|
|
||||||
}
|
|
||||||
}, {listenerSetter: this.listenerSetter});
|
}, {listenerSetter: this.listenerSetter});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,13 +42,15 @@ export default class AppPrivateSearchTab extends SliderSuperTab {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
open(peerId: number, threadId?: number, onDatePick?: AppPrivateSearchTab['onDatePick']) {
|
open(peerId: number, threadId?: number, onDatePick?: AppPrivateSearchTab['onDatePick'], query?: string) {
|
||||||
const ret = super.open();
|
const ret = super.open();
|
||||||
if(this.init) {
|
if(this.init) {
|
||||||
this.init();
|
this.init();
|
||||||
this.init = null;
|
this.init = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query && (this.inputSearch.inputField.value = query);
|
||||||
|
|
||||||
if(this.peerId !== 0) {
|
if(this.peerId !== 0) {
|
||||||
this.appSearch.beginSearch(this.peerId, this.threadId);
|
this.appSearch.beginSearch(this.peerId, this.threadId);
|
||||||
return ret;
|
return ret;
|
||||||
@ -64,7 +66,7 @@ export default class AppPrivateSearchTab extends SliderSuperTab {
|
|||||||
new PopupDatePicker(new Date(), this.onDatePick).show();
|
new PopupDatePicker(new Date(), this.onDatePick).show();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
appSidebarRight.toggleSidebar(true);
|
appSidebarRight.toggleSidebar(true);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
3
src/layer.d.ts
vendored
3
src/layer.d.ts
vendored
@ -4049,7 +4049,8 @@ export namespace MessageEntity {
|
|||||||
export type messageEntityBotCommand = {
|
export type messageEntityBotCommand = {
|
||||||
_: 'messageEntityBotCommand',
|
_: 'messageEntityBotCommand',
|
||||||
offset: number,
|
offset: number,
|
||||||
length: number
|
length: number,
|
||||||
|
unsafe?: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
export type messageEntityUrl = {
|
export type messageEntityUrl = {
|
||||||
|
@ -243,12 +243,47 @@ export class AppImManager {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
(window as any).execBotCommand = (element: HTMLAnchorElement, e: Event) => {
|
||||||
|
cancelEvent(null);
|
||||||
|
|
||||||
|
const href = element.href;
|
||||||
|
const params = this.parseUriParams(href);
|
||||||
|
if(!params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {command, bot} = params;
|
||||||
|
|
||||||
|
/* const promise = bot ? this.openUsername(bot).then(() => this.chat.peerId) : Promise.resolve(this.chat.peerId);
|
||||||
|
promise.then(peerId => {
|
||||||
|
appMessagesManager.sendText(peerId, '/' + command);
|
||||||
|
}); */
|
||||||
|
|
||||||
|
appMessagesManager.sendText(this.chat.peerId, '/' + command + (bot ? '@' + bot : ''));
|
||||||
|
|
||||||
|
//console.log(command, bot);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
(window as any).searchByHashtag = (element: HTMLAnchorElement, e: Event) => {
|
||||||
|
cancelEvent(null);
|
||||||
|
|
||||||
|
const href = element.href;
|
||||||
|
const params = this.parseUriParams(href);
|
||||||
|
if(!params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {hashtag} = params;
|
||||||
|
this.chat.initSearch('#' + hashtag + ' ');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private onHashChange = () => {
|
private parseUriParams(uri: string, splitted = uri.split('?')) {
|
||||||
const hash = location.hash;
|
|
||||||
const splitted = hash.split('?');
|
|
||||||
|
|
||||||
if(!splitted[1]) {
|
if(!splitted[1]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -258,6 +293,15 @@ export class AppImManager {
|
|||||||
params[item.split('=')[0]] = decodeURIComponent(item.split('=')[1]);
|
params[item.split('=')[0]] = decodeURIComponent(item.split('=')[1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onHashChange = () => {
|
||||||
|
const hash = location.hash;
|
||||||
|
const splitted = hash.split('?');
|
||||||
|
|
||||||
|
const params = this.parseUriParams(hash, splitted);
|
||||||
|
|
||||||
this.log('hashchange', hash, splitted[0], params);
|
this.log('hashchange', hash, splitted[0], params);
|
||||||
|
|
||||||
switch(splitted[0]) {
|
switch(splitted[0]) {
|
||||||
|
@ -426,6 +426,7 @@ export class AppMessagesManager {
|
|||||||
let entities = options.entities || [];
|
let entities = options.entities || [];
|
||||||
if(!options.viaBotId) {
|
if(!options.viaBotId) {
|
||||||
text = RichTextProcessor.parseMarkdown(text, entities);
|
text = RichTextProcessor.parseMarkdown(text, entities);
|
||||||
|
entities = RichTextProcessor.mergeEntities(entities, RichTextProcessor.parseEntities(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
let sendEntites = this.getInputEntities(entities);
|
let sendEntites = this.getInputEntities(entities);
|
||||||
@ -2549,6 +2550,7 @@ export class AppMessagesManager {
|
|||||||
let entities = RichTextProcessor.parseEntities(text.replace(/\n/g, ' '));
|
let entities = RichTextProcessor.parseEntities(text.replace(/\n/g, ' '));
|
||||||
|
|
||||||
if(highlightWord) {
|
if(highlightWord) {
|
||||||
|
highlightWord = highlightWord.trim();
|
||||||
if(!entities) entities = [];
|
if(!entities) entities = [];
|
||||||
let found = false;
|
let found = false;
|
||||||
let match: any;
|
let match: any;
|
||||||
|
@ -352,6 +352,10 @@ export class AppUsersManager {
|
|||||||
changedTitle = true;
|
changedTitle = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* if(user.pFlags.bot && user.bot_info_version !== oldUser.bot_info_version) {
|
||||||
|
|
||||||
|
} */
|
||||||
|
|
||||||
safeReplaceObject(oldUser, user);
|
safeReplaceObject(oldUser, user);
|
||||||
rootScope.broadcast('user_update', userId);
|
rootScope.broadcast('user_update', userId);
|
||||||
}
|
}
|
||||||
|
@ -214,11 +214,12 @@ namespace RichTextProcessor {
|
|||||||
offset: matchIndex + (match[10] ? match[10].length : 0),
|
offset: matchIndex + (match[10] ? match[10].length : 0),
|
||||||
length: match[11].length
|
length: match[11].length
|
||||||
});
|
});
|
||||||
} else if(match[12]) { // Bot command
|
} else if(match[13]) { // Bot command
|
||||||
entities.push({
|
entities.push({
|
||||||
_: 'messageEntityBotCommand',
|
_: 'messageEntityBotCommand',
|
||||||
offset: matchIndex + (match[11] ? match[11].length : 0),
|
offset: matchIndex + (match[11] ? match[11].length : 0),
|
||||||
length: 1 + match[12].length + (match[13] ? 1 + match[13].length : 0)
|
length: 1 + match[13].length + (match[14] ? 1 + match[14].length : 0),
|
||||||
|
unsafe: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,7 +418,7 @@ namespace RichTextProcessor {
|
|||||||
|
|
||||||
contextHashtag?: string
|
contextHashtag?: string
|
||||||
}> = {}) {
|
}> = {}) {
|
||||||
if(!text || !text.length) {
|
if(!text) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,7 +513,7 @@ namespace RichTextProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'messageEntityBotCommand': {
|
case 'messageEntityBotCommand': {
|
||||||
if(!(options.noLinks || options.noCommands || contextExternal)) {
|
if(!(options.noLinks || options.noCommands || contextExternal) && !entity.unsafe) {
|
||||||
const entityText = text.substr(entity.offset, entity.length);
|
const entityText = text.substr(entity.offset, entity.length);
|
||||||
let command = entityText.substr(1);
|
let command = entityText.substr(1);
|
||||||
let bot: string | boolean;
|
let bot: string | boolean;
|
||||||
@ -524,7 +525,7 @@ namespace RichTextProcessor {
|
|||||||
bot = options.fromBot;
|
bot = options.fromBot;
|
||||||
}
|
}
|
||||||
|
|
||||||
insertPart(entity, `<a href="${encodeEntities('tg://bot_command?command=' + encodeURIComponent(command) + (bot ? '&bot=' + encodeURIComponent(bot) : ''))}">`, `</a>`);
|
insertPart(entity, `<a href="${encodeEntities('tg://bot_command?command=' + encodeURIComponent(command) + (bot ? '&bot=' + encodeURIComponent(bot) : ''))}" onclick="execBotCommand(this)">`, `</a>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -617,7 +618,7 @@ namespace RichTextProcessor {
|
|||||||
if(contextUrl) {
|
if(contextUrl) {
|
||||||
const entityText = text.substr(entity.offset, entity.length);
|
const entityText = text.substr(entity.offset, entity.length);
|
||||||
const hashtag = entityText.substr(1);
|
const hashtag = entityText.substr(1);
|
||||||
insertPart(entity, `<a class="anchor-hashtag" href="${contextUrl.replace('{1}', encodeURIComponent(hashtag))}"${contextExternal ? ' target="_blank" rel="noopener noreferrer"' : ''}>`, '</a>');
|
insertPart(entity, `<a class="anchor-hashtag" href="${contextUrl.replace('{1}', encodeURIComponent(hashtag))}"${contextExternal ? ' target="_blank" rel="noopener noreferrer"' : ' onclick="searchByHashtag(this)"'}>`, '</a>');
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -101,6 +101,11 @@
|
|||||||
{"name": "length", "type": "number"}
|
{"name": "length", "type": "number"}
|
||||||
],
|
],
|
||||||
"type": "MessageEntity"
|
"type": "MessageEntity"
|
||||||
|
}, {
|
||||||
|
"predicate": "messageEntityBotCommand",
|
||||||
|
"params": [
|
||||||
|
{"name": "unsafe", "type": "boolean"}
|
||||||
|
]
|
||||||
}, {
|
}, {
|
||||||
"predicate": "user",
|
"predicate": "user",
|
||||||
"params": [
|
"params": [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user