From a490f29c40394dfb9ec882307f5a55ff3ca4b352 Mon Sep 17 00:00:00 2001 From: morethanwords Date: Sat, 15 Jan 2022 02:01:50 +0400 Subject: [PATCH] Added 'start bot' button --- src/components/chat/bubbles.ts | 10 ++- src/components/chat/chat.ts | 24 +++++-- src/components/chat/input.ts | 73 ++++++++++++++++++-- src/components/sidebarLeft/index.ts | 4 +- src/helpers/dom/toggleDisability.ts | 2 +- src/helpers/object.ts | 2 +- src/layer.d.ts | 3 +- src/lib/appManagers/appImManager.ts | 30 ++++++-- src/lib/appManagers/appMessagesManager.ts | 29 ++++++-- src/lib/mtproto/mtproto_config.ts | 1 + src/scripts/in/schema_additional_params.json | 3 +- 11 files changed, 153 insertions(+), 28 deletions(-) diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index 96266faa..20298870 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -1953,7 +1953,7 @@ export default class ChatBubbles { this.isTopPaddingSet = false; } - public setPeer(peerId: PeerId, lastMsgId?: number): {cached?: boolean, promise: Chat['setPeerPromise']} { + public setPeer(peerId: PeerId, lastMsgId?: number, startParam?: string): {cached?: boolean, promise: Chat['setPeerPromise']} { //console.time('appImManager setPeer'); //console.time('appImManager setPeer pre promise'); ////console.time('appImManager: pre render start'); @@ -2019,6 +2019,10 @@ export default class ChatBubbles { scrollable.scrollTop = scrollable.scrollHeight; this.chat.dispatchEvent('setPeer', lastMsgId, true); } + + if(startParam !== undefined) { + this.chat.input.setStartParam(startParam); + } return null; } @@ -2095,7 +2099,7 @@ export default class ChatBubbles { if(!samePeer) { scrollable.container.textContent = ''; //oldChatInner.remove(); - this.chat.finishPeerChange(isTarget, isJump, lastMsgId); + this.chat.finishPeerChange(isTarget, isJump, lastMsgId, startParam); this.preloader.attach(this.bubblesContainer); } } @@ -2110,7 +2114,7 @@ export default class ChatBubbles { if(cached) { if(!samePeer) { - this.chat.finishPeerChange(isTarget, isJump, lastMsgId); // * костыль + this.chat.finishPeerChange(isTarget, isJump, lastMsgId, startParam); // * костыль } } else { this.preloader.detach(); diff --git a/src/components/chat/chat.ts b/src/components/chat/chat.ts index 8b7e3c02..9bbedef6 100644 --- a/src/components/chat/chat.ts +++ b/src/components/chat/chat.ts @@ -34,7 +34,7 @@ import ChatContextMenu from "./contextMenu"; import ChatInput from "./input"; import ChatSelection from "./selection"; import ChatTopbar from "./topbar"; -import { NULL_PEER_ID, REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config"; +import { BOT_START_PARAM, NULL_PEER_ID, REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config"; import SetTransition from "../singleTransition"; import { fastRaf } from "../../helpers/schedulers"; import AppPrivateSearchTab from "../sidebarRight/tabs/search"; @@ -109,6 +109,8 @@ export default class Chat extends EventListenerBase<{ this.log = logger('CHAT', LogTypes.Log | LogTypes.Warn | LogTypes.Debug | LogTypes.Error); //this.log.error('Chat construction'); + this.peerId = NULL_PEER_ID; + this.container.append(this.backgroundEl); this.appImManager.chatsContainer.append(this.container); } @@ -255,7 +257,7 @@ export default class Chat extends EventListenerBase<{ this.selection.cleanup(); } - public setPeer(peerId: PeerId, lastMsgId?: number) { + public setPeer(peerId: PeerId, lastMsgId?: number, startParam?: string) { if(!peerId) { this.inited = undefined; } else if(!this.inited) { @@ -270,7 +272,7 @@ export default class Chat extends EventListenerBase<{ const samePeer = this.peerId === peerId; if(!samePeer) { rootScope.dispatchEvent('peer_changing', this); - this.peerId = peerId; + this.peerId = peerId || NULL_PEER_ID; } else if(this.setPeerPromise) { return; } @@ -306,7 +308,11 @@ export default class Chat extends EventListenerBase<{ this.peerChanged = samePeer; - const result = this.bubbles.setPeer(peerId, lastMsgId); + if(startParam === undefined && this.isStartButtonNeeded()) { + startParam = BOT_START_PARAM; + } + + const result = this.bubbles.setPeer(peerId, lastMsgId, startParam); if(!result) { return; } @@ -361,7 +367,7 @@ export default class Chat extends EventListenerBase<{ return this.setPeer(this.peerId, messageId); } - public finishPeerChange(isTarget: boolean, isJump: boolean, lastMsgId: number) { + public finishPeerChange(isTarget: boolean, isJump: boolean, lastMsgId: number, startParam?: string) { if(this.peerChanged) return; let peerId = this.peerId; @@ -372,7 +378,7 @@ export default class Chat extends EventListenerBase<{ this.topbar.setPeer(peerId); this.topbar.finishPeerChange(isTarget, isJump, lastMsgId); this.bubbles.finishPeerChange(); - this.input.finishPeerChange(); + this.input.finishPeerChange(startParam); appSidebarRight.sharedMediaTab.fillProfileElements(); @@ -421,4 +427,10 @@ export default class Chat extends EventListenerBase<{ public canSend(action?: ChatRights) { return this.appMessagesManager.canSendToPeer(this.peerId, this.threadId, action); } + + public isStartButtonNeeded() { + return this.appPeersManager.isBot(this.peerId) && + !this.appMessagesManager.getDialogOnly(this.peerId) && + !this.appMessagesManager.getHistoryStorage(this.peerId).history.length; + } } diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index c2ac2972..138861a0 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -80,12 +80,13 @@ import { copy } from '../../helpers/object'; import PopupPeer from '../popups/peer'; import MEDIA_MIME_TYPES_SUPPORTED from '../../environment/mediaMimeTypesSupport'; import appMediaPlaybackController from '../appMediaPlaybackController'; -import { NULL_PEER_ID } from '../../lib/mtproto/mtproto_config'; +import { BOT_START_PARAM, NULL_PEER_ID } from '../../lib/mtproto/mtproto_config'; import setCaretAt from '../../helpers/dom/setCaretAt'; import CheckboxField from '../checkboxField'; import DropdownHover from '../../helpers/dropdownHover'; import RadioForm from '../radioForm'; import findUpTag from '../../helpers/dom/findUpTag'; +import toggleDisability from '../../helpers/dom/toggleDisability'; const RECORD_MIN_TIME = 500; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; @@ -205,6 +206,7 @@ export default class ChatInput { private fakeSelectionWrapper: HTMLDivElement; private fakeWrapperTo: HTMLElement; + private toggleBotStartBtnDisability: () => void; // private activeContainer: HTMLElement; @@ -678,6 +680,20 @@ export default class ChatInput { if(this.replyToMsgId && msgs.has(this.replyToMsgId)) { this.clearHelper('reply'); } + + /* if(this.chat.isStartButtonNeeded()) { + this.setStartParam(BOT_START_PARAM); + } */ + } + }); + + this.listenerSetter.add(rootScope)('dialogs_multiupdate', (dialogs) => { + if(dialogs[this.chat.peerId]) { + if(this.startParam === BOT_START_PARAM) { + this.setStartParam(); + } else { // updateNewMessage comes earlier than dialog appers + this.center(true); + } } }); } @@ -794,6 +810,27 @@ export default class ChatInput { this.botStartBtn = Button('btn-primary btn-transparent text-bold chat-input-control-button'); this.botStartBtn.append(i18n('BotStart')); + attachClickEvent(this.botStartBtn, () => { + const {startParam} = this; + if(startParam === undefined) { + return; + } + + const toggle = this.toggleBotStartBtnDisability = toggleDisability([this.botStartBtn], true); + const peerId = this.chat.peerId; + const middleware = this.chat.bubbles.getMiddleware(() => { + return this.chat.peerId === peerId && this.startParam === startParam && this.toggleBotStartBtnDisability === toggle; + }); + + this.appMessagesManager.startBot(peerId.toUserId(), undefined, startParam).then(() => { + if(middleware()) { + toggle(); + this.toggleBotStartBtnDisability = undefined; + this.setStartParam(); + } + }); + }, {listenerSetter: this.listenerSetter}); + this.controlContainer.append(this.botStartBtn); } @@ -824,6 +861,10 @@ export default class ChatInput { return; } + if(neededFakeContainer === this.fakeWrapperTo) { + return; + } + /* if(neededFakeContainer === this.botStartContainer && this.fakeWrapperTo === this.fakeSelectionWrapper) { this.inputContainer.classList.remove('is-centering'); void this.rowsWrapper.offsetLeft; // reflow @@ -882,10 +923,23 @@ export default class ChatInput { }; } + public setStartParam(startParam?: string) { + if(this.startParam === startParam) { + return; + } + + this.startParam = startParam; + this.center(true); + } + public getNeededFakeContainer() { if(this.chat.selection.isSelecting) { return this.fakeSelectionWrapper; - } else if(this.startParam || !this.chat.canSend() || this.chat.type === 'pinned') { + } else if(this.startParam !== undefined || + !this.chat.canSend() || + this.chat.type === 'pinned' || + this.chat.isStartButtonNeeded() + ) { return this.controlContainer; } } @@ -1011,6 +1065,12 @@ export default class ChatInput { cancelSelection(); this.lastTimeType = 0; + this.startParam = undefined; + + if(this.toggleBotStartBtnDisability) { + this.toggleBotStartBtnDisability(); + this.toggleBotStartBtnDisability = undefined; + } if(this.messageInput) { this.clearInput(); @@ -1062,7 +1122,7 @@ export default class ChatInput { return true; } - public finishPeerChange() { + public finishPeerChange(startParam?: string) { const peerId = this.chat.peerId; const {forwardElements, btnScheduled, replyKeyboard, sendMenu, goDownBtn, chatInput} = this; @@ -1110,7 +1170,12 @@ export default class ChatInput { this.pinnedControlBtn.append(i18n(this.appPeersManager.canPinMessage(this.chat.peerId) ? 'Chat.Input.UnpinAll' : 'Chat.Pinned.DontShow')); } - this.center(); + // * testing + // this.startParam = this.appPeersManager.isBot(peerId) ? '123' : undefined; + + this.startParam = startParam; + + this.center(false); } public updateMessageInput() { diff --git a/src/components/sidebarLeft/index.ts b/src/components/sidebarLeft/index.ts index c8392359..6a4cf47c 100644 --- a/src/components/sidebarLeft/index.ts +++ b/src/components/sidebarLeft/index.ts @@ -159,7 +159,9 @@ export class AppSidebarLeft extends SidebarSlider { icon: 'help', text: 'TelegramFeatures', onClick: () => { - appImManager.openUsername('TelegramTips'); + appImManager.openUsername({ + userName: 'TelegramTips' + }); } }, { icon: 'bug', diff --git a/src/helpers/dom/toggleDisability.ts b/src/helpers/dom/toggleDisability.ts index f15ea649..3b20bab1 100644 --- a/src/helpers/dom/toggleDisability.ts +++ b/src/helpers/dom/toggleDisability.ts @@ -4,7 +4,7 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ -export default function toggleDisability(elements: HTMLElement[], disable: boolean) { +export default function toggleDisability(elements: HTMLElement[], disable: boolean): () => void { if(disable) { elements.forEach(el => el.setAttribute('disabled', 'true')); } else { diff --git a/src/helpers/object.ts b/src/helpers/object.ts index 9e9b66d1..8ea8b8a2 100644 --- a/src/helpers/object.ts +++ b/src/helpers/object.ts @@ -30,7 +30,7 @@ export function copy(obj: T): T { //lastly, handle objects // @ts-ignore let clonedObj = new obj.constructor(); - for(var prop in obj){ + for(var prop in obj) { if(obj.hasOwnProperty(prop)) { clonedObj[prop] = copy(obj[prop]); } diff --git a/src/layer.d.ts b/src/layer.d.ts index 2d1b3bca..064dbbf9 100644 --- a/src/layer.d.ts +++ b/src/layer.d.ts @@ -885,7 +885,8 @@ export namespace Message { totalEntities?: MessageEntity[], reply_to_mid?: number, savedFrom?: string, - sponsoredMessage?: SponsoredMessage.sponsoredMessage + sponsoredMessage?: SponsoredMessage.sponsoredMessage, + promise?: CancellablePromise }; export type messageService = { diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index b30cdac4..6df5b162 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -604,7 +604,12 @@ export class AppImManager { const postId = link.post ? appMessagesIdsManager.generateMessageId(+link.post) : undefined; const commentId = link.comment ? appMessagesIdsManager.generateMessageId(+link.comment) : undefined; - this.openUsername(link.domain, postId, undefined, commentId); + this.openUsername({ + userName: link.domain, + lastMsgId: postId, + commentId, + startParam: link.start + }); break; } @@ -740,7 +745,10 @@ export class AppImManager { switch(p[0]) { case '@': { - this.openUsername(p, postId); + this.openUsername({ + userName: p, + lastMsgId: postId + }); break; } @@ -759,8 +767,15 @@ export class AppImManager { //location.hash = ''; }; - public openUsername(username: string, lastMsgId?: number, threadId?: number, commentId?: number) { - return appUsersManager.resolveUsername(username).then(peer => { + public openUsername(options: { + userName: string, + lastMsgId?: number, + threadId?: number, + commentId?: number, + startParam?: string + }) { + const {userName, lastMsgId, threadId, commentId, startParam} = options; + return appUsersManager.resolveUsername(userName).then(peer => { const isUser = peer._ === 'user'; const peerId = peer.id.toPeerId(!isUser); @@ -772,7 +787,8 @@ export class AppImManager { return this.setInnerPeer({ peerId, - lastMsgId + lastMsgId, + startParam: startParam }); }, (err) => { if(err.type === 'USERNAME_NOT_OCCUPIED') { @@ -1433,6 +1449,8 @@ export class AppImManager { this.init = null; } + options.peerId ??= NULL_PEER_ID; + const {peerId, lastMsgId} = options; const chat = this.chat; @@ -1476,7 +1494,7 @@ export class AppImManager { } if(peerId || mediaSizes.activeScreen !== ScreenSize.mobile) { - const result = chat.setPeer(peerId, lastMsgId); + const result = chat.setPeer(peerId, lastMsgId, options.startParam); // * wait for cached render const promise = result?.cached ? result.promise : Promise.resolve(); diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index fffa88d7..a726401d 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -448,7 +448,7 @@ export class AppMessagesManager { silent: true }> = {}) { if(!text.trim()) { - return; + return Promise.resolve(); } //this.checkSendOptions(options); @@ -552,7 +552,13 @@ export class AppMessagesManager { //if(is(updates, updates._ === 'updateShortSentMessage')) { if(updates._ === 'updateShortSentMessage') { //assumeType(updates); + + // * fix copying object with promise + const promise = message.promise; + delete message.promise; const newMessage = copy(message); + message.promise = promise; + newMessage.date = updates.date; newMessage.id = updates.id; newMessage.media = updates.media; @@ -596,8 +602,10 @@ export class AppMessagesManager { // $timeout(function () { // ApiUpdatesManager.processUpdateMessage(upd) // }, 5000) - }, (/* error: any */) => { + message.promise.resolve(); + }, (error: any) => { toggleError(true); + message.promise.reject(error); }).finally(() => { if(this.pendingAfterMsgs[peerId] === sentRequestOptions) { delete this.pendingAfterMsgs[peerId]; @@ -610,6 +618,8 @@ export class AppMessagesManager { threadId: options.threadId, clearDraft: options.clearDraft }); + + return message.promise; } public sendFile(peerId: PeerId, file: File | Blob | MyDocument, options: Partial<{ @@ -1027,8 +1037,11 @@ export class AppMessagesManager { } toggleError(true); + throw error; }); }); + + sentDeferred.then(message.promise.resolve, message.promise.reject); } return {message, promise: sentDeferred}; @@ -1120,6 +1133,7 @@ export class AppMessagesManager { const invoke = (multiMedia: InputSingleMedia[]) => { this.setTyping(peerId, {_: 'sendMessageCancelAction'}); + const deferred = deferredPromise(); this.sendSmthLazyLoadQueue.push({ load: () => { return apiManager.invokeApi('messages.sendMultiMedia', { @@ -1131,11 +1145,15 @@ export class AppMessagesManager { clear_draft: options.clearDraft }).then((updates) => { apiUpdatesManager.processUpdateMessage(updates); + deferred.resolve(); }, (error) => { messages.forEach(message => toggleError(message, true)); + deferred.reject(error); }); } }); + + return deferred; }; const promises: Promise[] = messages.map((message) => { @@ -1181,8 +1199,8 @@ export class AppMessagesManager { }); }); - Promise.all(promises).then(inputs => { - invoke(inputs.filter(Boolean)); + return Promise.all(promises).then(inputs => { + return invoke(inputs.filter(Boolean)); }); } @@ -1362,6 +1380,8 @@ export class AppMessagesManager { threadId: options.threadId, clearDraft: options.clearDraft }); + + return message.promise; } /* private checkSendOptions(options: Partial<{ @@ -1472,6 +1492,7 @@ export class AppMessagesManager { replies: this.generateReplies(peerId), views: isBroadcast && 1, pending: true, + promise: options.groupId === undefined ? deferredPromise() : undefined }; return message; diff --git a/src/lib/mtproto/mtproto_config.ts b/src/lib/mtproto/mtproto_config.ts index 2c8d028c..5fe8023d 100644 --- a/src/lib/mtproto/mtproto_config.ts +++ b/src/lib/mtproto/mtproto_config.ts @@ -14,3 +14,4 @@ export const NULL_PEER_ID: PeerId = 0; export const REPLIES_PEER_ID: PeerId = 1271266957; export const SERVICE_PEER_ID: PeerId = 777000; export const MUTE_UNTIL = 0x7FFFFFFF; +export const BOT_START_PARAM = ''; diff --git a/src/scripts/in/schema_additional_params.json b/src/scripts/in/schema_additional_params.json index d465387a..e01e4cd2 100644 --- a/src/scripts/in/schema_additional_params.json +++ b/src/scripts/in/schema_additional_params.json @@ -82,7 +82,8 @@ {"name": "savedFrom", "type": "string"}, {"name": "sponsored", "type": "true"}, {"name": "local", "type": "true"}, - {"name": "sponsoredMessage", "type": "SponsoredMessage.sponsoredMessage"} + {"name": "sponsoredMessage", "type": "SponsoredMessage.sponsoredMessage"}, + {"name": "promise", "type": "CancellablePromise"} ] }, { "predicate": "messageService",