Media permissions

Check for rights for forward
This commit is contained in:
Eduard Kuzmenko 2023-02-27 17:11:37 +04:00
parent 5fdffa3b18
commit 820885dbd9
69 changed files with 1614 additions and 543 deletions

View File

@ -245,7 +245,7 @@ export class AppMediaPlaybackController extends EventListenerBase<{
return media; return media;
} }
const doc = getMediaFromMessage(message) as Document.document; const doc = getMediaFromMessage(message, true) as Document.document;
storage.set(mid, media = document.createElement(doc.type === 'round' || doc.type === 'video' ? 'video' : 'audio')); storage.set(mid, media = document.createElement(doc.type === 'round' || doc.type === 'video' ? 'video' : 'audio'));
// const source = document.createElement('source'); // const source = document.createElement('source');
// source.type = doc.type === 'voice' && !opusDecodeController.isPlaySupported() ? 'audio/wav' : doc.mime_type; // source.type = doc.type === 'voice' && !opusDecodeController.isPlaySupported() ? 'audio/wav' : doc.mime_type;
@ -431,7 +431,7 @@ export class AppMediaPlaybackController extends EventListenerBase<{
await onMediaLoad(playingMedia, undefined, false); // have to wait for load, otherwise on macOS won't set await onMediaLoad(playingMedia, undefined, false); // have to wait for load, otherwise on macOS won't set
const doc = getMediaFromMessage(message) as MyDocument; const doc = getMediaFromMessage(message, true) as MyDocument;
const artwork: MediaImage[] = []; const artwork: MediaImage[] = [];
@ -554,7 +554,7 @@ export class AppMediaPlaybackController extends EventListenerBase<{
const message = this.getMessageByMedia(playingMedia); const message = this.getMessageByMedia(playingMedia);
return { return {
doc: getMediaFromMessage(message) as MyDocument, doc: getMediaFromMessage(message, true) as MyDocument,
message, message,
media: playingMedia, media: playingMedia,
playbackParams: this.getPlaybackParams() playbackParams: this.getPlaybackParams()
@ -858,7 +858,7 @@ export class AppMediaPlaybackController extends EventListenerBase<{
} }
private getPlaybackMediaTypeFromMessage(message: Message.message) { private getPlaybackMediaTypeFromMessage(message: Message.message) {
const doc = getMediaFromMessage(message) as MyDocument; const doc = getMediaFromMessage(message, true) as MyDocument;
let mediaType: PlaybackMediaType = 'audio'; let mediaType: PlaybackMediaType = 'audio';
if(doc?.type) { if(doc?.type) {
if(doc.type === 'voice' || doc.type === 'round') { if(doc.type === 'voice' || doc.type === 'round') {

View File

@ -51,7 +51,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
processItem: (item) => { processItem: (item) => {
const isForDocument = this.searchContext.inputFilter._ === 'inputMessagesFilterDocument'; const isForDocument = this.searchContext.inputFilter._ === 'inputMessagesFilterDocument';
const {mid, peerId} = item; const {mid, peerId} = item;
const media: MyPhoto | MyDocument = getMediaFromMessage(item); const media = getMediaFromMessage(item, true);
if(!media) return; if(!media) return;
@ -226,7 +226,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
onDownloadClick = () => { onDownloadClick = () => {
const {message} = this.target; const {message} = this.target;
const media = getMediaFromMessage(message); const media = getMediaFromMessage(message, true);
if(!media) return; if(!media) return;
appDownloadManager.downloadToDisc({media, queueId: appImManager.chat.bubbles.lazyLoadQueue.queueId}); appDownloadManager.downloadToDisc({media, queueId: appImManager.chat.bubbles.lazyLoadQueue.queueId});
}; };
@ -258,7 +258,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
const mid = message.mid; const mid = message.mid;
const fromId = (message as Message.message).fwd_from && !message.fromId ? (message as Message.message).fwd_from.from_name : message.fromId; const fromId = (message as Message.message).fwd_from && !message.fromId ? (message as Message.message).fwd_from.from_name : message.fromId;
const media = getMediaFromMessage(message); const media = getMediaFromMessage(message, true);
const noForwards = await this.managers.appPeersManager.noForwards(message.peerId); const noForwards = await this.managers.appPeersManager.noForwards(message.peerId);
const isServiceMessage = message._ === 'messageService'; const isServiceMessage = message._ === 'messageService';
@ -283,7 +283,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
this.wholeDiv.classList.toggle('no-forwards', cantDownloadMessage); this.wholeDiv.classList.toggle('no-forwards', cantDownloadMessage);
this.setCaption(message); this.setCaption(message);
const promise = super._openMedia(media, message.date, fromId, fromRight, target, reverse, prevTargets, nextTargets, message/* , needLoadMore */); const promise = super._openMedia(media as MyPhoto | MyDocument, message.date, fromId, fromRight, target, reverse, prevTargets, nextTargets, message/* , needLoadMore */);
this.target.mid = mid; this.target.mid = mid;
this.target.peerId = message.peerId; this.target.peerId = message.peerId;
this.target.message = message; this.target.message = message;

View File

@ -42,7 +42,7 @@ import EventListenerBase from '../helpers/eventListenerBase';
import {MyMessage} from '../lib/appManagers/appMessagesManager'; import {MyMessage} from '../lib/appManagers/appMessagesManager';
import {NULL_PEER_ID} from '../lib/mtproto/mtproto_config'; import {NULL_PEER_ID} from '../lib/mtproto/mtproto_config';
import {isFullScreen} from '../helpers/dom/fullScreen'; import {isFullScreen} from '../helpers/dom/fullScreen';
import {attachClickEvent} from '../helpers/dom/clickEvent'; import {attachClickEvent, hasMouseMovedSinceDown} from '../helpers/dom/clickEvent';
import SearchListLoader from '../helpers/searchListLoader'; import SearchListLoader from '../helpers/searchListLoader';
import createVideo from '../helpers/dom/createVideo'; import createVideo from '../helpers/dom/createVideo';
import {AppManagers} from '../lib/appManagers/managers'; import {AppManagers} from '../lib/appManagers/managers';
@ -57,6 +57,7 @@ import {toastNew} from './toast';
import clamp from '../helpers/number/clamp'; import clamp from '../helpers/number/clamp';
import debounce from '../helpers/schedulers/debounce'; import debounce from '../helpers/schedulers/debounce';
import isBetween from '../helpers/number/isBetween'; import isBetween from '../helpers/number/isBetween';
import findUpAsChild from '../helpers/dom/findUpAsChild';
const ZOOM_STEP = 0.5; const ZOOM_STEP = 0.5;
const ZOOM_INITIAL_VALUE = 1; const ZOOM_INITIAL_VALUE = 1;
@ -137,6 +138,7 @@ export default class AppMediaViewerBase<
protected lastDragDelta: {x: number, y: number} = this.transform; protected lastDragDelta: {x: number, y: number} = this.transform;
protected lastGestureTime: number; protected lastGestureTime: number;
protected clampZoomDebounced: ReturnType<typeof debounce<() => void>>; protected clampZoomDebounced: ReturnType<typeof debounce<() => void>>;
ignoreNextClick: boolean;
get target() { get target() {
return this.listLoader.current; return this.listLoader.current;
@ -228,13 +230,25 @@ export default class AppMediaViewerBase<
}, ZOOM_INITIAL_VALUE); }, ZOOM_INITIAL_VALUE);
this.zoomElements.rangeSelector.setListeners(); this.zoomElements.rangeSelector.setListeners();
this.zoomElements.rangeSelector.setHandlers({ this.zoomElements.rangeSelector.setHandlers({
onScrub: this.setZoomValue, onScrub: (value) => {
onMouseUp: () => this.setZoomValue() const add = value - this.transform.scale;
this.addZoom(add);
this.clampZoomDebounced?.clearTimeout();
},
onMouseDown: () => {
this.moversContainer.classList.add('no-transition');
this.zoomElements.rangeSelector.container.classList.remove('with-transition');
},
onMouseUp: () => {
this.moversContainer.classList.remove('no-transition');
this.zoomElements.rangeSelector.container.classList.add('with-transition');
this.setZoomValue();
}
}); });
this.zoomElements.container.append(this.zoomElements.btnOut, this.zoomElements.rangeSelector.container, this.zoomElements.btnIn); this.zoomElements.container.append(this.zoomElements.btnOut, this.zoomElements.rangeSelector.container, this.zoomElements.btnIn);
if(!IS_TOUCH_SUPPORTED && false) { if(!IS_TOUCH_SUPPORTED) {
this.wholeDiv.append(this.zoomElements.container); this.wholeDiv.append(this.zoomElements.container);
} }
@ -349,7 +363,7 @@ export default class AppMediaViewerBase<
this.swipeHandler = new SwipeHandler({ this.swipeHandler = new SwipeHandler({
element: this.wholeDiv, element: this.wholeDiv,
onReset: this.onSwipeReset, onReset: this.onSwipeReset,
onFirstSwipe: this.onSwipeFirst, onFirstSwipe: this.onSwipeFirst as any,
onSwipe: (xDiff, yDiff, e, cancelDrag) => { onSwipe: (xDiff, yDiff, e, cancelDrag) => {
if(isFullScreen()) { if(isFullScreen()) {
return; return;
@ -400,6 +414,7 @@ export default class AppMediaViewerBase<
verifyTouchTarget: (e) => { verifyTouchTarget: (e) => {
// * Fix for seek input // * Fix for seek input
if(isFullScreen() || if(isFullScreen() ||
findUpAsChild(e.target as HTMLElement, this.zoomElements.container) ||
findUpClassName(e.target, 'ckin__controls') || findUpClassName(e.target, 'ckin__controls') ||
findUpClassName(e.target, 'media-viewer-caption') || findUpClassName(e.target, 'media-viewer-caption') ||
(findUpClassName(e.target, 'media-viewer-topbar') && e.type !== 'wheel')) { (findUpClassName(e.target, 'media-viewer-topbar') && e.type !== 'wheel')) {
@ -413,10 +428,13 @@ export default class AppMediaViewerBase<
}); });
} }
protected onSwipeFirst = () => { protected onSwipeFirst = (e: MouseEvent | TouchEvent | WheelEvent) => {
this.lastDragOffset = this.lastDragDelta = {x: 0, y: 0}; this.lastDragOffset = this.lastDragDelta = {x: 0, y: 0};
this.lastTransform = {...this.transform}; this.lastTransform = {...this.transform};
this.moversContainer.classList.add('no-transition'); if(e.type !== 'wheel' || !this.ctrlKeyDown) { // keep transition for real mouse wheel
this.moversContainer.classList.add('no-transition');
this.zoomElements.rangeSelector.container.classList.remove('with-transition');
}
this.isGesturingNow = true; this.isGesturingNow = true;
this.lastGestureTime = Date.now(); this.lastGestureTime = Date.now();
this.clampZoomDebounced.clearTimeout(); this.clampZoomDebounced.clearTimeout();
@ -426,11 +444,16 @@ export default class AppMediaViewerBase<
} }
}; };
protected onSwipeReset = () => { protected onSwipeReset = (e?: Event) => {
// move // move
this.moversContainer.classList.remove('no-transition'); this.moversContainer.classList.remove('no-transition');
this.zoomElements.rangeSelector.container.classList.add('with-transition');
this.clampZoomDebounced.clearTimeout(); this.clampZoomDebounced.clearTimeout();
if(e?.type === 'mouseup' && this.draggingType === 'mousemove') {
this.ignoreNextClick = true;
}
const {draggingType} = this; const {draggingType} = this;
this.isZoomingNow = false; this.isZoomingNow = false;
this.isGesturingNow = false; this.isGesturingNow = false;
@ -492,7 +515,7 @@ export default class AppMediaViewerBase<
this.isZoomingNow = true; this.isZoomingNow = true;
const zoomMaxBounceValue = ZOOM_MAX_VALUE * 3; const zoomMaxBounceValue = ZOOM_MAX_VALUE * 3;
const scale = zoomAdd ? clamp(this.lastTransform.scale + zoomAdd, ZOOM_MIN_VALUE, zoomMaxBounceValue) : (zoom ?? clamp(this.lastTransform.scale * zoomFactor, ZOOM_MIN_VALUE, zoomMaxBounceValue)); const scale = zoomAdd !== undefined ? clamp(this.lastTransform.scale + zoomAdd, ZOOM_MIN_VALUE, zoomMaxBounceValue) : (zoom ?? clamp(this.lastTransform.scale * zoomFactor, ZOOM_MIN_VALUE, zoomMaxBounceValue));
const scaleFactor = scale / this.lastTransform.scale; const scaleFactor = scale / this.lastTransform.scale;
const offsetX = Math.abs(Math.min(this.lastTransform.x, 0)); const offsetX = Math.abs(Math.min(this.lastTransform.x, 0));
const offsetY = Math.abs(Math.min(this.lastTransform.y, 0)); const offsetY = Math.abs(Math.min(this.lastTransform.y, 0));
@ -661,8 +684,8 @@ export default class AppMediaViewerBase<
this.moversContainer.style.transform = `translate3d(${this.transform.x.toFixed(3)}px, ${this.transform.y.toFixed(3)}px, 0px) scale(${value.toFixed(3)})`; this.moversContainer.style.transform = `translate3d(${this.transform.x.toFixed(3)}px, ${this.transform.y.toFixed(3)}px, 0px) scale(${value.toFixed(3)})`;
this.zoomElements.btnOut.classList.toggle('inactive', value === ZOOM_MIN_VALUE); this.zoomElements.btnOut.classList.toggle('inactive', value <= ZOOM_MIN_VALUE);
this.zoomElements.btnIn.classList.toggle('inactive', value === ZOOM_MAX_VALUE); this.zoomElements.btnIn.classList.toggle('inactive', value >= ZOOM_MAX_VALUE);
this.toggleZoom(value !== ZOOM_INITIAL_VALUE); this.toggleZoom(value !== ZOOM_INITIAL_VALUE);
}; };
@ -735,6 +758,11 @@ export default class AppMediaViewerBase<
} }
onClick = (e: MouseEvent) => { onClick = (e: MouseEvent) => {
if(this.ignoreNextClick) {
this.ignoreNextClick = undefined;
return;
}
if(this.setMoverAnimationPromise) return; if(this.setMoverAnimationPromise) return;
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
@ -756,7 +784,11 @@ export default class AppMediaViewerBase<
return; return;
} }
const isZooming = this.isZooming; if(hasMouseMovedSinceDown(e)) {
return;
}
const isZooming = this.isZooming && false;
let mover: HTMLElement = null; let mover: HTMLElement = null;
const classNames = ['ckin__player', 'media-viewer-buttons', 'media-viewer-author', 'media-viewer-caption', 'zoom-container']; const classNames = ['ckin__player', 'media-viewer-buttons', 'media-viewer-author', 'media-viewer-caption', 'zoom-container'];
if(isZooming) { if(isZooming) {
@ -1850,8 +1882,9 @@ export default class AppMediaViewerBase<
const cancellablePromise = isDocument ? appDownloadManager.downloadMediaURL({media}) : appDownloadManager.downloadMediaURL({media, thumb: size}); const cancellablePromise = isDocument ? appDownloadManager.downloadMediaURL({media}) : appDownloadManager.downloadMediaURL({media, thumb: size});
const photoSizes = !isDocument && media.sizes.slice().filter((size) => (size as PhotoSize.photoSize).w) as PhotoSize.photoSize[]; const photoSizes = !isDocument && media.sizes.slice().filter((size) => (size as PhotoSize.photoSize).w) as PhotoSize.photoSize[];
photoSizes?.sort((a, b) => b.size - a.size); photoSizes && photoSizes.sort((a, b) => b.size - a.size);
const cancellableFullPromise = !isDocument && appDownloadManager.downloadMediaURL({media, thumb: photoSizes?.[0]}); const fullPhotoSize = photoSizes?.[0];
const cancellableFullPromise = !isDocument && fullPhotoSize !== size && appDownloadManager.downloadMediaURL({media, thumb: fullPhotoSize});
onAnimationEnd.then(async() => { onAnimationEnd.then(async() => {
if(!(await getCacheContext()).url) { if(!(await getCacheContext()).url) {
@ -1866,8 +1899,6 @@ export default class AppMediaViewerBase<
return; return;
} }
// /////this.log('indochina', blob);
const url = (await getCacheContext()).url; const url = (await getCacheContext()).url;
if(target instanceof SVGSVGElement) { if(target instanceof SVGSVGElement) {
this.updateMediaSource(target, url, 'img'); this.updateMediaSource(target, url, 'img');
@ -1886,8 +1917,6 @@ export default class AppMediaViewerBase<
const image = new Image(); const image = new Image();
image.classList.add('thumbnail'); image.classList.add('thumbnail');
// this.log('will renderImageFromUrl:', image, div, target);
renderImageFromUrl(image, url, () => { renderImageFromUrl(image, url, () => {
fastRaf(() => { fastRaf(() => {
this.updateMediaSource(target, url, 'img'); this.updateMediaSource(target, url, 'img');
@ -1902,7 +1931,7 @@ export default class AppMediaViewerBase<
}); });
}, false); }, false);
cancellableFullPromise?.then((url) => { cancellableFullPromise && cancellableFullPromise.then((url) => {
const fullImage = new Image(); const fullImage = new Image();
fullImage.classList.add('thumbnail'); fullImage.classList.add('thumbnail');
renderImageFromUrl(fullImage, url, () => { renderImageFromUrl(fullImage, url, () => {

View File

@ -648,7 +648,7 @@ export default class AppSearchSuper {
} }
private async processPhotoVideoFilter({message, promises, middleware}: ProcessSearchSuperResult) { private async processPhotoVideoFilter({message, promises, middleware}: ProcessSearchSuperResult) {
const media = getMediaFromMessage(message); const media = getMediaFromMessage(message, true);
const div = document.createElement('div'); const div = document.createElement('div');
div.classList.add('grid-item'); div.classList.add('grid-item');
@ -711,7 +711,7 @@ export default class AppSearchSuper {
} }
private async processDocumentFilter({message, inputFilter}: ProcessSearchSuperResult) { private async processDocumentFilter({message, inputFilter}: ProcessSearchSuperResult) {
const document = getMediaFromMessage(message) as Document.document; const document = getMediaFromMessage(message, true) as Document.document;
const showSender = this.showSender || (['voice', 'round'] as MyDocument['type'][]).includes(document.type); const showSender = this.showSender || (['voice', 'round'] as MyDocument['type'][]).includes(document.type);
const div = await wrapDocument({ const div = await wrapDocument({

View File

@ -31,7 +31,7 @@ import filterAsync from '../helpers/array/filterAsync';
import getParticipantPeerId from '../lib/appManagers/utils/chats/getParticipantPeerId'; import getParticipantPeerId from '../lib/appManagers/utils/chats/getParticipantPeerId';
import getChatMembersString from './wrappers/getChatMembersString'; import getChatMembersString from './wrappers/getChatMembersString';
import getUserStatusString from './wrappers/getUserStatusString'; import getUserStatusString from './wrappers/getUserStatusString';
import {Chat, User} from '../layer'; import {ChannelsChannelParticipants, Chat, User} from '../layer';
import canSendToUser from '../lib/appManagers/utils/users/canSendToUser'; import canSendToUser from '../lib/appManagers/utils/users/canSendToUser';
import hasRights from '../lib/appManagers/utils/chats/hasRights'; import hasRights from '../lib/appManagers/utils/chats/hasRights';
import getDialogIndex from '../lib/appManagers/utils/dialogs/getDialogIndex'; import getDialogIndex from '../lib/appManagers/utils/dialogs/getDialogIndex';
@ -75,7 +75,7 @@ export default class AppSelectPeers {
private onChange: (length: number) => void; private onChange: (length: number) => void;
private peerType: SelectSearchPeerType[] = ['dialogs']; private peerType: SelectSearchPeerType[] = ['dialogs'];
private renderResultsFunc: (peerIds: PeerId[]) => void | Promise<void>; private renderResultsFunc: (peerIds: PeerId[]) => void | Promise<void>;
private chatRightsAction: ChatRights; private chatRightsActions: ChatRights[];
private multiSelect = true; private multiSelect = true;
private rippleEnabled = true; private rippleEnabled = true;
private avatarSize: DialogElementSize = 'abitbigger'; private avatarSize: DialogElementSize = 'abitbigger';
@ -104,7 +104,7 @@ export default class AppSelectPeers {
peerId?: AppSelectPeers['peerId'], peerId?: AppSelectPeers['peerId'],
onFirstRender?: () => void, onFirstRender?: () => void,
renderResultsFunc?: AppSelectPeers['renderResultsFunc'], renderResultsFunc?: AppSelectPeers['renderResultsFunc'],
chatRightsAction?: AppSelectPeers['chatRightsAction'], chatRightsActions?: AppSelectPeers['chatRightsActions'],
multiSelect?: AppSelectPeers['multiSelect'], multiSelect?: AppSelectPeers['multiSelect'],
rippleEnabled?: AppSelectPeers['rippleEnabled'], rippleEnabled?: AppSelectPeers['rippleEnabled'],
avatarSize?: AppSelectPeers['avatarSize'], avatarSize?: AppSelectPeers['avatarSize'],
@ -347,7 +347,7 @@ export default class AppSelectPeers {
dialogs = dialogs.slice(); dialogs = dialogs.slice();
findAndSplice(dialogs, d => d.peerId === rootScope.myId); // no my account findAndSplice(dialogs, d => d.peerId === rootScope.myId); // no my account
if(this.chatRightsAction) { if(this.chatRightsActions) {
dialogs = await filterAsync(dialogs, (d) => this.filterByRights(d.peerId)); dialogs = await filterAsync(dialogs, (d) => this.filterByRights(d.peerId));
if(!middleware()) { if(!middleware()) {
return; return;
@ -389,8 +389,8 @@ export default class AppSelectPeers {
private async filterByRights(peerId: PeerId) { private async filterByRights(peerId: PeerId) {
const peer: User | Chat = await this.managers.appPeersManager.getPeer(peerId); const peer: User | Chat = await this.managers.appPeersManager.getPeer(peerId);
if(peerId.isUser()) { if(peerId.isUser()) {
return this.chatRightsAction !== 'send_messages' || canSendToUser(peer as User.user); return this.chatRightsActions[0] !== 'send_plain' || canSendToUser(peer as User.user);
} else if(hasRights(peer as Chat.chat, this.chatRightsAction)) { } else if(this.chatRightsActions.every((action) => hasRights(peer as Chat.chat, action))) {
return true; return true;
} }
} }
@ -433,7 +433,7 @@ export default class AppSelectPeers {
// do not add global result if only dialogs needed // do not add global result if only dialogs needed
let resultPeerIds = isGlobalSearch ? searchResult.my_results.concat(searchResult.results) : searchResult.my_results; let resultPeerIds = isGlobalSearch ? searchResult.my_results.concat(searchResult.results) : searchResult.my_results;
if(this.chatRightsAction) { if(this.chatRightsActions) {
resultPeerIds = await filterAsync(resultPeerIds, (peerId) => this.filterByRights(peerId)); resultPeerIds = await filterAsync(resultPeerIds, (peerId) => this.filterByRights(peerId));
if(!middleware()) { if(!middleware()) {
return; return;
@ -474,7 +474,7 @@ export default class AppSelectPeers {
const pageCount = 50; // same as in group permissions to use cache const pageCount = 50; // same as in group permissions to use cache
const {middleware} = this.getTempId('channelParticipants'); const {middleware} = this.getTempId('channelParticipants');
const promise = this.managers.appProfileManager.getChannelParticipants( const promise = this.managers.appProfileManager.getParticipants(
this.peerId.toChatId(), this.peerId.toChatId(),
{ {
_: 'channelParticipantsSearch', _: 'channelParticipantsSearch',
@ -492,18 +492,20 @@ export default class AppSelectPeers {
this.loadedWhat.channelParticipants = true; this.loadedWhat.channelParticipants = true;
}); });
const participants = await promise; const chatParticipants = await promise;
if(!middleware()) { if(!middleware()) {
return; return;
} }
const peerIds = participants.participants.map((participant) => { const {participants} = chatParticipants;
return getParticipantPeerId(participant);
}); const peerIds = participants.map((participant) => getParticipantPeerId(participant));
indexOfAndSplice(peerIds, rootScope.myId); indexOfAndSplice(peerIds, rootScope.myId);
this.renderResultsFunc(peerIds); this.renderResultsFunc(peerIds);
if(this.list.childElementCount >= participants.count || participants.participants.length < pageCount) { const count = (chatParticipants as ChannelsChannelParticipants.channelsChannelParticipants).count ?? participants.length;
if(this.list.childElementCount >= count || participants.length < pageCount) {
this.loadedWhat.channelParticipants = true; this.loadedWhat.channelParticipants = true;
} }
} }

View File

@ -123,7 +123,7 @@ function createWaveformBars(waveform: Uint8Array, duration: number) {
const bar_value = Math.max(((maxValue * maxDelta) + ((normValue + 1) / 2)) / (normValue + 1), barHeightMin); const bar_value = Math.max(((maxValue * maxDelta) + ((normValue + 1) / 2)) / (normValue + 1), barHeightMin);
const h = ` const h = `
<rect x="${barX}" y="${barHeightMax - bar_value}" width="${barWidth}" height="${bar_value}" rx="1" ry="1"></rect> <rect class="audio-waveform-bar" x="${barX}" y="${barHeightMax - bar_value}" width="${barWidth}" height="${bar_value}" rx="1" ry="1"></rect>
`; `;
html += h; html += h;
@ -197,6 +197,11 @@ async function wrapVoiceMessage(audioEl: AudioElement) {
// TODO: State to enum // TODO: State to enum
audioEl.transcriptionState = 2; audioEl.transcriptionState = 2;
} else { } else {
const message = audioEl.message;
if(message.pFlags.is_outgoing) {
return;
}
audioEl.transcriptionState = 1; audioEl.transcriptionState = 1;
!speechRecognitionLoader.parentElement && speechRecognitionDiv.append(speechRecognitionLoader); !speechRecognitionLoader.parentElement && speechRecognitionDiv.append(speechRecognitionLoader);
doubleRaf().then(() => { doubleRaf().then(() => {

View File

@ -6,7 +6,7 @@
import contextMenuController from '../helpers/contextMenuController'; import contextMenuController from '../helpers/contextMenuController';
import cancelEvent from '../helpers/dom/cancelEvent'; import cancelEvent from '../helpers/dom/cancelEvent';
import {AttachClickOptions, CLICK_EVENT_NAME} from '../helpers/dom/clickEvent'; import {AttachClickOptions, CLICK_EVENT_NAME, hasMouseMovedSinceDown} from '../helpers/dom/clickEvent';
import ListenerSetter from '../helpers/listenerSetter'; import ListenerSetter from '../helpers/listenerSetter';
import ButtonIcon from './buttonIcon'; import ButtonIcon from './buttonIcon';
import ButtonMenu, {ButtonMenuItemOptionsVerifiable} from './buttonMenu'; import ButtonMenu, {ButtonMenuItemOptionsVerifiable} from './buttonMenu';
@ -28,7 +28,7 @@ export function ButtonMenuToggleHandler({
const add = options?.listenerSetter ? options.listenerSetter.add(el) : el.addEventListener.bind(el); const add = options?.listenerSetter ? options.listenerSetter.add(el) : el.addEventListener.bind(el);
add(CLICK_EVENT_NAME, (e: Event) => { add(CLICK_EVENT_NAME, (e: Event) => {
if(!el.classList.contains('btn-menu-toggle')) return false; if(!el.classList.contains('btn-menu-toggle') || hasMouseMovedSinceDown(e)) return false;
cancelEvent(e); cancelEvent(e);

View File

@ -656,11 +656,11 @@ export default class ChatBubbles {
dots?.remove(); dots?.remove();
} }
if(!text && !pending && !transcribedText.classList.contains('has-some-text')) { if(!text && !pending/* && !transcribedText.classList.contains('has-some-text') */) {
transcribedText.append(i18n('Chat.Voice.Transribe.Error')); transcribedText.replaceChildren(i18n('Chat.Voice.Transribe.Error'));
transcribedText.classList.add('is-error'); transcribedText.classList.add('is-error');
} else { } else if(text) {
transcribedText.classList.add('has-some-text'); // transcribedText.classList.add('has-some-text');
transcribedText.firstChild.textContent = text; transcribedText.firstChild.textContent = text;
} }
@ -1101,9 +1101,14 @@ export default class ChatBubbles {
const chat = await this.managers.appChatsManager.getChat(chatId); const chat = await this.managers.appChatsManager.getChat(chatId);
const hadRights = this.chatInner.classList.contains('has-rights'); const hadRights = this.chatInner.classList.contains('has-rights');
const hasRights = await this.chat.canSend(); const hadPlainRights = this.chat.input.canSendPlain();
const [hasRights, hasPlainRights, canEmbedLinks] = await Promise.all([
this.chat.canSend('send_messages'),
this.chat.canSend('send_plain'),
this.chat.canSend('embed_links')
]);
if(hadRights !== hasRights) { if(hadRights !== hasRights || hadPlainRights !== hasPlainRights) {
const callbacks = await Promise.all([ const callbacks = await Promise.all([
this.finishPeerChange(), this.finishPeerChange(),
this.chat.input.finishPeerChange() this.chat.input.finishPeerChange()
@ -1112,6 +1117,12 @@ export default class ChatBubbles {
callbacks.forEach((callback) => callback()); callbacks.forEach((callback) => callback());
} }
// reset webpage
if((canEmbedLinks && !this.chat.input.willSendWebPage) || (!canEmbedLinks && this.chat.input.willSendWebPage)) {
this.chat.input.lastUrl = '';
this.chat.input.onMessageInput();
}
if(!!(chat as MTChat.channel).pFlags.forum !== this.chat.isForum && this.chat.type === 'chat') { if(!!(chat as MTChat.channel).pFlags.forum !== this.chat.isForum && this.chat.type === 'chat') {
this.chat.peerId = 0; this.chat.peerId = 0;
this.chat.appImManager.setPeer({peerId}); this.chat.appImManager.setPeer({peerId});

View File

@ -19,7 +19,7 @@ import findUpClassName from '../../helpers/dom/findUpClassName';
import cancelEvent from '../../helpers/dom/cancelEvent'; import cancelEvent from '../../helpers/dom/cancelEvent';
import {attachClickEvent, simulateClickEvent} from '../../helpers/dom/clickEvent'; import {attachClickEvent, simulateClickEvent} from '../../helpers/dom/clickEvent';
import isSelectionEmpty from '../../helpers/dom/isSelectionEmpty'; import isSelectionEmpty from '../../helpers/dom/isSelectionEmpty';
import {Message, Poll, Chat as MTChat, MessageMedia, AvailableReaction, MessageEntity, InputStickerSet, StickerSet, Document, Reaction} from '../../layer'; import {Message, Poll, Chat as MTChat, MessageMedia, AvailableReaction, MessageEntity, InputStickerSet, StickerSet, Document, Reaction, Photo} from '../../layer';
import PopupReportMessages from '../popups/reportMessages'; import PopupReportMessages from '../popups/reportMessages';
import assumeType from '../../helpers/assumeType'; import assumeType from '../../helpers/assumeType';
import PopupSponsored from '../popups/sponsored'; import PopupSponsored from '../popups/sponsored';
@ -39,7 +39,7 @@ import {attachContextMenuListener} from '../../helpers/dom/attachContextMenuList
import filterAsync from '../../helpers/array/filterAsync'; import filterAsync from '../../helpers/array/filterAsync';
import appDownloadManager from '../../lib/appManagers/appDownloadManager'; import appDownloadManager from '../../lib/appManagers/appDownloadManager';
import {SERVICE_PEER_ID} from '../../lib/mtproto/mtproto_config'; import {SERVICE_PEER_ID} from '../../lib/mtproto/mtproto_config';
import {MessagesStorageKey} from '../../lib/appManagers/appMessagesManager'; import {MessagesStorageKey, MyMessage} from '../../lib/appManagers/appMessagesManager';
import filterUnique from '../../helpers/array/filterUnique'; import filterUnique from '../../helpers/array/filterUnique';
import replaceContent from '../../helpers/dom/replaceContent'; import replaceContent from '../../helpers/dom/replaceContent';
import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText'; import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText';
@ -79,6 +79,7 @@ export default class ChatContextMenu {
private albumMessages: Message.message[]; private albumMessages: Message.message[];
private linkToMessage: Awaited<ReturnType<ChatContextMenu['getUrlToMessage']>>; private linkToMessage: Awaited<ReturnType<ChatContextMenu['getUrlToMessage']>>;
private selectedMessagesText: string; private selectedMessagesText: string;
private selectedMessages: MyMessage[];
constructor( constructor(
private chat: Chat, private chat: Chat,
@ -199,6 +200,7 @@ export default class ChatContextMenu {
this.canOpenReactedList = undefined; this.canOpenReactedList = undefined;
this.linkToMessage = await this.getUrlToMessage(); this.linkToMessage = await this.getUrlToMessage();
this.selectedMessagesText = await this.getSelectedMessagesText(); this.selectedMessagesText = await this.getSelectedMessagesText();
this.selectedMessages = this.chat.selection.isSelecting ? await this.chat.selection.getSelectedMessages() : undefined;
const initResult = await this.init(); const initResult = await this.init();
if(!initResult) { if(!initResult) {
@ -445,31 +447,9 @@ export default class ChatContextMenu {
icon: 'download', icon: 'download',
text: 'MediaViewer.Context.Download', text: 'MediaViewer.Context.Download',
onClick: () => { onClick: () => {
appDownloadManager.downloadToDisc({media: getMediaFromMessage(this.message)}); appDownloadManager.downloadToDisc({media: getMediaFromMessage(this.message, true)});
}, },
verify: () => { verify: () => this.canDownload(this.message, true)
if(!canSaveMessageMedia(this.message) || this.noForwards) {
return false;
}
const isPhoto: boolean = !!((this.message as Message.message).media as MessageMedia.messageMediaPhoto)?.photo;
let isGoodType = false
if(isPhoto) {
isGoodType = true;
} else {
const doc: MyDocument = ((this.message as Message.message).media as MessageMedia.messageMediaDocument)?.document as any;
if(!doc) return false;
// isGoodType = doc.type && (['gif', 'video', 'audio', 'voice', 'sticker'] as MyDocument['type'][]).includes(doc.type)
isGoodType = true;
}
let hasTarget = !!IS_TOUCH_SUPPORTED;
if(isGoodType) hasTarget ||= !!findUpClassName(this.target, 'document') || !!findUpClassName(this.target, 'audio') || !!findUpClassName(this.target, 'media-sticker-wrapper') || !!findUpClassName(this.target, 'media-photo') || !!findUpClassName(this.target, 'media-video');
return isGoodType && hasTarget;
}
}, { }, {
icon: 'checkretract', icon: 'checkretract',
text: 'Chat.Poll.Unvote', text: 'Chat.Poll.Unvote',
@ -502,6 +482,16 @@ export default class ChatContextMenu {
!this.chat.selection.selectionForwardBtn.hasAttribute('disabled'), !this.chat.selection.selectionForwardBtn.hasAttribute('disabled'),
notDirect: () => true, notDirect: () => true,
withSelection: true withSelection: true
}, {
icon: 'download',
text: 'Message.Context.Selection.Download',
onClick: () => {
this.selectedMessages.forEach((message) => {
appDownloadManager.downloadToDisc({media: getMediaFromMessage(message, true)});
});
},
verify: () => this.selectedMessages ? this.selectedMessages.some((message) => this.canDownload(message, false)) : false,
withSelection: true
}, { }, {
icon: 'flag', icon: 'flag',
text: 'ReportChat', text: 'ReportChat',
@ -574,6 +564,30 @@ export default class ChatContextMenu {
}]; }];
} }
private canDownload(message: MyMessage, withTarget?: boolean) {
if(!canSaveMessageMedia(message) || this.noForwards) {
return false;
}
const isPhoto: boolean = !!((message as Message.message).media as MessageMedia.messageMediaPhoto)?.photo;
let isGoodType = false
if(isPhoto) {
isGoodType = true;
} else {
const doc: MyDocument = ((message as Message.message).media as MessageMedia.messageMediaDocument)?.document as any;
if(!doc) return false;
// isGoodType = doc.type && (['gif', 'video', 'audio', 'voice', 'sticker'] as MyDocument['type'][]).includes(doc.type)
isGoodType = true;
}
let hasTarget = !withTarget || !!IS_TOUCH_SUPPORTED;
if(isGoodType) hasTarget ||= !!findUpClassName(this.target, 'document') || !!findUpClassName(this.target, 'audio') || !!findUpClassName(this.target, 'media-sticker-wrapper') || !!findUpClassName(this.target, 'media-photo') || !!findUpClassName(this.target, 'media-video');
return isGoodType && hasTarget;
}
private getMessageWithText() { private getMessageWithText() {
return (this.albumMessages && getAlbumText(this.albumMessages)) || this.message; return (this.albumMessages && getAlbumText(this.albumMessages)) || this.message;
} }
@ -932,7 +946,7 @@ export default class ChatContextMenu {
} else { } else {
const peerId = this.peerId; const peerId = this.peerId;
const mids = this.isTargetAGroupedItem ? [this.mid] : await this.chat.getMidsByMid(this.mid); const mids = this.isTargetAGroupedItem ? [this.mid] : await this.chat.getMidsByMid(this.mid);
new PopupForward({ PopupForward.create({
[peerId]: mids [peerId]: mids
}); });
} }

View File

@ -29,6 +29,8 @@ import generateQId from '../../lib/appManagers/utils/inlineBots/generateQId';
import appDownloadManager from '../../lib/appManagers/appDownloadManager'; import appDownloadManager from '../../lib/appManagers/appDownloadManager';
import {AnimationItemGroup} from '../animationIntersector'; import {AnimationItemGroup} from '../animationIntersector';
import wrapPhoto from '../wrappers/photo'; import wrapPhoto from '../wrappers/photo';
import {i18n} from '../../lib/langPack';
import {POSTING_NOT_ALLOWED_MAP} from './input';
const ANIMATION_GROUP: AnimationItemGroup = 'INLINE-HELPER'; const ANIMATION_GROUP: AnimationItemGroup = 'INLINE-HELPER';
// const GRID_ITEMS = 5; // const GRID_ITEMS = 5;
@ -39,7 +41,7 @@ export default class InlineHelper extends AutocompleteHelper {
private gifsMasonry: GifsMasonry; private gifsMasonry: GifsMasonry;
private superStickerRenderer: SuperStickerRenderer; private superStickerRenderer: SuperStickerRenderer;
private onChangeScreen: () => void; private onChangeScreen: () => void;
public checkQuery: (peerId: PeerId, username: string, query: string) => ReturnType<InlineHelper['_checkQuery']>; public checkQuery: ReturnType<typeof debounce<InlineHelper['_checkQuery']>>;
constructor( constructor(
appendTo: HTMLElement, appendTo: HTMLElement,
@ -85,7 +87,7 @@ export default class InlineHelper extends AutocompleteHelper {
}); });
} }
public _checkQuery = async(peerId: PeerId, username: string, query: string) => { public _checkQuery = async(peerId: PeerId, username: string, query: string, canSendInline: boolean) => {
const middleware = this.controller.getMiddleware(); const middleware = this.controller.getMiddleware();
const peer = await this.managers.appUsersManager.resolveUsername(username); const peer = await this.managers.appUsersManager.resolveUsername(username);
@ -97,6 +99,21 @@ export default class InlineHelper extends AutocompleteHelper {
throw 'NOT_A_BOT'; throw 'NOT_A_BOT';
} }
if(!canSendInline) {
if(!middleware()) {
throw 'PEER_CHANGED';
}
if(this.init) {
this.init();
this.init = null;
}
this.container.classList.add('cant-send');
this.toggle(false);
throw 'NO_INLINES';
}
const renderPromise = this.managers.appInlineBotsManager.getInlineResults(peerId, peer.id, query).then((botResults) => { const renderPromise = this.managers.appInlineBotsManager.getInlineResults(peerId, peer.id, query).then((botResults) => {
if(!middleware()) { if(!middleware()) {
throw 'PEER_CHANGED'; throw 'PEER_CHANGED';
@ -252,10 +269,9 @@ export default class InlineHelper extends AutocompleteHelper {
parent.append(btnSwitchToPM); parent.append(btnSwitchToPM);
} }
parent.append(this.list = list); parent.append(this.list = list);
this.container.classList.remove('cant-send');
if(this.gifsMasonry) { this.gifsMasonry?.detach();
this.gifsMasonry.detach();
}
this.gifsMasonry = gifsMasonry; this.gifsMasonry = gifsMasonry;
gifsMasonry.attach(); gifsMasonry.attach();
@ -290,5 +306,9 @@ export default class InlineHelper extends AutocompleteHelper {
this.scrollable = new Scrollable(this.container); this.scrollable = new Scrollable(this.container);
this.lazyLoadQueue = new LazyLoadQueue(); this.lazyLoadQueue = new LazyLoadQueue();
this.superStickerRenderer = new SuperStickerRenderer(this.lazyLoadQueue, ANIMATION_GROUP, this.managers); this.superStickerRenderer = new SuperStickerRenderer(this.lazyLoadQueue, ANIMATION_GROUP, this.managers);
const span = i18n(POSTING_NOT_ALLOWED_MAP['send_inline']);
span.classList.add('inline-helper-cant-send');
this.container.append(span);
} }
} }

View File

@ -108,9 +108,21 @@ import getAttachMenuBotIcon from '../../lib/appManagers/utils/attachMenuBots/get
import TelegramWebView from '../telegramWebView'; import TelegramWebView from '../telegramWebView';
import forEachReverse from '../../helpers/array/forEachReverse'; import forEachReverse from '../../helpers/array/forEachReverse';
import {MARKDOWN_ENTITIES} from '../../lib/richTextProcessor'; import {MARKDOWN_ENTITIES} from '../../lib/richTextProcessor';
import IMAGE_MIME_TYPES_SUPPORTED from '../../environment/imageMimeTypesSupport';
import VIDEO_MIME_TYPES_SUPPORTED from '../../environment/videoMimeTypesSupport';
import {ChatRights} from '../../lib/appManagers/appChatsManager';
const RECORD_MIN_TIME = 500; const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
export const POSTING_NOT_ALLOWED_MAP: {[action in ChatRights]?: LangPackKey} = {
send_voices: 'GlobalAttachVoiceRestricted',
send_stickers: 'GlobalAttachStickersRestricted',
send_gifs: 'GlobalAttachGifRestricted',
send_media: 'GlobalAttachMediaRestricted',
send_plain: 'GlobalSendMessageRestricted',
send_polls: 'ErrorSendRestrictedPollsAll',
send_inline: 'GlobalAttachInlineRestricted'
};
type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply'; type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply';
@ -123,7 +135,7 @@ export default class ChatInput {
private inputMessageContainer: HTMLDivElement; private inputMessageContainer: HTMLDivElement;
private btnSend: HTMLButtonElement; private btnSend: HTMLButtonElement;
private btnCancelRecord: HTMLButtonElement; private btnCancelRecord: HTMLButtonElement;
private lastUrl = ''; public lastUrl = '';
private lastTimeType = 0; private lastTimeType = 0;
public chatInput: HTMLElement; public chatInput: HTMLElement;
@ -160,7 +172,7 @@ export default class ChatInput {
private forwardWasDroppingAuthor: boolean; private forwardWasDroppingAuthor: boolean;
private getWebPagePromise: Promise<void>; private getWebPagePromise: Promise<void>;
private willSendWebPage: WebPage = null; public willSendWebPage: WebPage = null;
private forwarding: {[fromPeerId: PeerId]: number[]}; private forwarding: {[fromPeerId: PeerId]: number[]};
public replyToMsgId: number; public replyToMsgId: number;
public editMsgId: number; public editMsgId: number;
@ -585,18 +597,37 @@ export default class ChatInput {
icon.classList.remove('state-back'); icon.classList.remove('state-back');
}); });
// const getSendMediaRights = () => Promise.all([this.chat.canSend('send_photos'), this.chat.canSend('send_videos')]).then(([photos, videos]) => ({photos, videos}));
const onAttachMediaClick = (photos: boolean, videos: boolean) => {
this.fileInput.value = '';
const accept = [
...(photos ? IMAGE_MIME_TYPES_SUPPORTED : []),
...(videos ? VIDEO_MIME_TYPES_SUPPORTED : [])
].join(', ');
this.fileInput.setAttribute('accept', accept);
this.willAttachType = 'media';
this.fileInput.click();
};
this.attachMenuButtons = [{ this.attachMenuButtons = [{
icon: 'image', icon: 'image',
text: 'Chat.Input.Attach.PhotoOrVideo', text: 'Chat.Input.Attach.PhotoOrVideo',
onClick: () => { onClick: () => onAttachMediaClick(true, true)
this.fileInput.value = ''; // verify: () => getSendMediaRights().then(({photos, videos}) => photos && videos)
const accept = [...MEDIA_MIME_TYPES_SUPPORTED].join(', '); }, /* {
this.fileInput.setAttribute('accept', accept); icon: 'image',
this.willAttachType = 'media'; text: 'AttachPhoto',
this.fileInput.click(); onClick: () => onAttachMediaClick(true, false),
}, verify: () => getSendMediaRights().then(({photos, videos}) => photos && !videos)
verify: () => this.chat.canSend('send_media')
}, { }, {
icon: 'image',
text: 'AttachVideo',
onClick: () => onAttachMediaClick(false, true),
verify: () => getSendMediaRights().then(({photos, videos}) => !photos && videos)
}, */ {
icon: 'document', icon: 'document',
text: 'Chat.Input.Attach.Document', text: 'Chat.Input.Attach.Document',
onClick: () => { onClick: () => {
@ -604,17 +635,26 @@ export default class ChatInput {
this.fileInput.removeAttribute('accept'); this.fileInput.removeAttribute('accept');
this.willAttachType = 'document'; this.willAttachType = 'document';
this.fileInput.click(); this.fileInput.click();
}, }
verify: () => this.chat.canSend('send_media') // verify: () => this.chat.canSend('send_docs')
}, { }, {
icon: 'poll', icon: 'poll',
text: 'Poll', text: 'Poll',
onClick: () => { onClick: async() => {
const action: ChatRights = 'send_polls';
if(!(await this.chat.canSend(action))) {
toastNew({langPackKey: POSTING_NOT_ALLOWED_MAP[action]});
return;
}
PopupElement.createPopup(PopupCreatePoll, this.chat).show(); PopupElement.createPopup(PopupCreatePoll, this.chat).show();
}, },
verify: () => (this.chat.peerId.isAnyChat() || this.chat.isBot) && this.chat.canSend('send_polls') verify: () => this.chat.peerId.isAnyChat() || this.chat.isBot
}]; }];
// preload the bots
this.managers.appAttachMenuBotsManager.getAttachMenuBots();
const attachMenuButtons = this.attachMenuButtons.slice(); const attachMenuButtons = this.attachMenuButtons.slice();
this.attachMenu = ButtonMenuToggle({ this.attachMenu = ButtonMenuToggle({
buttonOptions: {noRipple: true}, buttonOptions: {noRipple: true},
@ -1368,6 +1408,7 @@ export default class ChatInput {
canPinMessage, canPinMessage,
isBot, isBot,
canSend, canSend,
canSendPlain,
neededFakeContainer, neededFakeContainer,
ackedPeerFull, ackedPeerFull,
ackedScheduledMids, ackedScheduledMids,
@ -1377,7 +1418,8 @@ export default class ChatInput {
this.managers.appPeersManager.isBroadcast(peerId), this.managers.appPeersManager.isBroadcast(peerId),
this.managers.appPeersManager.canPinMessage(peerId), this.managers.appPeersManager.canPinMessage(peerId),
this.managers.appPeersManager.isBot(peerId), this.managers.appPeersManager.isBot(peerId),
this.chat.canSend(), this.chat.canSend('send_messages'),
this.chat.canSend('send_plain'),
this.getNeededFakeContainer(startParam), this.getNeededFakeContainer(startParam),
modifyAckedPromise(this.managers.acknowledged.appProfileManager.getProfileByPeerId(peerId)), modifyAckedPromise(this.managers.acknowledged.appProfileManager.getProfileByPeerId(peerId)),
btnScheduled ? modifyAckedPromise(this.managers.acknowledged.appMessagesManager.getScheduledMessages(peerId)) : undefined, btnScheduled ? modifyAckedPromise(this.managers.acknowledged.appMessagesManager.getScheduledMessages(peerId)) : undefined,
@ -1385,7 +1427,7 @@ export default class ChatInput {
this.filterAttachMenuButtons() this.filterAttachMenuButtons()
]); ]);
const placeholderKey = this.messageInput ? await this.getPlaceholderKey() : undefined; const placeholderKey = this.messageInput ? await this.getPlaceholderKey(canSendPlain) : undefined;
return () => { return () => {
// console.warn('[input] finishpeerchange start'); // console.warn('[input] finishpeerchange start');
@ -1394,7 +1436,6 @@ export default class ChatInput {
goDownBtn.classList.toggle('is-broadcast', isBroadcast); goDownBtn.classList.toggle('is-broadcast', isBroadcast);
goDownBtn.classList.remove('hide'); goDownBtn.classList.remove('hide');
this.messageInputField?.onFakeInput();
if(this.goDownUnreadBadge) { if(this.goDownUnreadBadge) {
this.setUnreadCount(); this.setUnreadCount();
@ -1439,27 +1480,18 @@ export default class ChatInput {
} }
} }
if(previousSendAs) { previousSendAs?.destroy();
previousSendAs.destroy(); setSendAsCallback?.();
} replyKeyboard?.setPeer(peerId);
sendMenu?.setPeerId(peerId);
if(setSendAsCallback) {
setSendAsCallback();
}
if(replyKeyboard) {
replyKeyboard.setPeer(peerId);
}
if(sendMenu) {
sendMenu.setPeerId(peerId);
}
if(this.messageInput) { if(this.messageInput) {
this.updateMessageInput(canSend, placeholderKey, filteredAttachMenuButtons); this.updateMessageInput(canSend, canSendPlain, placeholderKey, filteredAttachMenuButtons);
this.messageInput.dataset.peerId = '' + peerId; this.messageInput.dataset.peerId = '' + peerId;
} }
this.messageInputField?.onFakeInput(undefined, true);
let haveSomethingInControl = false; let haveSomethingInControl = false;
if(this.pinnedControlBtn) { if(this.pinnedControlBtn) {
const good = this.chat.type === 'pinned'; const good = this.chat.type === 'pinned';
@ -1530,10 +1562,13 @@ export default class ChatInput {
this.updateOffset('commands', forwards, skipAnimation, useRafs); this.updateOffset('commands', forwards, skipAnimation, useRafs);
} }
private async getPlaceholderKey() { private async getPlaceholderKey(canSend?: boolean) {
canSend ??= await this.chat.canSend('send_plain');
const {peerId, threadId, isForum} = this.chat; const {peerId, threadId, isForum} = this.chat;
let key: LangPackKey; let key: LangPackKey;
if(threadId && !isForum) { if(!canSend) {
key = 'Channel.Persmission.MessageBlock';
} else if(threadId && !isForum) {
key = 'Comment'; key = 'Comment';
} else if(await this.managers.appPeersManager.isBroadcast(peerId)) { } else if(await this.managers.appPeersManager.isBroadcast(peerId)) {
key = 'ChannelBroadcast'; key = 'ChannelBroadcast';
@ -1562,13 +1597,17 @@ export default class ChatInput {
private filterAttachMenuButtons() { private filterAttachMenuButtons() {
if(!this.attachMenuButtons) return; if(!this.attachMenuButtons) return;
return filterAsync(this.attachMenuButtons, (button) => { return filterAsync(this.attachMenuButtons, (button) => {
return button.verify(); return button.verify ? button.verify() : true;
}); });
} }
public updateMessageInput(canSend: boolean, placeholderKey: LangPackKey, visible: ChatInput['attachMenuButtons']) { public updateMessageInput(
canSend: boolean,
canSendPlain: boolean,
placeholderKey: LangPackKey,
visible: ChatInput['attachMenuButtons']
) {
const {chatInput, attachMenu, messageInput} = this; const {chatInput, attachMenu, messageInput} = this;
const {peerId, threadId} = this.chat;
const isHidden = chatInput.classList.contains('is-hidden'); const isHidden = chatInput.classList.contains('is-hidden');
const willBeHidden = !canSend; const willBeHidden = !canSend;
if(isHidden !== willBeHidden) { if(isHidden !== willBeHidden) {
@ -1580,14 +1619,18 @@ export default class ChatInput {
this.updateMessageInputPlaceholder(placeholderKey); this.updateMessageInputPlaceholder(placeholderKey);
if(!canSend) { if(!canSend || !canSendPlain) {
messageInput.contentEditable = 'inherit'; messageInput.contentEditable = 'false';
if(!canSendPlain) {
this.messageInputField.onFakeInput(undefined, true);
}
} else { } else {
messageInput.contentEditable = 'true'; messageInput.contentEditable = 'true';
this.setDraft(undefined, false); this.setDraft(undefined, false);
if(!messageInput.innerHTML) { if(!messageInput.innerHTML) {
this.messageInputField.onFakeInput(); this.messageInputField.onFakeInput(undefined, true);
} }
} }
@ -1651,6 +1694,14 @@ export default class ChatInput {
} }
}); });
attachClickEvent(this.messageInput, (e) => {
if(!this.canSendPlain()) {
toastNew({
langPackKey: POSTING_NOT_ALLOWED_MAP['send_plain']
});
}
}, {listenerSetter: this.listenerSetter});
if(IS_TOUCH_SUPPORTED) { if(IS_TOUCH_SUPPORTED) {
attachClickEvent(this.messageInput, (e) => { attachClickEvent(this.messageInput, (e) => {
if(emoticonsDropdown.isActive()) { if(emoticonsDropdown.isActive()) {
@ -1706,6 +1757,10 @@ export default class ChatInput {
}); });
} }
public canSendPlain() {
return !(!this.messageInput.isContentEditable && !this.chatInput.classList.contains('is-hidden'));
}
private prepareDocumentExecute = () => { private prepareDocumentExecute = () => {
this.executedHistory.push(this.messageInput.innerHTML); this.executedHistory.push(this.messageInput.innerHTML);
return () => this.canUndoFromHTML = this.messageInput.innerHTML; return () => this.canUndoFromHTML = this.messageInput.innerHTML;
@ -1877,9 +1932,7 @@ export default class ChatInput {
// checkForSingle(); // checkForSingle();
// saveExecuted(); // saveExecuted();
if(this.appImManager.markupTooltip) { this.appImManager.markupTooltip?.setActiveMarkupButton();
this.appImManager.markupTooltip.setActiveMarkupButton();
}
if(textNode) { if(textNode) {
(textNode.parentElement === this.messageInput ? textNode : textNode.parentElement).remove(); (textNode.parentElement === this.messageInput ? textNode : textNode.parentElement).remove();
@ -1957,7 +2010,7 @@ export default class ChatInput {
} }
}; };
private onMessageInput = (e?: Event) => { public onMessageInput = (e?: Event) => {
// * validate due to manual formatting through browser's context menu // * validate due to manual formatting through browser's context menu
/* const inputType = (e as InputEvent).inputType; /* const inputType = (e as InputEvent).inputType;
console.log('message input event', e); console.log('message input event', e);
@ -1999,17 +2052,15 @@ export default class ChatInput {
} }
} }
// console.log('messageInput url:', url);
if(this.lastUrl !== url) { if(this.lastUrl !== url) {
this.lastUrl = url; this.lastUrl = url;
// this.willSendWebPage = null; const promise = this.getWebPagePromise = Promise.all([
const promise = this.getWebPagePromise = this.managers.appWebPagesManager.getWebPage(url).then((webpage) => { this.managers.appWebPagesManager.getWebPage(url),
this.chat.canSend('embed_links')
]).then(([webpage, canEmbedLinks]) => {
if(this.getWebPagePromise === promise) this.getWebPagePromise = undefined; if(this.getWebPagePromise === promise) this.getWebPagePromise = undefined;
if(this.lastUrl !== url) return; if(this.lastUrl !== url) return;
if(webpage._ === 'webPage') { if(webpage._ === 'webPage' && canEmbedLinks) {
// console.log('got webpage: ', webpage);
this.setTopInfo('webpage', () => {}, webpage.site_name || webpage.title || 'Webpage', webpage.description || webpage.url || ''); this.setTopInfo('webpage', () => {}, webpage.site_name || webpage.title || 'Webpage', webpage.description || webpage.url || '');
delete this.noWebPage; delete this.noWebPage;
this.willSendWebPage = webpage; this.willSendWebPage = webpage;
@ -2039,31 +2090,27 @@ export default class ChatInput {
this.managers.appMessagesManager.setTyping(this.chat.peerId, {_: 'sendMessageCancelAction'}, undefined, this.chat.threadId); this.managers.appMessagesManager.setTyping(this.chat.peerId, {_: 'sendMessageCancelAction'}, undefined, this.chat.threadId);
} }
if(this.appImManager.markupTooltip) { this.appImManager.markupTooltip?.hide();
this.appImManager.markupTooltip.hide();
}
// * Chrome has a bug - it will preserve the formatting if the input with monospace text is cleared // * Chrome has a bug - it will preserve the formatting if the input with monospace text is cleared
// * so have to reset formatting // * so have to reset formatting
if(document.activeElement === this.messageInput) { if(document.activeElement === this.messageInput && !IS_MOBILE) {
// document.execCommand('styleWithCSS', false, 'true');
setTimeout(() => { setTimeout(() => {
if(document.activeElement === this.messageInput) { if(document.activeElement === this.messageInput) {
this.resetCurrentFontFormatting(); this.messageInput.textContent = '1';
placeCaretAtEnd(this.messageInput);
this.messageInput.textContent = '';
} }
}, 0); }, 0);
// document.execCommand('styleWithCSS', false, 'false');
} }
} else { } else {
const time = Date.now(); const time = Date.now();
if((time - this.lastTimeType) >= 6000) { if((time - this.lastTimeType) >= 6000 && e?.isTrusted) {
this.lastTimeType = time; this.lastTimeType = time;
this.managers.appMessagesManager.setTyping(this.chat.peerId, {_: 'sendMessageTypingAction'}, undefined, this.chat.threadId); this.managers.appMessagesManager.setTyping(this.chat.peerId, {_: 'sendMessageTypingAction'}, undefined, this.chat.threadId);
} }
if(this.botCommands) { this.botCommands?.toggle(true);
this.botCommands.toggle(true);
}
} }
if(this.botCommands) { if(this.botCommands) {
@ -2080,6 +2127,13 @@ export default class ChatInput {
}; };
public insertAtCaret(insertText: string, insertEntity?: MessageEntity, isHelper = true) { public insertAtCaret(insertText: string, insertEntity?: MessageEntity, isHelper = true) {
if(!this.canSendPlain()) {
toastNew({
langPackKey: POSTING_NOT_ALLOWED_MAP['send_plain']
});
return;
}
RichInputHandler.getInstance().makeFocused(this.messageInput); RichInputHandler.getInstance().makeFocused(this.messageInput);
const {value: fullValue, caretPos, entities} = getRichValueWithCaret(this.messageInput); const {value: fullValue, caretPos, entities} = getRichValueWithCaret(this.messageInput);
@ -2192,7 +2246,12 @@ export default class ChatInput {
} }
public onEmojiSelected = (emoji: ReturnType<typeof getEmojiFromElement>, autocomplete: boolean) => { public onEmojiSelected = (emoji: ReturnType<typeof getEmojiFromElement>, autocomplete: boolean) => {
const entity: MessageEntity = emoji.docId ? {_: 'messageEntityCustomEmoji', document_id: emoji.docId, length: emoji.emoji.length, offset: 0} : getEmojiEntityFromEmoji(emoji.emoji); const entity: MessageEntity = emoji.docId ? {
_: 'messageEntityCustomEmoji',
document_id: emoji.docId,
length: emoji.emoji.length,
offset: 0
} : getEmojiEntityFromEmoji(emoji.emoji);
this.insertAtCaret(emoji.emoji, entity, autocomplete); this.insertAtCaret(emoji.emoji, entity, autocomplete);
}; };
@ -2255,12 +2314,17 @@ export default class ChatInput {
} }
} }
foundHelper = this.checkInlineAutocomplete(value, foundHelper); let canSendInline: boolean;
if(!foundHelper) {
canSendInline = await this.chat.canSend('send_inline');
}
foundHelper = this.checkInlineAutocomplete(value, canSendInline, foundHelper);
this.autocompleteHelperController.hideOtherHelpers(foundHelper); this.autocompleteHelperController.hideOtherHelpers(foundHelper);
} }
private checkInlineAutocomplete(value: string, foundHelper?: AutocompleteHelper): AutocompleteHelper { private checkInlineAutocomplete(value: string, canSendInline: boolean, foundHelper?: AutocompleteHelper): AutocompleteHelper {
let needPlaceholder = false; let needPlaceholder = false;
const setPreloaderShow = (show: boolean) => { const setPreloaderShow = (show: boolean) => {
@ -2268,6 +2332,10 @@ export default class ChatInput {
return; return;
} }
if(show && !canSendInline) {
show = false;
}
SetTransition({ SetTransition({
element: this.btnPreloader, element: this.btnPreloader,
className: 'show', className: 'show',
@ -2293,7 +2361,7 @@ export default class ChatInput {
setPreloaderShow(true); setPreloaderShow(true);
} }
this.inlineHelper.checkQuery(this.chat.peerId, username, query).then(({user, renderPromise}) => { this.inlineHelper.checkQuery(this.chat.peerId, username, query, canSendInline).then(({user, renderPromise}) => {
if(needPlaceholder && user.bot_inline_placeholder) { if(needPlaceholder && user.bot_inline_placeholder) {
this.messageInput.dataset.inlinePlaceholder = user.bot_inline_placeholder; this.messageInput.dataset.inlinePlaceholder = user.bot_inline_placeholder;
} }
@ -2348,8 +2416,9 @@ export default class ChatInput {
} }
} else { } else {
const isAnyChat = this.chat.peerId.isAnyChat(); const isAnyChat = this.chat.peerId.isAnyChat();
if(isAnyChat && !(await this.chat.canSend('send_media'))) { const flag: ChatRights = 'send_voices';
toast(POSTING_MEDIA_NOT_ALLOWED); if(isAnyChat && !(await this.chat.canSend(flag))) {
toastNew({langPackKey: POSTING_NOT_ALLOWED_MAP[flag]});
return; return;
} }
@ -2477,7 +2546,7 @@ export default class ChatInput {
} }
}; };
private onHelperCancel = async(e?: Event, force?: boolean) => { public onHelperCancel = async(e?: Event, force?: boolean) => {
if(e) { if(e) {
cancelEvent(e); cancelEvent(e);
} }
@ -2770,7 +2839,7 @@ export default class ChatInput {
const flag = document.type === 'sticker' ? 'send_stickers' : (document.type === 'gif' ? 'send_gifs' : 'send_media'); const flag = document.type === 'sticker' ? 'send_stickers' : (document.type === 'gif' ? 'send_gifs' : 'send_media');
if(this.chat.peerId.isAnyChat() && !(await this.chat.canSend(flag))) { if(this.chat.peerId.isAnyChat() && !(await this.chat.canSend(flag))) {
toast(POSTING_MEDIA_NOT_ALLOWED); toastNew({langPackKey: POSTING_NOT_ALLOWED_MAP[flag]});
return false; return false;
} }
@ -3111,31 +3180,4 @@ export default class ChatInput {
return container; return container;
} }
// public saveScroll() {
// this.scrollTop = this.chat.bubbles.scrollable.container.scrollTop;
// this.scrollOffsetTop = this.chatInput.offsetTop;
// }
// public restoreScroll() {
// if(this.chatInput.style.display) return;
// //console.log('input resize', offsetTop, this.chatInput.offsetTop);
// let newOffsetTop = this.chatInput.offsetTop;
// let container = this.chat.bubbles.scrollable.container;
// let scrollTop = container.scrollTop;
// let clientHeight = container.clientHeight;
// let maxScrollTop = container.scrollHeight;
// if(newOffsetTop < this.scrollOffsetTop) {
// this.scrollDiff = this.scrollOffsetTop - newOffsetTop;
// container.scrollTop += this.scrollDiff;
// } else if(scrollTop !== this.scrollTop) {
// let endDiff = maxScrollTop - (scrollTop + clientHeight);
// if(endDiff < this.scrollDiff/* && false */) {
// //container.scrollTop -= endDiff;
// } else {
// container.scrollTop -= this.scrollDiff;
// }
// }
// }
} }

View File

@ -7,7 +7,7 @@
import replaceContent from '../../helpers/dom/replaceContent'; import replaceContent from '../../helpers/dom/replaceContent';
import {Middleware} from '../../helpers/middleware'; import {Middleware} from '../../helpers/middleware';
import limitSymbols from '../../helpers/string/limitSymbols'; import limitSymbols from '../../helpers/string/limitSymbols';
import {Document, Message, MessageMedia, Photo, WebPage} from '../../layer'; import {Document, Message, MessageMedia, Photo, WebPage, VideoSize} from '../../layer';
import appImManager from '../../lib/appManagers/appImManager'; import appImManager from '../../lib/appManagers/appImManager';
import choosePhotoSize from '../../lib/appManagers/utils/photos/choosePhotoSize'; import choosePhotoSize from '../../lib/appManagers/utils/photos/choosePhotoSize';
import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText'; import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText';
@ -85,7 +85,7 @@ export async function wrapReplyDivAndCaption(options: {
middleware, middleware,
loadPromises, loadPromises,
withoutPreloader: true, withoutPreloader: true,
videoSize: document.video_thumbs[0], videoSize: document.video_thumbs[0] as Extract<VideoSize, VideoSize.videoSize>,
group: animationGroup group: animationGroup
}); });
} else { } else {

View File

@ -37,6 +37,7 @@ import {AppManagers} from '../../lib/appManagers/managers';
import {attachContextMenuListener} from '../../helpers/dom/attachContextMenuListener'; import {attachContextMenuListener} from '../../helpers/dom/attachContextMenuListener';
import filterUnique from '../../helpers/array/filterUnique'; import filterUnique from '../../helpers/array/filterUnique';
import appImManager from '../../lib/appManagers/appImManager'; import appImManager from '../../lib/appManagers/appImManager';
import {Message} from '../../layer';
const accumulateMapSet = (map: Map<any, Set<number>>) => { const accumulateMapSet = (map: Map<any, Set<number>>) => {
return [...map.values()].reduce((acc, v) => acc + v.size, 0); return [...map.values()].reduce((acc, v) => acc + v.size, 0);
@ -360,7 +361,7 @@ class AppSelection extends EventListenerBase<{
cantDelete = !size; cantDelete = !size;
const cantSend = !size; const cantSend = !size;
for(const [peerId, mids] of this.selectedMids) { for(const [peerId, mids] of this.selectedMids) {
const storageKey: MessagesStorageKey = `${peerId}_${this.isScheduled ? 'scheduled' : 'history'}`; const storageKey = this.getStorageKey(peerId);
const r = await this.managers.appMessagesManager.cantForwardDeleteMids(storageKey, Array.from(mids)); const r = await this.managers.appMessagesManager.cantForwardDeleteMids(storageKey, Array.from(mids));
cantForward = r.cantForward; cantForward = r.cantForward;
cantDelete = r.cantDelete; cantDelete = r.cantDelete;
@ -371,6 +372,20 @@ class AppSelection extends EventListenerBase<{
this.onUpdateContainer?.(cantForward, cantDelete, cantSend); this.onUpdateContainer?.(cantForward, cantDelete, cantSend);
} }
private getStorageKey(peerId: PeerId): MessagesStorageKey {
return `${peerId}_${this.isScheduled ? 'scheduled' : 'history'}`;
}
public getSelectedMessages() {
const selectedMessagesPromises: Promise<Message.message | Message.messageService>[] = [];
this.selectedMids.forEach((mids, peerId) => {
const storageKey = this.getStorageKey(peerId);
const p = Array.from(mids).map((mid) => this.managers.appMessagesManager.getMessageFromStorage(storageKey, mid));
selectedMessagesPromises.push(...p);
});
return Promise.all(selectedMessagesPromises);
}
public toggleSelection(toggleCheckboxes = true, forceSelection = false) { public toggleSelection(toggleCheckboxes = true, forceSelection = false) {
const wasSelecting = this.isSelecting; const wasSelecting = this.isSelecting;
const size = this.selectedMids.size; const size = this.selectedMids.size;
@ -988,9 +1003,10 @@ export default class ChatSelection extends AppSelection {
void this.selectionInputWrapper.offsetLeft; // reflow void this.selectionInputWrapper.offsetLeft; // reflow
// background.style.opacity = ''; // background.style.opacity = '';
this.selectionInputWrapper.style.opacity = ''; this.selectionInputWrapper.style.opacity = '';
left.style.transform = '';
right.style.transform = '';
} }
this.selectionLeft.style.transform = '';
this.selectionRight.style.transform = '';
} else if(this.selectionLeft && translateButtonsX !== undefined) { } else if(this.selectionLeft && translateButtonsX !== undefined) {
this.selectionLeft.style.transform = `translateX(-${translateButtonsX}px)`; this.selectionLeft.style.transform = `translateX(-${translateButtonsX}px)`;
this.selectionRight.style.transform = `translateX(${translateButtonsX}px)`; this.selectionRight.style.transform = `translateX(${translateButtonsX}px)`;

View File

@ -448,7 +448,7 @@ export default class ChatTopbar {
}); });
}, },
placeholder: 'ShareModal.Search.Placeholder', placeholder: 'ShareModal.Search.Placeholder',
chatRightsAction: 'send_messages', chatRightsActions: ['send_plain'],
selfPresence: 'ChatYourSelf' selfPresence: 'ChatYourSelf'
}); });
}, },

View File

@ -36,7 +36,7 @@ export default class CheckboxField {
const label = this.label = document.createElement('label'); const label = this.label = document.createElement('label');
label.classList.add('checkbox-field'); label.classList.add('checkbox-field');
if(options.restriction) { if(options.restriction && !options.toggle) {
label.classList.add('checkbox-field-restriction'); label.classList.add('checkbox-field-restriction');
} }
@ -109,6 +109,10 @@ export default class CheckboxField {
if(options.toggle) { if(options.toggle) {
label.classList.add('checkbox-field-toggle'); label.classList.add('checkbox-field-toggle');
if(options.restriction) {
label.classList.add('checkbox-field-toggle-restriction');
}
const toggle = document.createElement('div'); const toggle = document.createElement('div');
toggle.classList.add('checkbox-toggle'); toggle.classList.add('checkbox-toggle');
label.append(toggle); label.append(toggle);

View File

@ -37,6 +37,9 @@ import BezierEasing from '../../vendor/bezier-easing';
import RichInputHandler from '../../helpers/dom/richInputHandler'; import RichInputHandler from '../../helpers/dom/richInputHandler';
import {getCaretPosF} from '../../helpers/dom/getCaretPosNew'; import {getCaretPosF} from '../../helpers/dom/getCaretPosNew';
import ListenerSetter from '../../helpers/listenerSetter'; import ListenerSetter from '../../helpers/listenerSetter';
import {ChatRights} from '../../lib/appManagers/appChatsManager';
import {toastNew} from '../toast';
import {POSTING_NOT_ALLOWED_MAP} from '../chat/input';
export const EMOTICONSSTICKERGROUP: AnimationItemGroup = 'emoticons-dropdown'; export const EMOTICONSSTICKERGROUP: AnimationItemGroup = 'emoticons-dropdown';
@ -79,12 +82,19 @@ export class EmoticonsDropdown extends DropdownHover {
private savedRange: Range; private savedRange: Range;
private managers: AppManagers; private managers: AppManagers;
private rights: {[action in ChatRights]?: boolean};
constructor() { constructor() {
super({ super({
element: document.getElementById('emoji-dropdown') as HTMLDivElement, element: document.getElementById('emoji-dropdown') as HTMLDivElement,
ignoreOutClickClassName: 'input-message-input' ignoreOutClickClassName: 'input-message-input'
}); });
this.rights = {
send_gifs: undefined,
send_stickers: undefined
};
this.addEventListener('open', async() => { this.addEventListener('open', async() => {
if(IS_TOUCH_SUPPORTED) { if(IS_TOUCH_SUPPORTED) {
// appImManager.chat.input.saveScroll(); // appImManager.chat.input.saveScroll();
@ -322,6 +332,17 @@ export class EmoticonsDropdown extends DropdownHover {
return; return;
} }
const rights: {[tabId: number]: ChatRights} = {
[this.stickersTab.tabId]: 'send_stickers',
[this.gifsTab.tabId]: 'send_gifs'
};
const action = rights[id];
if(action && !this.rights[action]) {
toastNew({langPackKey: POSTING_NOT_ALLOWED_MAP[action]});
return false;
}
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP); animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP);
this.tabId = id; this.tabId = id;
@ -331,19 +352,19 @@ export class EmoticonsDropdown extends DropdownHover {
private checkRights = async() => { private checkRights = async() => {
const {peerId, threadId} = appImManager.chat; const {peerId, threadId} = appImManager.chat;
const children = this.tabsEl.children;
const tabsElements = Array.from(children) as HTMLElement[];
const [canSendStickers, canSendGifs] = await Promise.all([ const actions = Object.keys(this.rights) as ChatRights[];
this.managers.appMessagesManager.canSendToPeer(peerId, threadId, 'send_stickers'),
this.managers.appMessagesManager.canSendToPeer(peerId, threadId, 'send_gifs')
]);
tabsElements[this.stickersTab.tabId + 1].toggleAttribute('disabled', !canSendStickers); const rights = await Promise.all(actions.map((action) => {
tabsElements[this.gifsTab.tabId + 1].toggleAttribute('disabled', !canSendGifs); return this.managers.appMessagesManager.canSendToPeer(peerId, threadId, action);
}));
actions.forEach((action, idx) => {
this.rights[action] = rights[idx];
});
const active = this.tabsEl.querySelector('.active'); const active = this.tabsEl.querySelector('.active');
if(active && whichChild(active) !== (this.emojiTab.tabId + 1) && (!canSendStickers || !canSendGifs)) { if(active && whichChild(active) !== (this.emojiTab.tabId + 1) && (!this.rights['send_stickers'] || !this.rights['send_gifs'])) {
this.selectTab(this.emojiTab.tabId, false); this.selectTab(this.emojiTab.tabId, false);
} }
}; };

View File

@ -177,7 +177,7 @@ let init = () => {
const peerId = (input.dataset.peerId || NULL_PEER_ID).toPeerId(); const peerId = (input.dataset.peerId || NULL_PEER_ID).toPeerId();
if(html.trim()) { if(html.trim()) {
console.log(html.replace(/ (style|class|id)=".+?"/g, '')); // console.log(html.replace(/ (style|class|id)=".+?"/g, ''));
html = html.replace(/<style([\s\S]*)<\/style>/, ''); html = html.replace(/<style([\s\S]*)<\/style>/, '');
html = html.replace(/<!--([\s\S]*)-->/, ''); html = html.replace(/<!--([\s\S]*)-->/, '');
@ -260,7 +260,7 @@ let init = () => {
mergeEntities(entities, entities2); mergeEntities(entities, entities2);
} }
console.log('usePlainText', usePlainText); // console.log('usePlainText', usePlainText);
} }
if(usePlainText) { if(usePlainText) {

View File

@ -42,20 +42,22 @@ export default class InputFieldAnimated extends InputField {
this.inputFake.className = this.input.className + ' input-field-input-fake'; this.inputFake.className = this.input.className + ' input-field-input-fake';
} }
public onFakeInput(setHeight = true) { public onFakeInput(setHeight = true, noAnimation?: boolean) {
const {scrollHeight: newHeight/* , clientHeight */} = this.inputFake; const {scrollHeight: newHeight/* , clientHeight */} = this.inputFake;
/* if(this.wasInputFakeClientHeight && this.wasInputFakeClientHeight !== clientHeight) { /* if(this.wasInputFakeClientHeight && this.wasInputFakeClientHeight !== clientHeight) {
this.input.classList.add('no-scrollbar'); // ! в сафари может вообще не появиться скролл после анимации, так как ему нужен полный reflow блока с overflow. this.input.classList.add('no-scrollbar'); // ! в сафари может вообще не появиться скролл после анимации, так как ему нужен полный reflow блока с overflow.
this.showScrollDebounced(); this.showScrollDebounced();
} */ } */
noAnimation ??= !this.input.isContentEditable;
const currentHeight = +this.input.style.height.replace('px', ''); const currentHeight = +this.input.style.height.replace('px', '');
if(currentHeight === newHeight) { if(currentHeight === newHeight) {
return; return;
} }
const TRANSITION_DURATION_FACTOR = 50; const TRANSITION_DURATION_FACTOR = 50;
const transitionDuration = Math.round( const transitionDuration = noAnimation ? 0 : Math.round(
TRANSITION_DURATION_FACTOR * Math.log(Math.abs(newHeight - currentHeight)) TRANSITION_DURATION_FACTOR * Math.log(Math.abs(newHeight - currentHeight))
); );

View File

@ -4,15 +4,19 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import type {ChatRights} from '../../lib/appManagers/appChatsManager';
import flatten from '../../helpers/array/flatten';
import appImManager from '../../lib/appManagers/appImManager'; import appImManager from '../../lib/appManagers/appImManager';
import rootScope from '../../lib/rootScope'; import rootScope from '../../lib/rootScope';
import {toastNew} from '../toast'; import {toastNew} from '../toast';
import PopupPickUser from './pickUser'; import PopupPickUser from './pickUser';
import getMediaFromMessage from '../../lib/appManagers/utils/messages/getMediaFromMessage';
export default class PopupForward extends PopupPickUser { export default class PopupForward extends PopupPickUser {
constructor( constructor(
peerIdMids?: {[fromPeerId: PeerId]: number[]}, peerIdMids?: {[fromPeerId: PeerId]: number[]},
onSelect?: (peerId: PeerId) => Promise<void> | void onSelect?: (peerId: PeerId) => Promise<void> | void,
chatRightsAction: ChatRights[] = ['send_plain']
) { ) {
super({ super({
peerTypes: ['dialogs', 'contacts'], peerTypes: ['dialogs', 'contacts'],
@ -43,8 +47,71 @@ export default class PopupForward extends PopupPickUser {
appImManager.chat.input.initMessagesForward(peerIdMids); appImManager.chat.input.initMessagesForward(peerIdMids);
}, },
placeholder: 'ShareModal.Search.ForwardPlaceholder', placeholder: 'ShareModal.Search.ForwardPlaceholder',
chatRightsAction: 'send_messages', chatRightsActions: chatRightsAction,
selfPresence: 'ChatYourSelf' selfPresence: 'ChatYourSelf'
}); });
} }
public static async create(...args: ConstructorParameters<typeof PopupForward>) {
const [peerIdMids] = args;
const messagesPromises = Object.keys(peerIdMids).map((peerId) => {
const mids = peerIdMids[peerId as any as number];
return mids.map((mid) => {
return rootScope.managers.appMessagesManager.getMessageByPeer(peerId.toPeerId(), mid);
});
});
const messages = await Promise.all(flatten(messagesPromises));
const actions: Set<ChatRights> = new Set();
messages.forEach((message) => {
if(!message) {
return;
}
const media = getMediaFromMessage(message);
let action: ChatRights;
if(!media) {
if(message.viaBotId) {
action = 'send_inline';
} else {
action = 'send_plain';
}
} else {
if(media._ === 'webPage') {
action = 'embed_links';
} else if(media._ === 'photo') {
action = 'send_photos';
} else if(media._ === 'game') {
action = 'send_games';
} else {
switch(media.type) {
case 'audio':
action = 'send_audios';
break;
case 'gif':
action = 'send_gifs';
break;
case 'round':
action = 'send_roundvideos';
break;
case 'sticker':
action = 'send_stickers';
break;
case 'voice':
action = 'send_voices';
break;
default:
action = 'send_docs';
break;
}
}
}
if(action) {
actions.add(action);
}
});
new PopupForward(args[0], args[1], Array.from(actions));
}
} }

View File

@ -6,9 +6,10 @@
import type Chat from '../chat/chat'; import type Chat from '../chat/chat';
import type {SendFileDetails} from '../../lib/appManagers/appMessagesManager'; import type {SendFileDetails} from '../../lib/appManagers/appMessagesManager';
import type {ChatRights} from '../../lib/appManagers/appChatsManager';
import PopupElement from '.'; import PopupElement from '.';
import Scrollable from '../scrollable'; import Scrollable from '../scrollable';
import {toast} from '../toast'; import {toast, toastNew} from '../toast';
import SendContextMenu from '../chat/sendContextMenu'; import SendContextMenu from '../chat/sendContextMenu';
import {createPosterFromMedia, createPosterFromVideo} from '../../helpers/createPoster'; import {createPosterFromMedia, createPosterFromVideo} from '../../helpers/createPoster';
import {MyDocument} from '../../lib/appManagers/appDocsManager'; import {MyDocument} from '../../lib/appManagers/appDocsManager';
@ -41,6 +42,11 @@ import {renderImageFromUrlPromise} from '../../helpers/dom/renderImageFromUrl';
import ButtonMenuToggle from '../buttonMenuToggle'; import ButtonMenuToggle from '../buttonMenuToggle';
import partition from '../../helpers/array/partition'; import partition from '../../helpers/array/partition';
import InputFieldAnimated from '../inputFieldAnimated'; import InputFieldAnimated from '../inputFieldAnimated';
import IMAGE_MIME_TYPES_SUPPORTED from '../../environment/imageMimeTypesSupport';
import VIDEO_MIME_TYPES_SUPPORTED from '../../environment/videoMimeTypesSupport';
import rootScope from '../../lib/rootScope';
import shake from '../../helpers/dom/shake';
import AUDIO_MIME_TYPES_SUPPORTED from '../../environment/audioMimeTypeSupport';
type SendFileParams = SendFileDetails & { type SendFileParams = SendFileDetails & {
file?: File, file?: File,
@ -96,6 +102,29 @@ export default class PopupNewMedia extends PopupElement {
this.construct(willAttachType); this.construct(willAttachType);
} }
public static async canSend(peerId: PeerId, onlyVisible?: boolean) {
const actions: ChatRights[] = [
'send_photos',
'send_videos',
'send_docs',
'send_audios',
'send_gifs'
];
const actionsPromises = actions.map((action) => {
return peerId.isAnyChat() && !onlyVisible ? rootScope.managers.appChatsManager.hasRights(peerId.toChatId(), action) : true;
});
const out: {[action in ChatRights]?: boolean} = {};
const results = await Promise.all(actionsPromises);
actions.forEach((action, idx) => {
out[action] = results[idx];
})
return out;
}
private async construct(willAttachType: PopupNewMedia['willAttach']['type']) { private async construct(willAttachType: PopupNewMedia['willAttach']['type']) {
this.willAttach = { this.willAttach = {
type: willAttachType, type: willAttachType,
@ -106,6 +135,12 @@ export default class PopupNewMedia extends PopupElement {
const captionMaxLength = await this.managers.apiManager.getLimit('caption'); const captionMaxLength = await this.managers.apiManager.getLimit('caption');
this.captionLengthMax = captionMaxLength; this.captionLengthMax = captionMaxLength;
const canSend = await PopupNewMedia.canSend(this.chat.peerId, true);
const canSendPhotos = canSend.send_photos;
const canSendVideos = canSend.send_videos;
const canSendDocs = canSend.send_docs;
attachClickEvent(this.btnConfirm, () => this.send(), {listenerSetter: this.listenerSetter}); attachClickEvent(this.btnConfirm, () => this.send(), {listenerSetter: this.listenerSetter});
const btnMenu = await ButtonMenuToggle({ const btnMenu = await ButtonMenuToggle({
@ -115,17 +150,35 @@ export default class PopupNewMedia extends PopupElement {
icon: 'image', icon: 'image',
text: 'Popup.Attach.AsMedia', text: 'Popup.Attach.AsMedia',
onClick: () => this.changeType('media'), onClick: () => this.changeType('media'),
verify: () => this.hasAnyMedia() && this.willAttach.type === 'document' verify: () => {
if(!this.hasAnyMedia() || this.willAttach.type !== 'document') {
return false;
}
if(!canSendPhotos && !canSendVideos) {
return false;
}
if(!canSendPhotos || !canSendVideos) {
const mimeTypes = canSendPhotos ? IMAGE_MIME_TYPES_SUPPORTED : VIDEO_MIME_TYPES_SUPPORTED;
const {media, files} = this.partition(mimeTypes);
if(files.length) {
return false;
}
}
return true;
}
}, { }, {
icon: 'document', icon: 'document',
text: 'SendAsFile', text: 'SendAsFile',
onClick: () => this.changeType('document'), onClick: () => this.changeType('document'),
verify: () => this.files.length === 1 && this.willAttach.type !== 'document' verify: () => this.files.length === 1 && this.willAttach.type !== 'document' && canSendDocs
}, { }, {
icon: 'document', icon: 'document',
text: 'SendAsFiles', text: 'SendAsFiles',
onClick: () => this.changeType('document'), onClick: () => this.changeType('document'),
verify: () => this.files.length > 1 && this.willAttach.type !== 'document' verify: () => this.files.length > 1 && this.willAttach.type !== 'document' && canSendDocs
}, { }, {
icon: 'groupmedia', icon: 'groupmedia',
text: 'Popup.Attach.GroupMedia', text: 'Popup.Attach.GroupMedia',
@ -365,8 +418,8 @@ export default class PopupNewMedia extends PopupElement {
this.willAttach.type = type; this.willAttach.type = type;
} }
private partition() { private partition(mimeTypes = MEDIA_MIME_TYPES_SUPPORTED) {
const [media, files] = partition(this.willAttach.sendFileDetails, (d) => MEDIA_MIME_TYPES_SUPPORTED.has(d.file.type)); const [media, files] = partition(this.willAttach.sendFileDetails, (d) => mimeTypes.has(d.file.type));
return { return {
media, media,
files files
@ -455,7 +508,69 @@ export default class PopupNewMedia extends PopupElement {
} }
}; };
private send(force = false) { private async send(force = false) {
let caption = this.messageInputField.value;
if(caption.length > this.captionLengthMax) {
toast(I18n.format('Error.PreviewSender.CaptionTooLong', true));
return;
}
const {peerId, input} = this.chat;
const canSend = await PopupNewMedia.canSend(peerId);
const willAttach = this.willAttach;
willAttach.isMedia = willAttach.type === 'media' || undefined;
const {sendFileDetails, isMedia} = willAttach;
let foundBad = false;
this.iterate((sendFileParams) => {
if(foundBad) {
return;
}
const isBad: (LangPackKey | boolean)[] = sendFileParams.map((params) => {
const a: [Set<string> | (() => boolean), LangPackKey, ChatRights][] = [
[AUDIO_MIME_TYPES_SUPPORTED, 'GlobalAttachAudioRestricted', 'send_audios'],
[() => !MEDIA_MIME_TYPES_SUPPORTED.has(params.file.type), 'GlobalAttachDocumentsRestricted', 'send_docs']
];
if(isMedia) {
a.unshift(
[IMAGE_MIME_TYPES_SUPPORTED, 'GlobalAttachPhotoRestricted', 'send_photos'],
[() => VIDEO_MIME_TYPES_SUPPORTED.has(params.file.type as any) && params.noSound, 'GlobalAttachGifRestricted', 'send_gifs'],
[VIDEO_MIME_TYPES_SUPPORTED, 'GlobalAttachVideoRestricted', 'send_videos']
);
}
const found = a.find(([verify]) => {
return typeof(verify) === 'function' ? verify() : verify.has(params.file.type);
});
if(found) {
return canSend[found[2]] ? undefined : found[1];
}
return (!isMedia && !canSend.send_docs && 'GlobalAttachDocumentsRestricted') || undefined;
});
const key = isBad.find((i) => typeof(i) === 'string') as LangPackKey;
if(key) {
toastNew({
langPackKey: key
});
if(rootScope.settings.animationsEnabled) {
shake(this.body);
}
}
foundBad ||= !!key;
});
if(foundBad) {
return;
}
if(this.chat.type === 'scheduled' && !force) { if(this.chat.type === 'scheduled' && !force) {
this.chat.input.scheduleSending(() => { this.chat.input.scheduleSending(() => {
this.send(true); this.send(true);
@ -464,18 +579,6 @@ export default class PopupNewMedia extends PopupElement {
return; return;
} }
let caption = this.messageInputField.value;
if(caption.length > this.captionLengthMax) {
toast(I18n.format('Error.PreviewSender.CaptionTooLong', true));
return;
}
const willAttach = this.willAttach;
willAttach.isMedia = willAttach.type === 'media' || undefined;
const {sendFileDetails, isMedia} = willAttach;
const {peerId, input} = this.chat;
const {length} = sendFileDetails; const {length} = sendFileDetails;
const sendingParams = this.chat.getMessageSendingParams(); const sendingParams = this.chat.getMessageSendingParams();
this.iterate((sendFileParams) => { this.iterate((sendFileParams) => {
@ -631,7 +734,7 @@ export default class PopupNewMedia extends PopupElement {
const file = params.file; const file = params.file;
const isPhoto = file.type.startsWith('image/'); const isPhoto = file.type.startsWith('image/');
const isAudio = file.type.startsWith('audio/'); const isAudio = AUDIO_MIME_TYPES_SUPPORTED.has(file.type as any);
if(isPhoto || isAudio || file.size < 20e6) { if(isPhoto || isAudio || file.size < 20e6) {
params.objectURL ||= await apiManagerProxy.invoke('createObjectURL', file); params.objectURL ||= await apiManagerProxy.invoke('createObjectURL', file);
} }
@ -860,3 +963,5 @@ export default class PopupNewMedia extends PopupElement {
}); });
} }
} }
(window as any).PopupNewMedia = PopupNewMedia;

View File

@ -16,7 +16,7 @@ export default class PopupPickUser extends PopupElement {
peerTypes: AppSelectPeers['peerType'], peerTypes: AppSelectPeers['peerType'],
onSelect?: (peerId: PeerId) => Promise<void> | void, onSelect?: (peerId: PeerId) => Promise<void> | void,
placeholder: LangPackKey, placeholder: LangPackKey,
chatRightsAction?: AppSelectPeers['chatRightsAction'], chatRightsActions?: AppSelectPeers['chatRightsActions'],
peerId?: number, peerId?: number,
selfPresence?: LangPackKey selfPresence?: LangPackKey
}) { }) {
@ -51,7 +51,7 @@ export default class PopupPickUser extends PopupElement {
this.selector.input.focus(); this.selector.input.focus();
} }
}, },
chatRightsAction: options.chatRightsAction, chatRightsActions: options.chatRightsActions,
multiSelect: false, multiSelect: false,
rippleEnabled: false, rippleEnabled: false,
avatarSize: 'abitbigger', avatarSize: 'abitbigger',

View File

@ -149,13 +149,15 @@ export default function ripple(
// }); // });
}; };
const isRippleUnneeded = (e: Event) => e.target !== elem && ( const isRippleUnneeded = (e: Event) => {
['BUTTON', 'A'].includes((e.target as HTMLElement).tagName) || return e.target !== elem && (
findUpClassName(e.target as HTMLElement, 'c-ripple') !== r ['BUTTON', 'A'].includes((e.target as HTMLElement).tagName) ||
) && ( findUpClassName(e.target as HTMLElement, 'c-ripple') !== r
attachListenerTo === elem || ) && (
!findUpAsChild(e.target as HTMLElement, attachListenerTo) attachListenerTo === elem ||
); !findUpAsChild(e.target as HTMLElement, attachListenerTo)
) && !findUpClassName(e.target, 'checkbox-field');
};
// TODO: rename this variable // TODO: rename this variable
let touchStartFired = false; let touchStartFired = false;

View File

@ -181,10 +181,15 @@ export default class AppEditChatTab extends SliderSuperTab {
if(canChangePermissions && !isBroadcast) { if(canChangePermissions && !isBroadcast) {
const flags = [ const flags = [
'send_messages',
'send_media',
'send_stickers', 'send_stickers',
'send_polls', 'send_polls',
'send_photos',
'send_videos',
'send_roundvideos',
'send_audios',
'send_voices',
'send_docs',
'send_plain',
'embed_links', 'embed_links',
'invite_users', 'invite_users',
'pin_messages', 'pin_messages',

View File

@ -4,13 +4,15 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import type {ChatRights} from '../../../lib/appManagers/appChatsManager';
import flatten from '../../../helpers/array/flatten';
import cancelEvent from '../../../helpers/dom/cancelEvent';
import {attachClickEvent} from '../../../helpers/dom/clickEvent'; import {attachClickEvent} from '../../../helpers/dom/clickEvent';
import findUpTag from '../../../helpers/dom/findUpTag'; import findUpTag from '../../../helpers/dom/findUpTag';
import replaceContent from '../../../helpers/dom/replaceContent'; import replaceContent from '../../../helpers/dom/replaceContent';
import ListenerSetter from '../../../helpers/listenerSetter'; import ListenerSetter from '../../../helpers/listenerSetter';
import ScrollableLoader from '../../../helpers/scrollableLoader'; import ScrollableLoader from '../../../helpers/scrollableLoader';
import {ChannelParticipant, Chat, ChatBannedRights, Update} from '../../../layer'; import {ChannelParticipant, Chat, ChatBannedRights} from '../../../layer';
import {ChatRights} from '../../../lib/appManagers/appChatsManager';
import appDialogsManager, {DialogDom, DIALOG_LIST_ELEMENT_TAG} from '../../../lib/appManagers/appDialogsManager'; import appDialogsManager, {DialogDom, DIALOG_LIST_ELEMENT_TAG} from '../../../lib/appManagers/appDialogsManager';
import {AppManagers} from '../../../lib/appManagers/managers'; import {AppManagers} from '../../../lib/appManagers/managers';
import combineParticipantBannedRights from '../../../lib/appManagers/utils/chats/combineParticipantBannedRights'; import combineParticipantBannedRights from '../../../lib/appManagers/utils/chats/combineParticipantBannedRights';
@ -21,20 +23,32 @@ import I18n, {i18n, join, LangPackKey} from '../../../lib/langPack';
import rootScope from '../../../lib/rootScope'; import rootScope from '../../../lib/rootScope';
import CheckboxField from '../../checkboxField'; import CheckboxField from '../../checkboxField';
import PopupPickUser from '../../popups/pickUser'; import PopupPickUser from '../../popups/pickUser';
import Row, {CreateRowFromCheckboxField} from '../../row'; import Row from '../../row';
import SettingSection from '../../settingSection'; import SettingSection from '../../settingSection';
import {SliderSuperTabEventable} from '../../sliderTab'; import {SliderSuperTabEventable} from '../../sliderTab';
import {toast} from '../../toast'; import {toast} from '../../toast';
import AppUserPermissionsTab from './userPermissions'; import AppUserPermissionsTab from './userPermissions';
import findUpAsChild from '../../../helpers/dom/findUpAsChild';
type T = {
flags: ChatRights[],
text: LangPackKey,
exceptionText: LangPackKey,
checkboxField?: CheckboxField,
nested?: T[],
nestedTo?: T,
nestedCounter?: HTMLElement,
setNestedCounter?: (count: number) => void,
toggleWith?: {checked?: ChatRights[], unchecked?: ChatRights[]}
};
export class ChatPermissions { export class ChatPermissions {
public v: Array<{ public v: Array<T>;
flags: ChatRights[],
text: LangPackKey, protected chat: Chat.chat | Chat.channel;
exceptionText: LangPackKey, protected rights: ChatBannedRights.chatBannedRights;
checkboxField?: CheckboxField, protected defaultBannedRights: ChatBannedRights.chatBannedRights;
}>; protected restrictionText: LangPackKey;
private toggleWith: Partial<{[chatRight in ChatRights]: ChatRights[]}>;
constructor(private options: { constructor(private options: {
chatId: ChatId, chatId: ChatId,
@ -46,79 +60,158 @@ export class ChatPermissions {
} }
public async construct() { public async construct() {
this.v = [ const mediaNested: T[] = [
{flags: ['send_messages'], text: 'UserRestrictionsSend', exceptionText: 'UserRestrictionsNoSend'}, {flags: ['send_photos'], text: 'UserRestrictionsSendPhotos', exceptionText: 'UserRestrictionsNoSendPhotos'},
{flags: ['send_media'], text: 'UserRestrictionsSendMedia', exceptionText: 'UserRestrictionsNoSendMedia'}, {flags: ['send_videos'], text: 'UserRestrictionsSendVideos', exceptionText: 'UserRestrictionsNoSendVideos'},
{flags: ['send_stickers', 'send_gifs'], text: 'UserRestrictionsSendStickers', exceptionText: 'UserRestrictionsNoSendStickers'}, {flags: ['send_stickers', 'send_gifs'], text: 'UserRestrictionsSendStickers', exceptionText: 'UserRestrictionsNoSendStickers'},
{flags: ['send_polls'], text: 'UserRestrictionsSendPolls', exceptionText: 'UserRestrictionsNoSendPolls'}, {flags: ['send_audios'], text: 'UserRestrictionsSendMusic', exceptionText: 'UserRestrictionsNoSendMusic'},
{flags: ['embed_links'], text: 'UserRestrictionsEmbedLinks', exceptionText: 'UserRestrictionsNoEmbedLinks'}, {flags: ['send_docs'], text: 'UserRestrictionsSendFiles', exceptionText: 'UserRestrictionsNoSendDocs'},
{flags: ['send_voices'], text: 'UserRestrictionsSendVoices', exceptionText: 'UserRestrictionsNoSendVoice'},
{flags: ['send_roundvideos'], text: 'UserRestrictionsSendRound', exceptionText: 'UserRestrictionsNoSendRound'},
{flags: ['embed_links'], text: 'UserRestrictionsEmbedLinks', exceptionText: 'UserRestrictionsNoEmbedLinks', toggleWith: {checked: ['send_plain']}},
{flags: ['send_polls'], text: 'UserRestrictionsSendPolls', exceptionText: 'UserRestrictionsNoSendPolls'}
];
const mediaToggleWith = flatten(mediaNested.map(({flags}) => flags));
const media: T = {flags: ['send_media'], text: 'UserRestrictionsSendMedia', exceptionText: 'UserRestrictionsNoSendMedia', nested: mediaNested, toggleWith: {unchecked: mediaToggleWith, checked: mediaToggleWith}};
this.v = [
{flags: ['send_plain'], text: 'UserRestrictionsSend', exceptionText: 'UserRestrictionsNoSend', toggleWith: {unchecked: ['embed_links']}},
media,
{flags: ['invite_users'], text: 'UserRestrictionsInviteUsers', exceptionText: 'UserRestrictionsNoInviteUsers'}, {flags: ['invite_users'], text: 'UserRestrictionsInviteUsers', exceptionText: 'UserRestrictionsNoInviteUsers'},
{flags: ['pin_messages'], text: 'UserRestrictionsPinMessages', exceptionText: 'UserRestrictionsNoPinMessages'}, {flags: ['pin_messages'], text: 'UserRestrictionsPinMessages', exceptionText: 'UserRestrictionsNoPinMessages'},
{flags: ['change_info'], text: 'UserRestrictionsChangeInfo', exceptionText: 'UserRestrictionsNoChangeInfo'} {flags: ['change_info'], text: 'UserRestrictionsChangeInfo', exceptionText: 'UserRestrictionsNoChangeInfo'}
]; ];
this.toggleWith = { mediaNested.forEach((info) => info.nestedTo = media);
'send_messages': ['send_media', 'send_stickers', 'send_polls', 'embed_links']
};
const options = this.options; const options = this.options;
const chat = await this.managers.appChatsManager.getChat(options.chatId) as Chat.chat | Chat.channel; const chat = this.chat = await this.managers.appChatsManager.getChat(options.chatId) as Chat.chat | Chat.channel;
const defaultBannedRights = chat.default_banned_rights; const defaultBannedRights = this.defaultBannedRights = chat.default_banned_rights;
const rights = options.participant ? combineParticipantBannedRights(chat as Chat.channel, options.participant.banned_rights) : defaultBannedRights; const rights = this.rights = options.participant ? combineParticipantBannedRights(chat as Chat.channel, options.participant.banned_rights) : defaultBannedRights;
const restrictionText: LangPackKey = options.participant ? 'UserRestrictionsDisabled' : 'EditCantEditPermissionsPublic';
for(const info of this.v) { for(const info of this.v) {
const mainFlag = info.flags[0]; const {nodes} = this.createRow(info);
const row = CreateRowFromCheckboxField( options.appendTo.append(...nodes);
info.checkboxField = new CheckboxField({ }
text: info.text,
checked: hasRights(chat, mainFlag, rights),
restriction: true,
listenerSetter: options.listenerSetter
})
);
if(( this.v.push(...mediaNested);
options.participant && }
defaultBannedRights.pFlags[mainFlag as keyof typeof defaultBannedRights['pFlags']]
) || (
getPeerActiveUsernames(chat as Chat.channel)[0] &&
(
info.flags.includes('pin_messages') ||
info.flags.includes('change_info')
)
)
) {
info.checkboxField.input.disabled = true;
/* options.listenerSetter.add(info.checkboxField.input)('change', (e) => { protected createRow(info: T, isNested?: boolean) {
if(!e.isTrusted) { const {defaultBannedRights, chat, rights, restrictionText} = this;
return;
const mainFlag = info.flags[0];
const row = new Row({
titleLangKey: isNested ? undefined : info.text,
checkboxField: info.checkboxField = new CheckboxField({
text: isNested ? info.text : undefined,
checked: info.nested ? false : hasRights(chat, mainFlag, rights),
toggle: !isNested,
listenerSetter: this.options.listenerSetter,
restriction: !isNested
}),
listenerSetter: this.options.listenerSetter,
clickable: info.nested ? (e) => {
if(findUpAsChild(e.target as HTMLElement, row.checkboxField.label)) {
return;
}
cancelEvent(e);
row.container.classList.toggle('accordion-toggler-expanded');
accordion.classList.toggle('is-expanded');
} : undefined
});
if((
this.options.participant &&
defaultBannedRights.pFlags[mainFlag as keyof typeof defaultBannedRights['pFlags']]
) || (
getPeerActiveUsernames(chat as Chat.channel)[0] &&
(
info.flags.includes('pin_messages') ||
info.flags.includes('change_info')
)
)
) {
info.checkboxField.input.disabled = true;
attachClickEvent(info.checkboxField.label, (e) => {
toast(I18n.format(restrictionText, true));
}, {listenerSetter: this.options.listenerSetter});
}
if(info.toggleWith || info.nestedTo) {
const processToggleWith = info.toggleWith ? (info: T) => {
const {toggleWith, nested} = info;
const value = info.checkboxField.checked;
const arr = value ? toggleWith.checked : toggleWith.unchecked;
if(!arr) {
return;
}
const other = this.v.filter((i) => arr.includes(i.flags[0]));
other.forEach((info) => {
info.checkboxField.setValueSilently(value);
if(info.nestedTo && !nested) {
this.setNestedCounter(info.nestedTo);
} }
cancelEvent(e); if(info.toggleWith) {
toast('This option is disabled for all members in Group Permissions.'); processToggleWith(info);
info.checkboxField.checked = false;
}); */
attachClickEvent(info.checkboxField.label, (e) => {
toast(I18n.format(restrictionText, true));
}, {listenerSetter: options.listenerSetter});
}
if(this.toggleWith[mainFlag]) {
options.listenerSetter.add(info.checkboxField.input)('change', () => {
if(!info.checkboxField.checked) {
const other = this.v.filter((i) => this.toggleWith[mainFlag].includes(i.flags[0]));
other.forEach((info) => {
info.checkboxField.checked = false;
});
} }
}); });
}
options.appendTo.append(row.container); if(info.nested) {
this.setNestedCounter(info);
}
} : undefined;
const processNestedTo = info.nestedTo ? () => {
const length = this.getNestedCheckedLength(info.nestedTo);
info.nestedTo.checkboxField.setValueSilently(length === info.nestedTo.nested.length);
this.setNestedCounter(info.nestedTo, length);
} : undefined;
this.options.listenerSetter.add(info.checkboxField.input)('change', () => {
processToggleWith?.(info);
processNestedTo?.();
});
} }
const nodes: HTMLElement[] = [row.container];
let accordion: HTMLElement, nestedCounter: HTMLElement;
if(info.nested) {
const container = accordion = document.createElement('div');
container.classList.add('accordion');
container.style.setProperty('--max-height', info.nested.length * 48 + 'px');
info.nested.forEach((info) => {
container.append(...this.createRow(info, true).nodes);
});
nodes.push(container);
const span = document.createElement('span');
span.classList.add('tgico-down', 'accordion-icon');
nestedCounter = info.nestedCounter = document.createElement('b');
this.setNestedCounter(info);
row.title.append(' ', nestedCounter, ' ', span);
row.container.classList.add('accordion-toggler');
row.titleRow.classList.add('with-delimiter');
row.checkboxField.setValueSilently(this.getNestedCheckedLength(info) === info.nested.length);
}
return {row, nodes};
}
protected getNestedCheckedLength(info: T) {
return info.nested.reduce((acc, v) => acc + +v.checkboxField.checked, 0);
}
protected setNestedCounter(info: T, count = this.getNestedCheckedLength(info)) {
info.nestedCounter.textContent = `${count}/${info.nested.length}`;
} }
public takeOut() { public takeOut() {
@ -128,14 +221,23 @@ export class ChatPermissions {
pFlags: {} pFlags: {}
}; };
const IGNORE_FLAGS: Set<ChatRights> = new Set([
'send_media'
]);
for(const info of this.v) { for(const info of this.v) {
const banned = !info.checkboxField.checked; const banned = !info.checkboxField.checked;
if(banned) { if(!banned) {
info.flags.forEach((flag) => { continue;
// @ts-ignore
rights.pFlags[flag] = true;
});
} }
info.flags.forEach((flag) => {
if(IGNORE_FLAGS.has(flag)) {
return;
}
// @ts-ignore
rights.pFlags[flag] = true;
});
} }
return rights; return rights;
@ -195,7 +297,7 @@ export default class AppGroupPermissionsTab extends SliderSuperTabEventable {
const openPermissions = async(peerId: PeerId) => { const openPermissions = async(peerId: PeerId) => {
let participant: AppUserPermissionsTab['participant']; let participant: AppUserPermissionsTab['participant'];
try { try {
participant = await this.managers.appProfileManager.getChannelParticipant(this.chatId, peerId) as any; participant = await this.managers.appProfileManager.getParticipant(this.chatId, peerId);
} catch(err) { } catch(err) {
toast('User is no longer participant'); toast('User is no longer participant');
return; return;
@ -270,37 +372,35 @@ export default class AppGroupPermissionsTab extends SliderSuperTabEventable {
append append
}); });
setSubtitle(dom, participant); (dom.listEl as any).dialogDom = dom;
// dom.titleSpan.innerHTML = 'Chinaza Akachi'; setSubtitle(dom, participant);
// dom.lastMessageSpan.innerHTML = 'Can Add Users and Pin Messages';
}; };
// this.listenerSetter.add(rootScope)('updateChannelParticipant', (update: Update.updateChannelParticipant) => { this.listenerSetter.add(rootScope)('chat_participant', (update) => {
// const needAdd = update.new_participant?._ === 'channelParticipantBanned' && !update.new_participant.banned_rights.pFlags.view_messages; const needAdd = update.new_participant?._ === 'channelParticipantBanned' &&
// const li = list.querySelector(`[data-peer-id="${update.user_id}"]`); !update.new_participant.banned_rights.pFlags.view_messages;
// if(needAdd) { const li = list.querySelector(`[data-peer-id="${update.user_id}"]`);
// if(!li) { if(needAdd) {
// add(update.new_participant as ChannelParticipant.channelParticipantBanned, false); if(!li) {
// } else { add(update.new_participant as ChannelParticipant.channelParticipantBanned, false);
// setSubtitle(li, update.new_participant as ChannelParticipant.channelParticipantBanned); } else {
// } setSubtitle((li as any).dialogDom, update.new_participant as ChannelParticipant.channelParticipantBanned);
}
// if(update.prev_participant?._ !== 'channelParticipantBanned') { if(update.prev_participant?._ !== 'channelParticipantBanned') {
// ++exceptionsCount; ++exceptionsCount;
// } }
// } else { } else {
// if(li) { li?.remove();
// li.remove();
// }
// if(update.prev_participant?._ === 'channelParticipantBanned') { if(update.prev_participant?._ === 'channelParticipantBanned') {
// --exceptionsCount; --exceptionsCount;
// } }
// } }
// setLength(); setLength();
// }); });
const setLength = () => { const setLength = () => {
replaceContent(addExceptionRow.subtitle, i18n(exceptionsCount ? 'Permissions.ExceptionsCount' : 'Permissions.NoExceptions', [exceptionsCount])); replaceContent(addExceptionRow.subtitle, i18n(exceptionsCount ? 'Permissions.ExceptionsCount' : 'Permissions.NoExceptions', [exceptionsCount]));

View File

@ -7,16 +7,18 @@
import {attachClickEvent} from '../../../helpers/dom/clickEvent'; import {attachClickEvent} from '../../../helpers/dom/clickEvent';
import toggleDisability from '../../../helpers/dom/toggleDisability'; import toggleDisability from '../../../helpers/dom/toggleDisability';
import deepEqual from '../../../helpers/object/deepEqual'; import deepEqual from '../../../helpers/object/deepEqual';
import {ChannelParticipant} from '../../../layer'; import {ChannelParticipant, ChatParticipant} from '../../../layer';
import appDialogsManager from '../../../lib/appManagers/appDialogsManager'; import appDialogsManager from '../../../lib/appManagers/appDialogsManager';
import Button from '../../button'; import Button from '../../button';
import confirmationPopup from '../../confirmationPopup';
import SettingSection from '../../settingSection'; import SettingSection from '../../settingSection';
import {SliderSuperTabEventable} from '../../sliderTab'; import {SliderSuperTabEventable} from '../../sliderTab';
import getUserStatusString from '../../wrappers/getUserStatusString'; import getUserStatusString from '../../wrappers/getUserStatusString';
import wrapPeerTitle from '../../wrappers/peerTitle';
import {ChatPermissions} from './groupPermissions'; import {ChatPermissions} from './groupPermissions';
export default class AppUserPermissionsTab extends SliderSuperTabEventable { export default class AppUserPermissionsTab extends SliderSuperTabEventable {
public participant: ChannelParticipant; public participant: ChannelParticipant | ChatParticipant;
public chatId: ChatId; public chatId: ChatId;
public userId: UserId; public userId: UserId;
@ -26,6 +28,8 @@ export default class AppUserPermissionsTab extends SliderSuperTabEventable {
let destroyListener: () => void; let destroyListener: () => void;
const isChannel = await this.managers.appChatsManager.isChannel(this.chatId);
{ {
const section = new SettingSection({ const section = new SettingSection({
name: 'UserRestrictionsCanDo' name: 'UserRestrictionsCanDo'
@ -55,7 +59,6 @@ export default class AppUserPermissionsTab extends SliderSuperTabEventable {
}, this.managers); }, this.managers);
destroyListener = () => { destroyListener = () => {
// appChatsManager.editChatDefaultBannedRights(this.chatId, p.takeOut());
const rights = p.takeOut(); const rights = p.takeOut();
if(this.participant._ === 'channelParticipantBanned' && deepEqual(this.participant.banned_rights.pFlags, rights.pFlags)) { if(this.participant._ === 'channelParticipantBanned' && deepEqual(this.participant.banned_rights.pFlags, rights.pFlags)) {
return; return;
@ -77,7 +80,10 @@ export default class AppUserPermissionsTab extends SliderSuperTabEventable {
attachClickEvent(btnDeleteException, () => { attachClickEvent(btnDeleteException, () => {
const toggle = toggleDisability([btnDeleteException], true); const toggle = toggleDisability([btnDeleteException], true);
this.managers.appChatsManager.clearChannelParticipantBannedRights(this.chatId, this.participant).then(() => { this.managers.appChatsManager.clearChannelParticipantBannedRights(
this.chatId,
this.participant as ChannelParticipant.channelParticipantBanned
).then(() => {
this.eventListener.removeEventListener('destroy', destroyListener); this.eventListener.removeEventListener('destroy', destroyListener);
this.close(); this.close();
}, () => { }, () => {
@ -90,31 +96,34 @@ export default class AppUserPermissionsTab extends SliderSuperTabEventable {
const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'deleteuser', text: 'UserRestrictionsBlock'}); const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'deleteuser', text: 'UserRestrictionsBlock'});
attachClickEvent(btnDelete, () => { attachClickEvent(btnDelete, async() => {
const toggle = toggleDisability([btnDelete], true); const toggle = toggleDisability([btnDelete], true);
this.managers.appChatsManager.kickFromChannel(this.chatId, this.participant).then(() => {
this.eventListener.removeEventListener('destroy', destroyListener);
this.close();
});
/* new PopupPeer('popup-group-kick-user', {
peerId: -this.chatId,
title: 'Ban User?',
description: `Are you sure you want to ban <b>${appPeersManager.getPeerTitle(this.userId)}</b>`,
buttons: addCancelButton([{
text: 'BAN',
callback: () => {
const toggle = toggleDisability([btnDelete], true);
appChatsManager.kickFromChannel(this.chatId, this.participant).then(() => { try {
this.eventListener.removeEventListener('destroy', destroyListener); const peerId = this.userId.toPeerId();
this.close(); await confirmationPopup({
}, () => { peerId: this.chatId.toPeerId(true),
toggle(); descriptionLangKey: 'Permissions.RemoveFromGroup',
}); descriptionLangArgs: [await wrapPeerTitle({peerId: peerId})],
}, titleLangKey: 'ChannelBlockUser',
isDanger: true button: {
}]) langKey: 'Remove',
}).show(); */ isDanger: true
}
});
if(!isChannel) {
await this.managers.appChatsManager.kickFromChat(this.chatId, this.participant);
} else {
await this.managers.appChatsManager.kickFromChannel(this.chatId, this.participant as ChannelParticipant);
}
} catch(err) {
toggle();
return;
}
this.eventListener.removeEventListener('destroy', destroyListener);
this.close();
}, {listenerSetter: this.listenerSetter}); }, {listenerSetter: this.listenerSetter});
section.content.append(btnDelete); section.content.append(btnDelete);

View File

@ -98,7 +98,7 @@ export default class SwipeHandler {
private onSwipe: (xDiff: number, yDiff: number, e: EE, cancelDrag?: (x: boolean, y: boolean) => void) => boolean | void; private onSwipe: (xDiff: number, yDiff: number, e: EE, cancelDrag?: (x: boolean, y: boolean) => void) => boolean | void;
private verifyTouchTarget: (evt: EE) => boolean | Promise<boolean>; private verifyTouchTarget: (evt: EE) => boolean | Promise<boolean>;
private onFirstSwipe: (e: EE) => void; private onFirstSwipe: (e: EE) => void;
private onReset: () => void; private onReset: (e?: Event) => void;
private onStart: () => void; private onStart: () => void;
private onZoom: (details: ZoomDetails) => void; private onZoom: (details: ZoomDetails) => void;
private onDrag: (e: EE, captureEvent: E, details: {dragOffsetX: number, dragOffsetY: number}, cancelDrag: (x: boolean, y: boolean) => void) => void; private onDrag: (e: EE, captureEvent: E, details: {dragOffsetX: number, dragOffsetY: number}, cancelDrag: (x: boolean, y: boolean) => void) => void;
@ -252,7 +252,7 @@ export default class SwipeHandler {
} }
if(this.hadMove) { if(this.hadMove) {
this.onReset?.(); this.onReset?.(e);
} }
this.releaseWheelDrag?.clearTimeout(); this.releaseWheelDrag?.clearTimeout();
@ -291,10 +291,14 @@ export default class SwipeHandler {
} }
const e = getEvent(_e); const e = getEvent(_e);
if(Math.max(0, e.button ?? 0) !== 0) { if(![0, 1].includes(Math.max(0, e.button ?? 0))) {
return; return;
} }
if(e.button === 1) {
cancelEvent(_e as any);
}
if(isSwipingBackSafari(_e as any)) { if(isSwipingBackSafari(_e as any)) {
return; return;
} }

View File

@ -37,7 +37,7 @@ export default function wrapAlbum({messages, attachmentDiv, middleware, uploadin
// !lowest msgID will be the FIRST in album // !lowest msgID will be the FIRST in album
for(const message of messages) { for(const message of messages) {
const media: Photo.photo | Document.document = getMediaFromMessage(message); const media = getMediaFromMessage(message, true);
const size: any = media._ === 'photo' ? choosePhotoSize(media, 480, 480) : {w: media.w, h: media.h}; const size: any = media._ === 'photo' ? choosePhotoSize(media, 480, 480) : {w: media.w, h: media.h};
items.push({size, media, message}); items.push({size, media, message});

View File

@ -35,7 +35,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
isOut?: boolean, isOut?: boolean,
lazyLoadQueue?: LazyLoadQueue, lazyLoadQueue?: LazyLoadQueue,
middleware?: Middleware, middleware?: Middleware,
size?: PhotoSize | VideoSize, size?: PhotoSize | Extract<VideoSize, VideoSize.videoSize>,
withoutPreloader?: boolean, withoutPreloader?: boolean,
loadPromises?: Promise<any>[], loadPromises?: Promise<any>[],
autoDownloadSize?: number, autoDownloadSize?: number,

View File

@ -85,7 +85,7 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
skipRatio?: number, skipRatio?: number,
static?: boolean, static?: boolean,
managers?: AppManagers, managers?: AppManagers,
fullThumb?: PhotoSize | VideoSize, fullThumb?: PhotoSize | Extract<VideoSize, VideoSize.videoSize>,
isOut?: boolean, isOut?: boolean,
noPremium?: boolean, noPremium?: boolean,
withLock?: boolean, withLock?: boolean,

View File

@ -40,7 +40,7 @@ export default function wrapStickerAnimation({
skipRatio?: number, skipRatio?: number,
play: boolean, play: boolean,
managers?: AppManagers, managers?: AppManagers,
fullThumb?: PhotoSize | VideoSize, fullThumb?: PhotoSize | Extract<VideoSize, VideoSize.videoSize>,
withRandomOffset?: boolean, withRandomOffset?: boolean,
relativeEffect?: boolean, relativeEffect?: boolean,
loopEffect?: boolean loopEffect?: boolean

View File

@ -82,7 +82,7 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
loadPromises?: Promise<any>[], loadPromises?: Promise<any>[],
autoDownload?: ChatAutoDownloadSettings, autoDownload?: ChatAutoDownloadSettings,
photoSize?: PhotoSize, photoSize?: PhotoSize,
videoSize?: VideoSize, videoSize?: Extract<VideoSize, VideoSize.videoSize>,
searchContext?: MediaSearchContext, searchContext?: MediaSearchContext,
managers?: AppManagers, managers?: AppManagers,
noAutoplayAttribute?: boolean noAutoplayAttribute?: boolean

View File

@ -21,7 +21,7 @@ const App = {
version: process.env.VERSION, version: process.env.VERSION,
versionFull: process.env.VERSION_FULL, versionFull: process.env.VERSION_FULL,
build: +process.env.BUILD, build: +process.env.BUILD,
langPackVersion: '0.8.6', langPackVersion: '0.9.3',
langPack: 'webk', langPack: 'webk',
langPackCode: 'en', langPackCode: 'en',
domains: MAIN_DOMAINS, domains: MAIN_DOMAINS,

View File

@ -0,0 +1,8 @@
export type AUDIO_MIME_TYPE = 'audio/mpeg' | 'audio/aac' | 'audio/wav';
const AUDIO_MIME_TYPES_SUPPORTED: Set<AUDIO_MIME_TYPE> = new Set([
'audio/mpeg',
'audio/aac',
'audio/wav'
]);
export default AUDIO_MIME_TYPES_SUPPORTED;

5
src/global.d.ts vendored
View File

@ -56,7 +56,7 @@ declare global {
type LocalFileError = ApiFileManagerError | ReferenceError | StorageError; type LocalFileError = ApiFileManagerError | ReferenceError | StorageError;
type LocalErrorType = LocalFileError | NetworkerError | FiltersError | type LocalErrorType = LocalFileError | NetworkerError | FiltersError |
'UNKNOWN' | 'NO_DOC' | 'MIDDLEWARE' | 'PORT_DISCONNECTED' | 'NO_AUTO_DOWNLOAD'; 'UNKNOWN' | 'NO_DOC' | 'MIDDLEWARE' | 'PORT_DISCONNECTED' | 'NO_AUTO_DOWNLOAD' | 'CHAT_PRIVATE';
type ServerErrorType = 'FILE_REFERENCE_EXPIRED' | 'SESSION_REVOKED' | 'AUTH_KEY_DUPLICATED' | type ServerErrorType = 'FILE_REFERENCE_EXPIRED' | 'SESSION_REVOKED' | 'AUTH_KEY_DUPLICATED' |
'SESSION_PASSWORD_NEEDED' | 'CONNECTION_NOT_INITED' | 'ERROR_EMPTY' | 'MTPROTO_CLUSTER_INVALID' | 'SESSION_PASSWORD_NEEDED' | 'CONNECTION_NOT_INITED' | 'ERROR_EMPTY' | 'MTPROTO_CLUSTER_INVALID' |
@ -64,7 +64,8 @@ declare global {
'VOICE_MESSAGES_FORBIDDEN' | 'PHOTO_INVALID_DIMENSIONS' | 'PHOTO_SAVE_FILE_INVALID' | 'VOICE_MESSAGES_FORBIDDEN' | 'PHOTO_INVALID_DIMENSIONS' | 'PHOTO_SAVE_FILE_INVALID' |
'USER_ALREADY_PARTICIPANT' | 'USERNAME_INVALID' | 'USERNAME_PURCHASE_AVAILABLE' | 'USERNAMES_ACTIVE_TOO_MUCH' | 'USER_ALREADY_PARTICIPANT' | 'USERNAME_INVALID' | 'USERNAME_PURCHASE_AVAILABLE' | 'USERNAMES_ACTIVE_TOO_MUCH' |
'BOT_INVALID' | 'USERNAME_NOT_OCCUPIED' | 'PINNED_TOO_MUCH' | 'LOCATION_INVALID' | 'BOT_INVALID' | 'USERNAME_NOT_OCCUPIED' | 'PINNED_TOO_MUCH' | 'LOCATION_INVALID' |
'FILE_ID_INVALID' | 'CHANNEL_FORUM_MISSING' | 'TRANSCRIPTION_FAILED'; 'FILE_ID_INVALID' | 'CHANNEL_FORUM_MISSING' | 'TRANSCRIPTION_FAILED' | 'USER_NOT_PARTICIPANT' |
'PEER_ID_INVALID';
type ErrorType = LocalErrorType | ServerErrorType; type ErrorType = LocalErrorType | ServerErrorType;

View File

@ -8,6 +8,17 @@ import type ListenerSetter from '../listenerSetter';
import IS_TOUCH_SUPPORTED from '../../environment/touchSupport'; import IS_TOUCH_SUPPORTED from '../../environment/touchSupport';
import simulateEvent from './dispatchEvent'; import simulateEvent from './dispatchEvent';
let lastMouseDownElement: HTMLElement;
document.addEventListener('mousedown', (e) => {
lastMouseDownElement = e.target as HTMLElement;
});
export function hasMouseMovedSinceDown(e: Event) {
if(e.isTrusted && e.type === 'click' && e.target !== lastMouseDownElement) {
return true;
}
}
export const CLICK_EVENT_NAME: 'mousedown' /* | 'touchend' */ | 'click' = (IS_TOUCH_SUPPORTED ? 'mousedown' : 'click') as any; export const CLICK_EVENT_NAME: 'mousedown' /* | 'touchend' */ | 'click' = (IS_TOUCH_SUPPORTED ? 'mousedown' : 'click') as any;
export type AttachClickOptions = AddEventListenerOptions & Partial<{listenerSetter: ListenerSetter, touchMouseDown: true}>; export type AttachClickOptions = AddEventListenerOptions & Partial<{listenerSetter: ListenerSetter, touchMouseDown: true}>;
export function attachClickEvent(elem: HTMLElement | Window, callback: (e: /* TouchEvent | */MouseEvent) => void, options: AttachClickOptions = {}) { export function attachClickEvent(elem: HTMLElement | Window, callback: (e: /* TouchEvent | */MouseEvent) => void, options: AttachClickOptions = {}) {
@ -42,6 +53,18 @@ export function attachClickEvent(elem: HTMLElement | Window, callback: (e: /* To
} else { } else {
add(CLICK_EVENT_NAME, callback, options); add(CLICK_EVENT_NAME, callback, options);
} */ } */
if(CLICK_EVENT_NAME === 'click') {
const cb = callback;
callback = (e) => {
if(hasMouseMovedSinceDown(e)) {
return;
}
cb(e);
};
}
add(CLICK_EVENT_NAME, callback, options); add(CLICK_EVENT_NAME, callback, options);
// @ts-ignore // @ts-ignore

View File

@ -8,7 +8,7 @@ import appNavigationController, {NavigationItem} from '../components/appNavigati
import IS_TOUCH_SUPPORTED from '../environment/touchSupport'; import IS_TOUCH_SUPPORTED from '../environment/touchSupport';
import {IS_MOBILE_SAFARI} from '../environment/userAgent'; import {IS_MOBILE_SAFARI} from '../environment/userAgent';
import cancelEvent from './dom/cancelEvent'; import cancelEvent from './dom/cancelEvent';
import {CLICK_EVENT_NAME} from './dom/clickEvent'; import {CLICK_EVENT_NAME, hasMouseMovedSinceDown} from './dom/clickEvent';
import findUpAsChild from './dom/findUpAsChild'; import findUpAsChild from './dom/findUpAsChild';
import EventListenerBase from './eventListenerBase'; import EventListenerBase from './eventListenerBase';
@ -28,6 +28,10 @@ export default class OverlayClickHandler extends EventListenerBase<{
} }
protected onClick = (e: MouseEvent | TouchEvent) => { protected onClick = (e: MouseEvent | TouchEvent) => {
if(hasMouseMovedSinceDown(e)) {
return;
}
if(this.element && findUpAsChild(e.target as HTMLElement, this.element)) { if(this.element && findUpAsChild(e.target as HTMLElement, this.element)) {
return; return;
} }

View File

@ -82,6 +82,7 @@ const lang = {
'Message.Context.Selection.Clear': 'Clear selection', 'Message.Context.Selection.Clear': 'Clear selection',
'Message.Context.Selection.Delete': 'Delete selected', 'Message.Context.Selection.Delete': 'Delete selected',
'Message.Context.Selection.Forward': 'Forward selected', 'Message.Context.Selection.Forward': 'Forward selected',
'Message.Context.Selection.Download': 'Download selected',
'Message.Context.Selection.SendNow': 'Send Now selected', 'Message.Context.Selection.SendNow': 'Send Now selected',
'Message.Unsupported.Desktop': '__This message is currently not supported on Telegram Web. Try [getdesktop.telegram.org](https://getdesktop.telegram.org/)__', 'Message.Unsupported.Desktop': '__This message is currently not supported on Telegram Web. Try [getdesktop.telegram.org](https://getdesktop.telegram.org/)__',
'Message.Unsupported.Mobile': '__This message is currently not supported on Telegram Web. Try [telegram.org/dl](https://telegram.org/dl/)__', 'Message.Unsupported.Mobile': '__This message is currently not supported on Telegram Web. Try [telegram.org/dl](https://telegram.org/dl/)__',
@ -109,6 +110,7 @@ const lang = {
'one_value': '%d exception', 'one_value': '%d exception',
'other_value': '%d exceptions' 'other_value': '%d exceptions'
}, },
'Permissions.RemoveFromGroup': 'Are you sure you want to remove **%s** from the group?',
'PWA.Install': 'Install App', 'PWA.Install': 'Install App',
'Link.Available': 'Link is available', 'Link.Available': 'Link is available',
'Link.Taken': 'Link is already taken', 'Link.Taken': 'Link is already taken',
@ -400,6 +402,12 @@ const lang = {
'UserRestrictionsSendMedia': 'Send Media', 'UserRestrictionsSendMedia': 'Send Media',
'UserRestrictionsSendPolls': 'Send Polls', 'UserRestrictionsSendPolls': 'Send Polls',
'UserRestrictionsSendStickers': 'Send Stickers and GIFs', 'UserRestrictionsSendStickers': 'Send Stickers and GIFs',
'UserRestrictionsSendPhotos': 'Send Photos',
'UserRestrictionsSendVideos': 'Send Videos',
'UserRestrictionsSendMusic': 'Send Music',
'UserRestrictionsSendFiles': 'Send Files',
'UserRestrictionsSendVoices': 'Send Voice Messages',
'UserRestrictionsSendRound': 'Send Video Messages',
'UserRestrictionsEmbedLinks': 'Embed Links', 'UserRestrictionsEmbedLinks': 'Embed Links',
'UserRestrictionsChangeInfo': 'Change Chat Info', 'UserRestrictionsChangeInfo': 'Change Chat Info',
'UserRestrictionsPinMessages': 'Pin Messages', 'UserRestrictionsPinMessages': 'Pin Messages',
@ -409,6 +417,12 @@ const lang = {
'UserRestrictionsNoSendMedia': 'no media', 'UserRestrictionsNoSendMedia': 'no media',
'UserRestrictionsNoSendPolls': 'no polls', 'UserRestrictionsNoSendPolls': 'no polls',
'UserRestrictionsNoSendStickers': 'no stickers & GIFs', 'UserRestrictionsNoSendStickers': 'no stickers & GIFs',
'UserRestrictionsNoSendPhotos': 'no photos',
'UserRestrictionsNoSendVideos': 'no videos',
'UserRestrictionsNoSendMusic': 'no music',
'UserRestrictionsNoSendDocs': 'no files',
'UserRestrictionsNoSendVoice': 'no voice',
'UserRestrictionsNoSendRound': 'no round',
'UserRestrictionsNoEmbedLinks': 'no embed links', 'UserRestrictionsNoEmbedLinks': 'no embed links',
'UserRestrictionsNoChangeInfo': 'can\'t change Info', 'UserRestrictionsNoChangeInfo': 'can\'t change Info',
'UserRestrictionsNoPinMessages': 'no pins', 'UserRestrictionsNoPinMessages': 'no pins',
@ -892,6 +906,22 @@ const lang = {
'ThemeDay': 'Day', 'ThemeDay': 'Day',
'ThemeNight': 'Night', 'ThemeNight': 'Night',
'AutoNightSystemDefault': 'System Default', 'AutoNightSystemDefault': 'System Default',
'GlobalAttachPlainRestricted': 'Sending text messages isn\'t allowed in this group.',
'GlobalAttachDocumentsRestricted': 'Sending documents isn\'t allowed in this group.',
'GlobalAttachMediaRestricted': 'Sending media isn\'t allowed in this group.',
'GlobalAttachAudioRestricted': 'Sending music isn\'t allowed in this group.',
'GlobalAttachPhotoRestricted': 'Sending photos isn\'t allowed in this group.',
'GlobalAttachVideoRestricted': 'Sending videos isn\'t allowed in this group.',
'GlobalAttachVoiceRestricted': 'Sending voice isn\'t allowed in this group.',
'GlobalAttachRoundRestricted': 'Sending round videos isn\'t allowed in this group.',
'GlobalAttachInlineRestricted': 'Sending inline content isn\'t allowed in this group.',
'GlobalAttachStickersRestricted': 'Stickers aren\'t allowed in this group.',
'GlobalAttachGifRestricted': 'Sending GIFs is not allowed in this group.',
'GlobalAttachEmojiRestricted': 'Text messages aren\'t allowed in this group.',
'GlobalSendMessageRestricted': 'Sending messages is not allowed in this group.',
'ErrorSendRestrictedPollsAll': 'Sorry, sending polls is not allowed in this group.',
'Remove': 'Remove',
'ChannelBlockUser': 'Remove User',
// * macos // * macos
'AccountSettings.Filters': 'Chat Folders', 'AccountSettings.Filters': 'Chat Folders',
@ -1110,6 +1140,7 @@ const lang = {
'ChatList.Mute.3Days': 'For 3 Days', 'ChatList.Mute.3Days': 'For 3 Days',
'ChatList.Mute.Forever': 'Forever', 'ChatList.Mute.Forever': 'Forever',
'Channel.DescriptionHolderDescrpiton': 'You can provide an optional description for your channel.', 'Channel.DescriptionHolderDescrpiton': 'You can provide an optional description for your channel.',
'Channel.Persmission.MessageBlock': 'Text is not Allowed',
'ChannelVisibility.Confirm.MakePrivate.Channel': 'If you make this channel private, the name @%@ will be removed. Anyone else will be able to take it for their public groups or channels.', 'ChannelVisibility.Confirm.MakePrivate.Channel': 'If you make this channel private, the name @%@ will be removed. Anyone else will be able to take it for their public groups or channels.',
'ChannelVisibility.Confirm.MakePrivate.Group': 'If you make this group private, the name @%@ will be removed. Anyone else will be able to take it for their public groups or channels.', 'ChannelVisibility.Confirm.MakePrivate.Group': 'If you make this group private, the name @%@ will be removed. Anyone else will be able to take it for their public groups or channels.',
'Context.ViewStickerSet': 'View Sticker Set', 'Context.ViewStickerSet': 'View Sticker Set',

376
src/layer.d.ts vendored
View File

@ -285,7 +285,8 @@ export namespace InputChatPhoto {
flags?: number, flags?: number,
file?: InputFile, file?: InputFile,
video?: InputFile, video?: InputFile,
video_start_ts?: number video_start_ts?: number,
video_emoji_markup?: VideoSize
}; };
export type inputChatPhoto = { export type inputChatPhoto = {
@ -699,6 +700,7 @@ export namespace ChatFull {
pFlags: Partial<{ pFlags: Partial<{
can_set_username?: true, can_set_username?: true,
has_scheduled?: true, has_scheduled?: true,
translations_disabled?: true,
}>, }>,
id: string | number, id: string | number,
about: string, about: string,
@ -733,6 +735,7 @@ export namespace ChatFull {
can_delete_channel?: true, can_delete_channel?: true,
antispam?: true, antispam?: true,
participants_hidden?: true, participants_hidden?: true,
translations_disabled?: true,
}>, }>,
flags2?: number, flags2?: number,
id: string | number, id: string | number,
@ -1071,7 +1074,7 @@ export namespace MessageMedia {
/** /**
* @link https://core.telegram.org/type/MessageAction * @link https://core.telegram.org/type/MessageAction
*/ */
export type MessageAction = MessageAction.messageActionEmpty | MessageAction.messageActionChatCreate | MessageAction.messageActionChatEditTitle | MessageAction.messageActionChatEditPhoto | MessageAction.messageActionChatDeletePhoto | MessageAction.messageActionChatAddUser | MessageAction.messageActionChatDeleteUser | MessageAction.messageActionChatJoinedByLink | MessageAction.messageActionChannelCreate | MessageAction.messageActionChatMigrateTo | MessageAction.messageActionChannelMigrateFrom | MessageAction.messageActionPinMessage | MessageAction.messageActionHistoryClear | MessageAction.messageActionGameScore | MessageAction.messageActionPaymentSentMe | MessageAction.messageActionPaymentSent | MessageAction.messageActionPhoneCall | MessageAction.messageActionScreenshotTaken | MessageAction.messageActionCustomAction | MessageAction.messageActionBotAllowed | MessageAction.messageActionSecureValuesSentMe | MessageAction.messageActionSecureValuesSent | MessageAction.messageActionContactSignUp | MessageAction.messageActionGeoProximityReached | MessageAction.messageActionGroupCall | MessageAction.messageActionInviteToGroupCall | MessageAction.messageActionSetMessagesTTL | MessageAction.messageActionGroupCallScheduled | MessageAction.messageActionSetChatTheme | MessageAction.messageActionChatJoinedByRequest | MessageAction.messageActionWebViewDataSentMe | MessageAction.messageActionWebViewDataSent | MessageAction.messageActionGiftPremium | MessageAction.messageActionTopicCreate | MessageAction.messageActionTopicEdit | MessageAction.messageActionSuggestProfilePhoto | MessageAction.messageActionAttachMenuBotAllowed | MessageAction.messageActionDiscussionStarted | MessageAction.messageActionChatLeave | MessageAction.messageActionChannelDeletePhoto | MessageAction.messageActionChannelEditTitle | MessageAction.messageActionChannelEditPhoto | MessageAction.messageActionChannelEditVideo | MessageAction.messageActionChatEditVideo | MessageAction.messageActionChatAddUsers | MessageAction.messageActionChatJoined | MessageAction.messageActionChatReturn | MessageAction.messageActionChatJoinedYou | MessageAction.messageActionChatReturnYou; export type MessageAction = MessageAction.messageActionEmpty | MessageAction.messageActionChatCreate | MessageAction.messageActionChatEditTitle | MessageAction.messageActionChatEditPhoto | MessageAction.messageActionChatDeletePhoto | MessageAction.messageActionChatAddUser | MessageAction.messageActionChatDeleteUser | MessageAction.messageActionChatJoinedByLink | MessageAction.messageActionChannelCreate | MessageAction.messageActionChatMigrateTo | MessageAction.messageActionChannelMigrateFrom | MessageAction.messageActionPinMessage | MessageAction.messageActionHistoryClear | MessageAction.messageActionGameScore | MessageAction.messageActionPaymentSentMe | MessageAction.messageActionPaymentSent | MessageAction.messageActionPhoneCall | MessageAction.messageActionScreenshotTaken | MessageAction.messageActionCustomAction | MessageAction.messageActionBotAllowed | MessageAction.messageActionSecureValuesSentMe | MessageAction.messageActionSecureValuesSent | MessageAction.messageActionContactSignUp | MessageAction.messageActionGeoProximityReached | MessageAction.messageActionGroupCall | MessageAction.messageActionInviteToGroupCall | MessageAction.messageActionSetMessagesTTL | MessageAction.messageActionGroupCallScheduled | MessageAction.messageActionSetChatTheme | MessageAction.messageActionChatJoinedByRequest | MessageAction.messageActionWebViewDataSentMe | MessageAction.messageActionWebViewDataSent | MessageAction.messageActionGiftPremium | MessageAction.messageActionTopicCreate | MessageAction.messageActionTopicEdit | MessageAction.messageActionSuggestProfilePhoto | MessageAction.messageActionAttachMenuBotAllowed | MessageAction.messageActionRequestedPeer | MessageAction.messageActionDiscussionStarted | MessageAction.messageActionChatLeave | MessageAction.messageActionChannelDeletePhoto | MessageAction.messageActionChannelEditTitle | MessageAction.messageActionChannelEditPhoto | MessageAction.messageActionChannelEditVideo | MessageAction.messageActionChatEditVideo | MessageAction.messageActionChatAddUsers | MessageAction.messageActionChatJoined | MessageAction.messageActionChatReturn | MessageAction.messageActionChatJoinedYou | MessageAction.messageActionChatReturnYou;
export namespace MessageAction { export namespace MessageAction {
export type messageActionEmpty = { export type messageActionEmpty = {
@ -1296,6 +1299,12 @@ export namespace MessageAction {
_: 'messageActionAttachMenuBotAllowed' _: 'messageActionAttachMenuBotAllowed'
}; };
export type messageActionRequestedPeer = {
_: 'messageActionRequestedPeer',
button_id: number,
peer: Peer
};
export type messageActionDiscussionStarted = { export type messageActionDiscussionStarted = {
_: 'messageActionDiscussionStarted' _: 'messageActionDiscussionStarted'
}; };
@ -1527,7 +1536,7 @@ export namespace GeoPoint {
/** /**
* @link https://core.telegram.org/type/auth.SentCode * @link https://core.telegram.org/type/auth.SentCode
*/ */
export type AuthSentCode = AuthSentCode.authSentCode; export type AuthSentCode = AuthSentCode.authSentCode | AuthSentCode.authSentCodeSuccess;
export namespace AuthSentCode { export namespace AuthSentCode {
export type authSentCode = { export type authSentCode = {
@ -1539,6 +1548,11 @@ export namespace AuthSentCode {
timeout?: number, timeout?: number,
phone_number?: string phone_number?: string
}; };
export type authSentCodeSuccess = {
_: 'auth.sentCodeSuccess',
authorization: AuthAuthorization
};
} }
/** /**
@ -1555,6 +1569,7 @@ export namespace AuthAuthorization {
}>, }>,
otherwise_relogin_days?: number, otherwise_relogin_days?: number,
tmp_sessions?: number, tmp_sessions?: number,
future_auth_token?: Uint8Array,
user: User user: User
}; };
@ -1766,6 +1781,7 @@ export namespace UserFull {
has_scheduled?: true, has_scheduled?: true,
video_calls_available?: true, video_calls_available?: true,
voice_messages_forbidden?: true, voice_messages_forbidden?: true,
translations_disabled?: true,
}>, }>,
id: string | number, id: string | number,
about?: string, about?: string,
@ -2086,7 +2102,7 @@ export namespace MessagesFilter {
/** /**
* @link https://core.telegram.org/type/Update * @link https://core.telegram.org/type/Update
*/ */
export type Update = Update.updateNewMessage | Update.updateMessageID | Update.updateDeleteMessages | Update.updateUserTyping | Update.updateChatUserTyping | Update.updateChatParticipants | Update.updateUserStatus | Update.updateUserName | Update.updateNewEncryptedMessage | Update.updateEncryptedChatTyping | Update.updateEncryption | Update.updateEncryptedMessagesRead | Update.updateChatParticipantAdd | Update.updateChatParticipantDelete | Update.updateDcOptions | Update.updateNotifySettings | Update.updateServiceNotification | Update.updatePrivacy | Update.updateUserPhone | Update.updateReadHistoryInbox | Update.updateReadHistoryOutbox | Update.updateWebPage | Update.updateReadMessagesContents | Update.updateChannelTooLong | Update.updateChannel | Update.updateNewChannelMessage | Update.updateReadChannelInbox | Update.updateDeleteChannelMessages | Update.updateChannelMessageViews | Update.updateChatParticipantAdmin | Update.updateNewStickerSet | Update.updateStickerSetsOrder | Update.updateStickerSets | Update.updateSavedGifs | Update.updateBotInlineQuery | Update.updateBotInlineSend | Update.updateEditChannelMessage | Update.updateBotCallbackQuery | Update.updateEditMessage | Update.updateInlineBotCallbackQuery | Update.updateReadChannelOutbox | Update.updateDraftMessage | Update.updateReadFeaturedStickers | Update.updateRecentStickers | Update.updateConfig | Update.updatePtsChanged | Update.updateChannelWebPage | Update.updateDialogPinned | Update.updatePinnedDialogs | Update.updateBotWebhookJSON | Update.updateBotWebhookJSONQuery | Update.updateBotShippingQuery | Update.updateBotPrecheckoutQuery | Update.updatePhoneCall | Update.updateLangPackTooLong | Update.updateLangPack | Update.updateFavedStickers | Update.updateChannelReadMessagesContents | Update.updateContactsReset | Update.updateChannelAvailableMessages | Update.updateDialogUnreadMark | Update.updateMessagePoll | Update.updateChatDefaultBannedRights | Update.updateFolderPeers | Update.updatePeerSettings | Update.updatePeerLocated | Update.updateNewScheduledMessage | Update.updateDeleteScheduledMessages | Update.updateTheme | Update.updateGeoLiveViewed | Update.updateLoginToken | Update.updateMessagePollVote | Update.updateDialogFilter | Update.updateDialogFilterOrder | Update.updateDialogFilters | Update.updatePhoneCallSignalingData | Update.updateChannelMessageForwards | Update.updateReadChannelDiscussionInbox | Update.updateReadChannelDiscussionOutbox | Update.updatePeerBlocked | Update.updateChannelUserTyping | Update.updatePinnedMessages | Update.updatePinnedChannelMessages | Update.updateChat | Update.updateGroupCallParticipants | Update.updateGroupCall | Update.updatePeerHistoryTTL | Update.updateChatParticipant | Update.updateChannelParticipant | Update.updateBotStopped | Update.updateGroupCallConnection | Update.updateBotCommands | Update.updatePendingJoinRequests | Update.updateBotChatInviteRequester | Update.updateMessageReactions | Update.updateAttachMenuBots | Update.updateWebViewResultSent | Update.updateBotMenuButton | Update.updateSavedRingtones | Update.updateTranscribedAudio | Update.updateReadFeaturedEmojiStickers | Update.updateUserEmojiStatus | Update.updateRecentEmojiStatuses | Update.updateRecentReactions | Update.updateMoveStickerSetToTop | Update.updateMessageExtendedMedia | Update.updateChannelPinnedTopic | Update.updateChannelPinnedTopics | Update.updateUser | Update.updateNewDiscussionMessage | Update.updateDeleteDiscussionMessages | Update.updateChannelReload; export type Update = Update.updateNewMessage | Update.updateMessageID | Update.updateDeleteMessages | Update.updateUserTyping | Update.updateChatUserTyping | Update.updateChatParticipants | Update.updateUserStatus | Update.updateUserName | Update.updateNewEncryptedMessage | Update.updateEncryptedChatTyping | Update.updateEncryption | Update.updateEncryptedMessagesRead | Update.updateChatParticipantAdd | Update.updateChatParticipantDelete | Update.updateDcOptions | Update.updateNotifySettings | Update.updateServiceNotification | Update.updatePrivacy | Update.updateUserPhone | Update.updateReadHistoryInbox | Update.updateReadHistoryOutbox | Update.updateWebPage | Update.updateReadMessagesContents | Update.updateChannelTooLong | Update.updateChannel | Update.updateNewChannelMessage | Update.updateReadChannelInbox | Update.updateDeleteChannelMessages | Update.updateChannelMessageViews | Update.updateChatParticipantAdmin | Update.updateNewStickerSet | Update.updateStickerSetsOrder | Update.updateStickerSets | Update.updateSavedGifs | Update.updateBotInlineQuery | Update.updateBotInlineSend | Update.updateEditChannelMessage | Update.updateBotCallbackQuery | Update.updateEditMessage | Update.updateInlineBotCallbackQuery | Update.updateReadChannelOutbox | Update.updateDraftMessage | Update.updateReadFeaturedStickers | Update.updateRecentStickers | Update.updateConfig | Update.updatePtsChanged | Update.updateChannelWebPage | Update.updateDialogPinned | Update.updatePinnedDialogs | Update.updateBotWebhookJSON | Update.updateBotWebhookJSONQuery | Update.updateBotShippingQuery | Update.updateBotPrecheckoutQuery | Update.updatePhoneCall | Update.updateLangPackTooLong | Update.updateLangPack | Update.updateFavedStickers | Update.updateChannelReadMessagesContents | Update.updateContactsReset | Update.updateChannelAvailableMessages | Update.updateDialogUnreadMark | Update.updateMessagePoll | Update.updateChatDefaultBannedRights | Update.updateFolderPeers | Update.updatePeerSettings | Update.updatePeerLocated | Update.updateNewScheduledMessage | Update.updateDeleteScheduledMessages | Update.updateTheme | Update.updateGeoLiveViewed | Update.updateLoginToken | Update.updateMessagePollVote | Update.updateDialogFilter | Update.updateDialogFilterOrder | Update.updateDialogFilters | Update.updatePhoneCallSignalingData | Update.updateChannelMessageForwards | Update.updateReadChannelDiscussionInbox | Update.updateReadChannelDiscussionOutbox | Update.updatePeerBlocked | Update.updateChannelUserTyping | Update.updatePinnedMessages | Update.updatePinnedChannelMessages | Update.updateChat | Update.updateGroupCallParticipants | Update.updateGroupCall | Update.updatePeerHistoryTTL | Update.updateChatParticipant | Update.updateChannelParticipant | Update.updateBotStopped | Update.updateGroupCallConnection | Update.updateBotCommands | Update.updatePendingJoinRequests | Update.updateBotChatInviteRequester | Update.updateMessageReactions | Update.updateAttachMenuBots | Update.updateWebViewResultSent | Update.updateBotMenuButton | Update.updateSavedRingtones | Update.updateTranscribedAudio | Update.updateReadFeaturedEmojiStickers | Update.updateUserEmojiStatus | Update.updateRecentEmojiStatuses | Update.updateRecentReactions | Update.updateMoveStickerSetToTop | Update.updateMessageExtendedMedia | Update.updateChannelPinnedTopic | Update.updateChannelPinnedTopics | Update.updateUser | Update.updateAutoSaveSettings | Update.updateNewDiscussionMessage | Update.updateDeleteDiscussionMessages | Update.updateChannelReload;
export namespace Update { export namespace Update {
export type updateNewMessage = { export type updateNewMessage = {
@ -2868,6 +2884,10 @@ export namespace Update {
user_id: string | number user_id: string | number
}; };
export type updateAutoSaveSettings = {
_: 'updateAutoSaveSettings'
};
export type updateNewDiscussionMessage = { export type updateNewDiscussionMessage = {
_: 'updateNewDiscussionMessage', _: 'updateNewDiscussionMessage',
message?: Message message?: Message
@ -4325,7 +4345,7 @@ export namespace BotInfo {
/** /**
* @link https://core.telegram.org/type/KeyboardButton * @link https://core.telegram.org/type/KeyboardButton
*/ */
export type KeyboardButton = KeyboardButton.keyboardButton | KeyboardButton.keyboardButtonUrl | KeyboardButton.keyboardButtonCallback | KeyboardButton.keyboardButtonRequestPhone | KeyboardButton.keyboardButtonRequestGeoLocation | KeyboardButton.keyboardButtonSwitchInline | KeyboardButton.keyboardButtonGame | KeyboardButton.keyboardButtonBuy | KeyboardButton.keyboardButtonUrlAuth | KeyboardButton.inputKeyboardButtonUrlAuth | KeyboardButton.keyboardButtonRequestPoll | KeyboardButton.inputKeyboardButtonUserProfile | KeyboardButton.keyboardButtonUserProfile | KeyboardButton.keyboardButtonWebView | KeyboardButton.keyboardButtonSimpleWebView; export type KeyboardButton = KeyboardButton.keyboardButton | KeyboardButton.keyboardButtonUrl | KeyboardButton.keyboardButtonCallback | KeyboardButton.keyboardButtonRequestPhone | KeyboardButton.keyboardButtonRequestGeoLocation | KeyboardButton.keyboardButtonSwitchInline | KeyboardButton.keyboardButtonGame | KeyboardButton.keyboardButtonBuy | KeyboardButton.keyboardButtonUrlAuth | KeyboardButton.inputKeyboardButtonUrlAuth | KeyboardButton.keyboardButtonRequestPoll | KeyboardButton.inputKeyboardButtonUserProfile | KeyboardButton.keyboardButtonUserProfile | KeyboardButton.keyboardButtonWebView | KeyboardButton.keyboardButtonSimpleWebView | KeyboardButton.keyboardButtonRequestPeer;
export namespace KeyboardButton { export namespace KeyboardButton {
export type keyboardButton = { export type keyboardButton = {
@ -4430,6 +4450,13 @@ export namespace KeyboardButton {
text: string, text: string,
url: string url: string
}; };
export type keyboardButtonRequestPeer = {
_: 'keyboardButtonRequestPeer',
text: string,
button_id: number,
peer_type: RequestPeerType
};
} }
/** /**
@ -5268,7 +5295,7 @@ export namespace AuthCodeType {
/** /**
* @link https://core.telegram.org/type/auth.SentCodeType * @link https://core.telegram.org/type/auth.SentCodeType
*/ */
export type AuthSentCodeType = AuthSentCodeType.authSentCodeTypeApp | AuthSentCodeType.authSentCodeTypeSms | AuthSentCodeType.authSentCodeTypeCall | AuthSentCodeType.authSentCodeTypeFlashCall | AuthSentCodeType.authSentCodeTypeMissedCall | AuthSentCodeType.authSentCodeTypeEmailCode | AuthSentCodeType.authSentCodeTypeSetUpEmailRequired | AuthSentCodeType.authSentCodeTypeFragmentSms; export type AuthSentCodeType = AuthSentCodeType.authSentCodeTypeApp | AuthSentCodeType.authSentCodeTypeSms | AuthSentCodeType.authSentCodeTypeCall | AuthSentCodeType.authSentCodeTypeFlashCall | AuthSentCodeType.authSentCodeTypeMissedCall | AuthSentCodeType.authSentCodeTypeEmailCode | AuthSentCodeType.authSentCodeTypeSetUpEmailRequired | AuthSentCodeType.authSentCodeTypeFragmentSms | AuthSentCodeType.authSentCodeTypeFirebaseSms;
export namespace AuthSentCodeType { export namespace AuthSentCodeType {
export type authSentCodeTypeApp = { export type authSentCodeTypeApp = {
@ -5323,6 +5350,15 @@ export namespace AuthSentCodeType {
url: string, url: string,
length: number length: number
}; };
export type authSentCodeTypeFirebaseSms = {
_: 'auth.sentCodeTypeFirebaseSms',
flags?: number,
nonce?: Uint8Array,
receipt?: string,
push_timeout?: number,
length: number
};
} }
/** /**
@ -8090,6 +8126,13 @@ export namespace ChatBannedRights {
invite_users?: true, invite_users?: true,
pin_messages?: true, pin_messages?: true,
manage_topics?: true, manage_topics?: true,
send_photos?: true,
send_videos?: true,
send_roundvideos?: true,
send_audios?: true,
send_voices?: true,
send_docs?: true,
send_plain?: true,
}>, }>,
until_date: number until_date: number
}; };
@ -8149,8 +8192,11 @@ export namespace CodeSettings {
current_number?: true, current_number?: true,
allow_app_hash?: true, allow_app_hash?: true,
allow_missed_call?: true, allow_missed_call?: true,
allow_firebase?: true,
}>, }>,
logout_tokens?: Array<Uint8Array> logout_tokens?: Array<Uint8Array>,
token?: string,
app_sandbox?: boolean
}; };
} }
@ -8857,7 +8903,7 @@ export namespace HelpPromoData {
/** /**
* @link https://core.telegram.org/type/VideoSize * @link https://core.telegram.org/type/VideoSize
*/ */
export type VideoSize = VideoSize.videoSize; export type VideoSize = VideoSize.videoSize | VideoSize.videoSizeEmojiMarkup | VideoSize.videoSizeStickerMarkup;
export namespace VideoSize { export namespace VideoSize {
export type videoSize = { export type videoSize = {
@ -8869,6 +8915,19 @@ export namespace VideoSize {
size: number, size: number,
video_start_ts?: number video_start_ts?: number
}; };
export type videoSizeEmojiMarkup = {
_: 'videoSizeEmojiMarkup',
emoji_id: string | number,
background_colors: Array<number>
};
export type videoSizeStickerMarkup = {
_: 'videoSizeStickerMarkup',
stickerset: InputStickerSet,
sticker_id: string | number,
background_colors: Array<number>
};
} }
/** /**
@ -9798,22 +9857,6 @@ export namespace MessagesAvailableReactions {
}; };
} }
/**
* @link https://core.telegram.org/type/messages.TranslatedText
*/
export type MessagesTranslatedText = MessagesTranslatedText.messagesTranslateNoResult | MessagesTranslatedText.messagesTranslateResultText;
export namespace MessagesTranslatedText {
export type messagesTranslateNoResult = {
_: 'messages.translateNoResult'
};
export type messagesTranslateResultText = {
_: 'messages.translateResultText',
text: string
};
}
/** /**
* @link https://core.telegram.org/type/MessagePeerReaction * @link https://core.telegram.org/type/MessagePeerReaction
*/ */
@ -10171,6 +10214,7 @@ export namespace InputStorePaymentPurpose {
flags?: number, flags?: number,
pFlags: Partial<{ pFlags: Partial<{
restore?: true, restore?: true,
upgrade?: true,
}> }>
}; };
@ -10212,6 +10256,19 @@ export namespace PaymentFormMethod {
}; };
} }
/**
* @link https://core.telegram.org/type/bots.PremiumGiftsOptions
*/
export type BotsPremiumGiftsOptions = BotsPremiumGiftsOptions.botsPremiumGiftsOptions;
export namespace BotsPremiumGiftsOptions {
export type botsPremiumGiftsOptions = {
_: 'bots.premiumGiftsOptions',
name: string,
gifts: Array<PremiumGiftOption>
};
}
/** /**
* @link https://core.telegram.org/type/EmojiStatus * @link https://core.telegram.org/type/EmojiStatus
*/ */
@ -10387,6 +10444,7 @@ export namespace PremiumSubscriptionOption {
current?: true, current?: true,
can_purchase_upgrade?: true, can_purchase_upgrade?: true,
}>, }>,
transaction?: string,
months: number, months: number,
currency: string, currency: string,
amount: string | number, amount: string | number,
@ -10549,6 +10607,164 @@ export namespace ExportedContactToken {
}; };
} }
/**
* @link https://core.telegram.org/type/RequestPeerType
*/
export type RequestPeerType = RequestPeerType.requestPeerTypeUser | RequestPeerType.requestPeerTypeChat | RequestPeerType.requestPeerTypeBroadcast;
export namespace RequestPeerType {
export type requestPeerTypeUser = {
_: 'requestPeerTypeUser',
flags?: number,
bot?: boolean,
premium?: boolean
};
export type requestPeerTypeChat = {
_: 'requestPeerTypeChat',
flags?: number,
pFlags: Partial<{
creator?: true,
bot_participant?: true,
}>,
has_username?: boolean,
forum?: boolean,
user_admin_rights?: ChatAdminRights,
bot_admin_rights?: ChatAdminRights
};
export type requestPeerTypeBroadcast = {
_: 'requestPeerTypeBroadcast',
flags?: number,
pFlags: Partial<{
creator?: true,
}>,
has_username?: boolean,
user_admin_rights?: ChatAdminRights,
bot_admin_rights?: ChatAdminRights
};
}
/**
* @link https://core.telegram.org/type/EmojiList
*/
export type EmojiList = EmojiList.emojiListNotModified | EmojiList.emojiList;
export namespace EmojiList {
export type emojiListNotModified = {
_: 'emojiListNotModified'
};
export type emojiList = {
_: 'emojiList',
hash: string | number,
document_id: Array<string | number>
};
}
/**
* @link https://core.telegram.org/type/EmojiGroup
*/
export type EmojiGroup = EmojiGroup.emojiGroup;
export namespace EmojiGroup {
export type emojiGroup = {
_: 'emojiGroup',
title: string,
icon_emoji_id: string | number,
emoticons: Array<string>
};
}
/**
* @link https://core.telegram.org/type/messages.EmojiGroups
*/
export type MessagesEmojiGroups = MessagesEmojiGroups.messagesEmojiGroupsNotModified | MessagesEmojiGroups.messagesEmojiGroups;
export namespace MessagesEmojiGroups {
export type messagesEmojiGroupsNotModified = {
_: 'messages.emojiGroupsNotModified'
};
export type messagesEmojiGroups = {
_: 'messages.emojiGroups',
hash: number,
groups: Array<EmojiGroup>
};
}
/**
* @link https://core.telegram.org/type/TextWithEntities
*/
export type TextWithEntities = TextWithEntities.textWithEntities;
export namespace TextWithEntities {
export type textWithEntities = {
_: 'textWithEntities',
text: string,
entities: Array<MessageEntity>
};
}
/**
* @link https://core.telegram.org/type/messages.TranslatedText
*/
export type MessagesTranslatedText = MessagesTranslatedText.messagesTranslateResult;
export namespace MessagesTranslatedText {
export type messagesTranslateResult = {
_: 'messages.translateResult',
result: Array<TextWithEntities>
};
}
/**
* @link https://core.telegram.org/type/AutoSaveSettings
*/
export type AutoSaveSettings = AutoSaveSettings.autoSaveSettings;
export namespace AutoSaveSettings {
export type autoSaveSettings = {
_: 'autoSaveSettings',
flags?: number,
pFlags: Partial<{
photos?: true,
videos?: true,
}>,
video_max_size?: string | number
};
}
/**
* @link https://core.telegram.org/type/AutoSaveException
*/
export type AutoSaveException = AutoSaveException.autoSaveException;
export namespace AutoSaveException {
export type autoSaveException = {
_: 'autoSaveException',
peer: Peer,
settings: AutoSaveSettings
};
}
/**
* @link https://core.telegram.org/type/account.AutoSaveSettings
*/
export type AccountAutoSaveSettings = AccountAutoSaveSettings.accountAutoSaveSettings;
export namespace AccountAutoSaveSettings {
export type accountAutoSaveSettings = {
_: 'account.autoSaveSettings',
users_settings: AutoSaveSettings,
chats_settings: AutoSaveSettings,
broadcasts_settings: AutoSaveSettings,
exceptions: Array<AutoSaveException>,
chats: Array<Chat>,
users: Array<User>
};
}
export interface ConstructorDeclMap { export interface ConstructorDeclMap {
'error': Error.error, 'error': Error.error,
'inputPeerEmpty': InputPeer.inputPeerEmpty, 'inputPeerEmpty': InputPeer.inputPeerEmpty,
@ -11495,8 +11711,6 @@ export interface ConstructorDeclMap {
'messages.availableReactions': MessagesAvailableReactions.messagesAvailableReactions, 'messages.availableReactions': MessagesAvailableReactions.messagesAvailableReactions,
'messageEntitySpoiler': MessageEntity.messageEntitySpoiler, 'messageEntitySpoiler': MessageEntity.messageEntitySpoiler,
'channelAdminLogEventActionChangeAvailableReactions': ChannelAdminLogEventAction.channelAdminLogEventActionChangeAvailableReactions, 'channelAdminLogEventActionChangeAvailableReactions': ChannelAdminLogEventAction.channelAdminLogEventActionChangeAvailableReactions,
'messages.translateNoResult': MessagesTranslatedText.messagesTranslateNoResult,
'messages.translateResultText': MessagesTranslatedText.messagesTranslateResultText,
'messagePeerReaction': MessagePeerReaction.messagePeerReaction, 'messagePeerReaction': MessagePeerReaction.messagePeerReaction,
'groupCallStreamChannel': GroupCallStreamChannel.groupCallStreamChannel, 'groupCallStreamChannel': GroupCallStreamChannel.groupCallStreamChannel,
'phone.groupCallStreamChannels': PhoneGroupCallStreamChannels.phoneGroupCallStreamChannels, 'phone.groupCallStreamChannels': PhoneGroupCallStreamChannels.phoneGroupCallStreamChannels,
@ -11557,6 +11771,7 @@ export interface ConstructorDeclMap {
'privacyKeyVoiceMessages': PrivacyKey.privacyKeyVoiceMessages, 'privacyKeyVoiceMessages': PrivacyKey.privacyKeyVoiceMessages,
'paymentFormMethod': PaymentFormMethod.paymentFormMethod, 'paymentFormMethod': PaymentFormMethod.paymentFormMethod,
'inputWebFileAudioAlbumThumbLocation': InputWebFileLocation.inputWebFileAudioAlbumThumbLocation, 'inputWebFileAudioAlbumThumbLocation': InputWebFileLocation.inputWebFileAudioAlbumThumbLocation,
'bots.premiumGiftsOptions': BotsPremiumGiftsOptions.botsPremiumGiftsOptions,
'emojiStatusEmpty': EmojiStatus.emojiStatusEmpty, 'emojiStatusEmpty': EmojiStatus.emojiStatusEmpty,
'emojiStatus': EmojiStatus.emojiStatus, 'emojiStatus': EmojiStatus.emojiStatus,
'emojiStatusUntil': EmojiStatus.emojiStatusUntil, 'emojiStatusUntil': EmojiStatus.emojiStatusUntil,
@ -11619,6 +11834,26 @@ export interface ConstructorDeclMap {
'messageActionAttachMenuBotAllowed': MessageAction.messageActionAttachMenuBotAllowed, 'messageActionAttachMenuBotAllowed': MessageAction.messageActionAttachMenuBotAllowed,
'stickerSetNoCovered': StickerSetCovered.stickerSetNoCovered, 'stickerSetNoCovered': StickerSetCovered.stickerSetNoCovered,
'updateUser': Update.updateUser, 'updateUser': Update.updateUser,
'auth.sentCodeSuccess': AuthSentCode.authSentCodeSuccess,
'messageActionRequestedPeer': MessageAction.messageActionRequestedPeer,
'requestPeerTypeUser': RequestPeerType.requestPeerTypeUser,
'requestPeerTypeChat': RequestPeerType.requestPeerTypeChat,
'requestPeerTypeBroadcast': RequestPeerType.requestPeerTypeBroadcast,
'keyboardButtonRequestPeer': KeyboardButton.keyboardButtonRequestPeer,
'emojiListNotModified': EmojiList.emojiListNotModified,
'emojiList': EmojiList.emojiList,
'auth.sentCodeTypeFirebaseSms': AuthSentCodeType.authSentCodeTypeFirebaseSms,
'emojiGroup': EmojiGroup.emojiGroup,
'messages.emojiGroupsNotModified': MessagesEmojiGroups.messagesEmojiGroupsNotModified,
'messages.emojiGroups': MessagesEmojiGroups.messagesEmojiGroups,
'videoSizeEmojiMarkup': VideoSize.videoSizeEmojiMarkup,
'videoSizeStickerMarkup': VideoSize.videoSizeStickerMarkup,
'textWithEntities': TextWithEntities.textWithEntities,
'messages.translateResult': MessagesTranslatedText.messagesTranslateResult,
'autoSaveSettings': AutoSaveSettings.autoSaveSettings,
'autoSaveException': AutoSaveException.autoSaveException,
'account.autoSaveSettings': AccountAutoSaveSettings.accountAutoSaveSettings,
'updateAutoSaveSettings': Update.updateAutoSaveSettings,
'messageEntityEmoji': MessageEntity.messageEntityEmoji, 'messageEntityEmoji': MessageEntity.messageEntityEmoji,
'messageEntityHighlight': MessageEntity.messageEntityHighlight, 'messageEntityHighlight': MessageEntity.messageEntityHighlight,
'messageEntityLinebreak': MessageEntity.messageEntityLinebreak, 'messageEntityLinebreak': MessageEntity.messageEntityLinebreak,
@ -11998,7 +12233,8 @@ export type PhotosUploadProfilePhoto = {
fallback?: boolean, fallback?: boolean,
file?: InputFile, file?: InputFile,
video?: InputFile, video?: InputFile,
video_start_ts?: number video_start_ts?: number,
video_emoji_markup?: VideoSize
}; };
export type PhotosDeletePhotos = { export type PhotosDeletePhotos = {
@ -12353,6 +12589,7 @@ export type ChannelsCreateChannel = {
broadcast?: boolean, broadcast?: boolean,
megagroup?: boolean, megagroup?: boolean,
for_import?: boolean, for_import?: boolean,
forum?: boolean,
title: string, title: string,
about: string, about: string,
geo_point?: InputGeoPoint, geo_point?: InputGeoPoint,
@ -12807,6 +13044,8 @@ export type StickersCreateStickerSet = {
masks?: boolean, masks?: boolean,
animated?: boolean, animated?: boolean,
videos?: boolean, videos?: boolean,
emojis?: boolean,
text_color?: boolean,
user_id: InputUser, user_id: InputUser,
title: string, title: string,
short_name: string, short_name: string,
@ -14034,9 +14273,8 @@ export type MessagesSetDefaultReaction = {
export type MessagesTranslateText = { export type MessagesTranslateText = {
flags?: number, flags?: number,
peer?: InputPeer, peer?: InputPeer,
msg_id?: number, id?: Array<number>,
text?: string, text?: Array<TextWithEntities>,
from_lang?: string,
to_lang: string to_lang: string
}; };
@ -14392,7 +14630,8 @@ export type PhotosUploadContactProfilePhoto = {
user_id: InputUser, user_id: InputUser,
file?: InputFile, file?: InputFile,
video?: InputFile, video?: InputFile,
video_start_ts?: number video_start_ts?: number,
video_emoji_markup?: VideoSize
}; };
export type ChannelsToggleParticipantsHidden = { export type ChannelsToggleParticipantsHidden = {
@ -14400,6 +14639,69 @@ export type ChannelsToggleParticipantsHidden = {
enabled: boolean enabled: boolean
}; };
export type MessagesSendBotRequestedPeer = {
peer: InputPeer,
msg_id: number,
button_id: number,
requested_peer: InputPeer
};
export type AccountGetDefaultProfilePhotoEmojis = {
hash: string | number
};
export type AccountGetDefaultGroupPhotoEmojis = {
hash: string | number
};
export type AuthRequestFirebaseSms = {
flags?: number,
phone_number: string,
phone_code_hash: string,
safety_net_token?: string,
ios_push_secret?: string
};
export type MessagesGetEmojiGroups = {
hash: number
};
export type MessagesGetEmojiStatusGroups = {
hash: number
};
export type MessagesGetEmojiProfilePhotoGroups = {
hash: number
};
export type MessagesSearchCustomEmoji = {
emoticon: string,
hash: string | number
};
export type MessagesTogglePeerTranslations = {
flags?: number,
disabled?: boolean,
peer: InputPeer
};
export type AccountGetAutoSaveSettings = {
};
export type AccountSaveAutoSaveSettings = {
flags?: number,
users?: boolean,
chats?: boolean,
broadcasts?: boolean,
peer?: InputPeer,
settings: AutoSaveSettings
};
export type AccountDeleteAutoSaveExceptions = {
};
export interface MethodDeclMap { export interface MethodDeclMap {
'invokeAfterMsg': {req: InvokeAfterMsg, res: any}, 'invokeAfterMsg': {req: InvokeAfterMsg, res: any},
'invokeAfterMsgs': {req: InvokeAfterMsgs, res: any}, 'invokeAfterMsgs': {req: InvokeAfterMsgs, res: any},
@ -14879,5 +15181,17 @@ export interface MethodDeclMap {
'contacts.importContactToken': {req: ContactsImportContactToken, res: User}, 'contacts.importContactToken': {req: ContactsImportContactToken, res: User},
'photos.uploadContactProfilePhoto': {req: PhotosUploadContactProfilePhoto, res: PhotosPhoto}, 'photos.uploadContactProfilePhoto': {req: PhotosUploadContactProfilePhoto, res: PhotosPhoto},
'channels.toggleParticipantsHidden': {req: ChannelsToggleParticipantsHidden, res: Updates}, 'channels.toggleParticipantsHidden': {req: ChannelsToggleParticipantsHidden, res: Updates},
'messages.sendBotRequestedPeer': {req: MessagesSendBotRequestedPeer, res: Updates},
'account.getDefaultProfilePhotoEmojis': {req: AccountGetDefaultProfilePhotoEmojis, res: EmojiList},
'account.getDefaultGroupPhotoEmojis': {req: AccountGetDefaultGroupPhotoEmojis, res: EmojiList},
'auth.requestFirebaseSms': {req: AuthRequestFirebaseSms, res: boolean},
'messages.getEmojiGroups': {req: MessagesGetEmojiGroups, res: MessagesEmojiGroups},
'messages.getEmojiStatusGroups': {req: MessagesGetEmojiStatusGroups, res: MessagesEmojiGroups},
'messages.getEmojiProfilePhotoGroups': {req: MessagesGetEmojiProfilePhotoGroups, res: MessagesEmojiGroups},
'messages.searchCustomEmoji': {req: MessagesSearchCustomEmoji, res: EmojiList},
'messages.togglePeerTranslations': {req: MessagesTogglePeerTranslations, res: boolean},
'account.getAutoSaveSettings': {req: AccountGetAutoSaveSettings, res: AccountAutoSaveSettings},
'account.saveAutoSaveSettings': {req: AccountSaveAutoSaveSettings, res: boolean},
'account.deleteAutoSaveExceptions': {req: AccountDeleteAutoSaveExceptions, res: boolean},
} }

View File

@ -20,6 +20,7 @@ import parseMarkdown from '../richTextProcessor/parseMarkdown';
import ctx from '../../environment/ctx'; import ctx from '../../environment/ctx';
import EventListenerBase from '../../helpers/eventListenerBase'; import EventListenerBase from '../../helpers/eventListenerBase';
import applyMixins from '../../helpers/applyMixins'; import applyMixins from '../../helpers/applyMixins';
import tsNow from '../../helpers/tsNow';
type UpdatesState = { type UpdatesState = {
pendingPtsUpdates: (Update & {pts: number, pts_count: number})[], pendingPtsUpdates: (Update & {pts: number, pts_count: number})[],
@ -311,7 +312,7 @@ class ApiUpdatesManager {
updatesState.date = nextState.date; updatesState.date = nextState.date;
} else { } else {
updatesState.pts = differenceResult.pts; updatesState.pts = differenceResult.pts;
updatesState.date = (Date.now() / 1000 | 0) + this.timeManager.getServerTimeOffset(); updatesState.date = tsNow(true) + this.timeManager.getServerTimeOffset();
delete updatesState.seq; delete updatesState.seq;
this.channelStates = {}; this.channelStates = {};
@ -740,7 +741,7 @@ class ApiUpdatesManager {
message, message,
type: 'local', type: 'local',
pFlags: {}, pFlags: {},
inbox_date: Date.now() / 1000 | 0, inbox_date: tsNow(true),
media: undefined media: undefined
}; };

View File

@ -12,7 +12,7 @@
import deepEqual from '../../helpers/object/deepEqual'; import deepEqual from '../../helpers/object/deepEqual';
import isObject from '../../helpers/object/isObject'; import isObject from '../../helpers/object/isObject';
import safeReplaceObject from '../../helpers/object/safeReplaceObject'; import safeReplaceObject from '../../helpers/object/safeReplaceObject';
import {ChannelParticipant, ChannelsCreateChannel, Chat, ChatAdminRights, ChatBannedRights, ChatInvite, ChatPhoto, ChatReactions, InputChannel, InputChatPhoto, InputFile, InputPeer, MessagesSponsoredMessages, SponsoredMessage, Update, Updates} from '../../layer'; import {ChannelParticipant, ChannelsCreateChannel, Chat, ChatAdminRights, ChatBannedRights, ChatInvite, ChatParticipant, ChatPhoto, ChatReactions, InputChannel, InputChatPhoto, InputFile, InputPeer, MessagesSponsoredMessages, SponsoredMessage, Update, Updates} from '../../layer';
import {isRestricted} from '../../helpers/restrictions'; import {isRestricted} from '../../helpers/restrictions';
import {AppManager} from './manager'; import {AppManager} from './manager';
import hasRights from './utils/chats/hasRights'; import hasRights from './utils/chats/hasRights';
@ -21,6 +21,7 @@ import {AppStoragesManager} from './appStoragesManager';
import getServerMessageId from './utils/messageId/getServerMessageId'; import getServerMessageId from './utils/messageId/getServerMessageId';
import {randomLong} from '../../helpers/random'; import {randomLong} from '../../helpers/random';
import generateMessageId from './utils/messageId/generateMessageId'; import generateMessageId from './utils/messageId/generateMessageId';
import tsNow from '../../helpers/tsNow';
export type Channel = Chat.channel; export type Channel = Chat.channel;
export type ChatRights = keyof ChatBannedRights['pFlags'] | keyof ChatAdminRights['pFlags'] | 'change_type' | 'change_permissions' | 'delete_chat' | 'view_participants'; export type ChatRights = keyof ChatBannedRights['pFlags'] | keyof ChatAdminRights['pFlags'] | 'change_type' | 'change_permissions' | 'delete_chat' | 'view_participants';
@ -37,26 +38,9 @@ export class AppChatsManager extends AppManager {
this.clear(true); this.clear(true);
this.apiUpdatesManager.addMultipleEventsListeners({ this.apiUpdatesManager.addMultipleEventsListeners({
/* updateChannel: (update) => { updateChannelParticipant: this.onUpdateChannelParticipant,
const channelId = update.channel_id;
//console.log('updateChannel:', update);
rootScope.broadcast('channel_settings', {channelId});
}, */
updateChannelParticipant: (update) => { updateChatDefaultBannedRights: this.onUpdateChatDefaultBannedRights
this.apiManager.clearCache('channels.getParticipants', (params) => {
return (params.channel as InputChannel.inputChannel).channel_id === update.channel_id;
});
},
updateChatDefaultBannedRights: (update) => {
const chatId = this.appPeersManager.getPeerId(update.peer).toChatId();
const chat = this.chats[chatId] as Chat.chat;
if(chat) {
chat.default_banned_rights = update.default_banned_rights;
this.rootScope.dispatchEvent('chat_update', chatId);
}
}
}); });
return Promise.all([ return Promise.all([
@ -535,8 +519,14 @@ export class AppChatsManager extends AppManager {
}); });
} }
public editBanned(id: ChatId, participant: PeerId | ChannelParticipant, banned_rights: ChatBannedRights) { public async editBanned(id: ChatId, participant: PeerId | ChannelParticipant | ChatParticipant, banned_rights: ChatBannedRights) {
const peerId = typeof(participant) !== 'object' ? participant : getParticipantPeerId(participant); const peerId = typeof(participant) !== 'object' ? participant : getParticipantPeerId(participant);
const wasChannel = this.isChannel(id);
if(!wasChannel) {
const channelId = await this.migrateChat(id);
id = channelId;
}
return this.apiManager.invokeApi('channels.editBanned', { return this.apiManager.invokeApi('channels.editBanned', {
channel: this.getChannelInput(id), channel: this.getChannelInput(id),
participant: this.appPeersManager.getInputPeerById(peerId), participant: this.appPeersManager.getInputPeerById(peerId),
@ -545,7 +535,7 @@ export class AppChatsManager extends AppManager {
this.onChatUpdated(id, updates); this.onChatUpdated(id, updates);
if(typeof(participant) === 'object') { if(typeof(participant) === 'object') {
const timestamp = Date.now() / 1000 | 0; const timestamp = tsNow(true);
this.apiUpdatesManager.processLocalUpdate({ this.apiUpdatesManager.processLocalUpdate({
_: 'updateChannelParticipant', _: 'updateChannelParticipant',
channel_id: id, channel_id: id,
@ -553,7 +543,7 @@ export class AppChatsManager extends AppManager {
actor_id: undefined, actor_id: undefined,
qts: undefined, qts: undefined,
user_id: peerId, user_id: peerId,
prev_participant: participant, prev_participant: wasChannel ? participant as ChannelParticipant : undefined,
new_participant: Object.keys(banned_rights.pFlags).length ? { new_participant: Object.keys(banned_rights.pFlags).length ? {
_: 'channelParticipantBanned', _: 'channelParticipantBanned',
date: timestamp, date: timestamp,
@ -585,9 +575,9 @@ export class AppChatsManager extends AppManager {
}); });
} }
public kickFromChat(id: ChatId, participant: PeerId | ChannelParticipant) { public kickFromChat(id: ChatId, participant: PeerId | ChannelParticipant | ChatParticipant) {
if(this.isChannel(id)) return this.kickFromChannel(id, participant); if(this.isChannel(id)) return this.kickFromChannel(id, participant as ChannelParticipant);
else return this.deleteChatUser(id, (participant as PeerId).toUserId()); else return this.deleteChatUser(id, isObject(participant) ? getParticipantPeerId(participant) : (participant as PeerId).toUserId());
} }
public resolveChannel(id: ChatId) { public resolveChannel(id: ChatId) {
@ -904,4 +894,21 @@ export class AppChatsManager extends AppManager {
pinned pinned
}).then(this.onChatUpdated.bind(this, chatId)); }).then(this.onChatUpdated.bind(this, chatId));
} }
private onUpdateChannelParticipant = (update: Update.updateChannelParticipant) => {
this.apiManager.clearCache('channels.getParticipants', (params) => {
return (params.channel as InputChannel.inputChannel).channel_id === update.channel_id;
});
this.rootScope.dispatchEvent('chat_participant', update);
};
private onUpdateChatDefaultBannedRights = (update: Update.updateChatDefaultBannedRights) => {
const chatId = this.appPeersManager.getPeerId(update.peer).toChatId();
const chat = this.chats[chatId] as Chat.chat;
if(chat) {
chat.default_banned_rights = update.default_banned_rights;
this.rootScope.dispatchEvent('chat_update', chatId);
}
};
} }

View File

@ -221,7 +221,7 @@ type DialogElementOptions = {
wrapOptions?: WrapSomethingOptions, wrapOptions?: WrapSomethingOptions,
isMainList?: boolean isMainList?: boolean
}; };
class DialogElement extends Row { export class DialogElement extends Row {
public dom: DialogDom; public dom: DialogDom;
constructor({ constructor({
@ -2748,7 +2748,7 @@ export class AppDialogsManager {
let mediaContainer: HTMLElement; let mediaContainer: HTMLElement;
const willPrepend: (Promise<any> | HTMLElement)[] = []; const willPrepend: (Promise<any> | HTMLElement)[] = [];
if(lastMessage && !draftMessage && !isRestricted) { if(lastMessage && !draftMessage && !isRestricted) {
const media: MyDocument | MyPhoto = getMediaFromMessage(lastMessage); const media = getMediaFromMessage(lastMessage, true);
const videoTypes: Set<MyDocument['type']> = new Set(['video', 'gif', 'round']); const videoTypes: Set<MyDocument['type']> = new Set(['video', 'gif', 'round']);
if(media && (media._ === 'photo' || videoTypes.has(media.type))) { if(media && (media._ === 'photo' || videoTypes.has(media.type))) {
const size = choosePhotoSize(media, 20, 20); const size = choosePhotoSize(media, 20, 20);

View File

@ -26,6 +26,7 @@ import getDocumentURL from './utils/docs/getDocumentURL';
import makeError from '../../helpers/makeError'; import makeError from '../../helpers/makeError';
import {EXTENSION_MIME_TYPE_MAP} from '../../environment/mimeTypeMap'; import {EXTENSION_MIME_TYPE_MAP} from '../../environment/mimeTypeMap';
import {THUMB_TYPE_FULL} from '../mtproto/mtproto_config'; import {THUMB_TYPE_FULL} from '../mtproto/mtproto_config';
import tsNow from '../../helpers/tsNow';
export type MyDocument = Document.document; export type MyDocument = Document.document;
@ -331,7 +332,7 @@ export class AppDocsManager extends AppManager {
id, id,
mime_type: file.type as MTMimeType, mime_type: file.type as MTMimeType,
size: file.size, size: file.size,
date: Date.now() / 1000, date: tsNow(true),
pFlags: {}, pFlags: {},
thumbs: [thumb], thumbs: [thumb],
file_name: file.name file_name: file.name

View File

@ -239,7 +239,7 @@ export class AppDraftsManager extends AppManager {
public setDraft(peerId: PeerId, threadId: number, message: string, entities?: MessageEntity[]) { public setDraft(peerId: PeerId, threadId: number, message: string, entities?: MessageEntity[]) {
const draft: DraftMessage.draftMessage = { const draft: DraftMessage.draftMessage = {
_: 'draftMessage', _: 'draftMessage',
date: Date.now() / 1000 | 0, date: tsNow(true),
message, message,
pFlags: {}, pFlags: {},
entities entities

View File

@ -45,6 +45,7 @@ import debounce from '../../helpers/schedulers/debounce';
import pause from '../../helpers/schedulers/pause'; import pause from '../../helpers/schedulers/pause';
import {InternalLink, InternalLinkTypeMap, INTERNAL_LINK_TYPE} from './internalLink'; import {InternalLink, InternalLinkTypeMap, INTERNAL_LINK_TYPE} from './internalLink';
import MEDIA_MIME_TYPES_SUPPORTED from '../../environment/mediaMimeTypesSupport'; import MEDIA_MIME_TYPES_SUPPORTED from '../../environment/mediaMimeTypesSupport';
import IMAGE_MIME_TYPES_SUPPORTED from '../../environment/imageMimeTypesSupport';
import {NULL_PEER_ID} from '../mtproto/mtproto_config'; import {NULL_PEER_ID} from '../mtproto/mtproto_config';
import telegramMeWebManager from '../mtproto/telegramMeWebManager'; import telegramMeWebManager from '../mtproto/telegramMeWebManager';
import {ONE_DAY} from '../../helpers/date'; import {ONE_DAY} from '../../helpers/date';
@ -102,6 +103,8 @@ import findUpTag from '../../helpers/dom/findUpTag';
import {MTAppConfig} from '../mtproto/appConfig'; import {MTAppConfig} from '../mtproto/appConfig';
import PopupForward from '../../components/popups/forward'; import PopupForward from '../../components/popups/forward';
import AppBackgroundTab from '../../components/sidebarLeft/tabs/background'; import AppBackgroundTab from '../../components/sidebarLeft/tabs/background';
import partition from '../../helpers/array/partition';
import indexOfAndSplice from '../../helpers/array/indexOfAndSplice';
export type ChatSavedPosition = { export type ChatSavedPosition = {
mids: number[], mids: number[],
@ -1728,24 +1731,43 @@ export class AppImManager extends EventListenerBase<{
return; return;
} }
const rights = await PopupNewMedia.canSend(this.chat.peerId, true);
const _dropsContainer = newMediaPopup ? mediaDropsContainer : dropsContainer; const _dropsContainer = newMediaPopup ? mediaDropsContainer : dropsContainer;
const _drops = newMediaPopup ? mediaDrops : drops; const _drops = newMediaPopup ? mediaDrops : drops;
if(mount && !_drops.length) { if(mount && !_drops.length) {
const force = isFiles && !types.length; // * can't get file items not from 'drop' on Safari const force = isFiles && !types.length; // * can't get file items not from 'drop' on Safari
const foundMedia = types.filter((t) => MEDIA_MIME_TYPES_SUPPORTED.has(t)).length; const [foundMedia, foundDocuments] = partition(types, (t) => MEDIA_MIME_TYPES_SUPPORTED.has(t));
// const foundDocuments = types.length - foundMedia; const [foundPhotos, foundVideos] = partition(foundMedia, (t) => IMAGE_MIME_TYPES_SUPPORTED.has(t));
this.log('drag files', types); if(!rights.send_docs) {
foundDocuments.length = 0;
} else {
foundDocuments.push(...foundMedia);
}
if(!rights.send_photos) {
foundPhotos.forEach((mimeType) => indexOfAndSplice(foundMedia, mimeType));
foundPhotos.length = 0;
}
if(!rights.send_videos) {
foundVideos.forEach((mimeType) => indexOfAndSplice(foundMedia, mimeType));
foundVideos.length = 0;
}
this.log('drag files', types, foundMedia, foundDocuments, foundPhotos, foundVideos);
if(newMediaPopup) { if(newMediaPopup) {
newMediaPopup.appendDrops(_dropsContainer); newMediaPopup.appendDrops(_dropsContainer);
if(types.length || force) { const length = (rights.send_docs ? [foundDocuments] : [foundPhotos, foundVideos]).reduce((acc, v) => acc + v.length, 0);
if(length || force) {
_drops.push(new ChatDragAndDrop(_dropsContainer, { _drops.push(new ChatDragAndDrop(_dropsContainer, {
header: 'Preview.Dragging.AddItems', header: 'Preview.Dragging.AddItems',
headerArgs: [types.length], headerArgs: [length],
onDrop: (e: DragEvent) => { onDrop: (e: DragEvent) => {
toggle(e, false); toggle(e, false);
this.log('drop', e); this.log('drop', e);
@ -1754,7 +1776,7 @@ export class AppImManager extends EventListenerBase<{
})); }));
} }
} else { } else {
if(types.length || force) { if(foundDocuments.length || force) {
_drops.push(new ChatDragAndDrop(_dropsContainer, { _drops.push(new ChatDragAndDrop(_dropsContainer, {
icon: 'dragfiles', icon: 'dragfiles',
header: 'Chat.DropTitle', header: 'Chat.DropTitle',
@ -1767,8 +1789,7 @@ export class AppImManager extends EventListenerBase<{
})); }));
} }
// if((foundMedia && !foundDocuments) || force) { if(foundMedia.length || force) {
if(foundMedia || force) {
_drops.push(new ChatDragAndDrop(_dropsContainer, { _drops.push(new ChatDragAndDrop(_dropsContainer, {
icon: 'dragmedia', icon: 'dragmedia',
header: 'Chat.DropTitle', header: 'Chat.DropTitle',

View File

@ -524,7 +524,7 @@ export class AppMessagesManager extends AppManager {
}); });
} }
public transcribeAudio(message: Message.message): Promise<MessagesTranscribedAudio> { public async transcribeAudio(message: Message.message): Promise<MessagesTranscribedAudio> {
const {id, peerId} = message; const {id, peerId} = message;
const process = (result: MessagesTranscribedAudio) => { const process = (result: MessagesTranscribedAudio) => {
@ -3916,16 +3916,12 @@ export class AppMessagesManager extends AppManager {
}); });
} }
public async deleteMessages(peerId: PeerId, mids: number[], revoke?: boolean) { public async deleteMessages(peerId: PeerId, mids: number[], revoke?: boolean, isRecursion?: boolean) {
let promise: Promise<any>; let promise: Promise<any>;
const config = await this.apiManager.getConfig(); const isChannel = this.appPeersManager.isChannel(peerId);
const overflowMids = mids.splice(config.forwarded_count_max, mids.length - config.forwarded_count_max); const channelId = isChannel && peerId.toChatId();
if(isChannel && !isRecursion) {
const localMessageIds = mids.map((mid) => getServerMessageId(mid));
if(peerId.isAnyChat() && this.appPeersManager.isChannel(peerId)) {
const channelId = peerId.toChatId();
const channel = this.appChatsManager.getChat(channelId) as Chat.channel; const channel = this.appChatsManager.getChat(channelId) as Chat.channel;
if(!channel.pFlags.creator && !channel.admin_rights?.pFlags?.delete_messages) { if(!channel.pFlags.creator && !channel.admin_rights?.pFlags?.delete_messages) {
mids = mids.filter((mid) => { mids = mids.filter((mid) => {
@ -3937,10 +3933,21 @@ export class AppMessagesManager extends AppManager {
return; return;
} }
} }
}
const config = await this.apiManager.getConfig();
const overflowMids = mids.splice(config.forwarded_count_max, mids.length - config.forwarded_count_max);
const serverMessageIds = mids.map((mid) => {
const messageId = getServerMessageId(mid);
// filter outgoing messages
return generateMessageId(messageId) === mid && messageId;
}).filter(Boolean);
if(isChannel) {
promise = this.apiManager.invokeApi('channels.deleteMessages', { promise = this.apiManager.invokeApi('channels.deleteMessages', {
channel: this.appChatsManager.getChannelInput(channelId), channel: this.appChatsManager.getChannelInput(channelId),
id: localMessageIds id: serverMessageIds
}).then((affectedMessages) => { }).then((affectedMessages) => {
this.apiUpdatesManager.processLocalUpdate({ this.apiUpdatesManager.processLocalUpdate({
_: 'updateDeleteChannelMessages', _: 'updateDeleteChannelMessages',
@ -3953,7 +3960,7 @@ export class AppMessagesManager extends AppManager {
} else { } else {
promise = this.apiManager.invokeApi('messages.deleteMessages', { promise = this.apiManager.invokeApi('messages.deleteMessages', {
revoke, revoke,
id: localMessageIds id: serverMessageIds
}).then((affectedMessages) => { }).then((affectedMessages) => {
this.apiUpdatesManager.processLocalUpdate({ this.apiUpdatesManager.processLocalUpdate({
_: 'updateDeleteMessages', _: 'updateDeleteMessages',
@ -3966,7 +3973,7 @@ export class AppMessagesManager extends AppManager {
const promises: (typeof promise)[] = [promise]; const promises: (typeof promise)[] = [promise];
if(overflowMids.length) { if(overflowMids.length) {
promises.push(this.deleteMessages(peerId, overflowMids, revoke)); promises.push(this.deleteMessages(peerId, overflowMids, revoke, true));
} }
return Promise.all(promises).then(noop); return Promise.all(promises).then(noop);

View File

@ -20,6 +20,8 @@ import getParticipantPeerId from './utils/chats/getParticipantPeerId';
import ctx from '../../environment/ctx'; import ctx from '../../environment/ctx';
import {ReferenceContext} from '../mtproto/referenceDatabase'; import {ReferenceContext} from '../mtproto/referenceDatabase';
import generateMessageId from './utils/messageId/generateMessageId'; import generateMessageId from './utils/messageId/generateMessageId';
import assumeType from '../../helpers/assumeType';
import makeError from '../../helpers/makeError';
export type UserTyping = Partial<{userId: UserId, action: SendMessageAction, timeout: number}>; export type UserTyping = Partial<{userId: UserId, action: SendMessageAction, timeout: number}>;
@ -34,56 +36,11 @@ export class AppProfileManager extends AppManager {
protected after() { protected after() {
this.apiUpdatesManager.addMultipleEventsListeners({ this.apiUpdatesManager.addMultipleEventsListeners({
updateChatParticipants: (update) => { updateChatParticipants: this.onUpdateChatParticipants,
const participants = update.participants;
if(participants._ === 'chatParticipants') {
const chatId = participants.chat_id;
const chatFull = this.chatsFull[chatId] as ChatFull.chatFull;
if(chatFull !== undefined) {
chatFull.participants = participants;
this.rootScope.dispatchEvent('chat_full_update', chatId);
}
}
},
updateChatParticipantAdd: (update) => { updateChatParticipantAdd: this.onUpdateChatParticipantAdd,
const chatFull = this.chatsFull[update.chat_id] as ChatFull.chatFull;
if(chatFull !== undefined) {
const _participants = chatFull.participants as ChatParticipants.chatParticipants;
const participants = _participants.participants || [];
for(let i = 0, length = participants.length; i < length; i++) {
if(participants[i].user_id === update.user_id) {
return;
}
}
participants.push({ updateChatParticipantDelete: this.onUpdateChatParticipantDelete,
_: 'chatParticipant',
user_id: update.user_id,
inviter_id: update.inviter_id,
date: tsNow(true)
});
_participants.version = update.version;
this.rootScope.dispatchEvent('chat_full_update', update.chat_id);
}
},
updateChatParticipantDelete: (update) => {
const chatFull = this.chatsFull[update.chat_id] as ChatFull.chatFull;
if(chatFull !== undefined) {
const _participants = chatFull.participants as ChatParticipants.chatParticipants;
const participants = _participants.participants || [];
for(let i = 0, length = participants.length; i < length; i++) {
if(participants[i].user_id === update.user_id) {
participants.splice(i, 1);
_participants.version = update.version;
this.rootScope.dispatchEvent('chat_full_update', update.chat_id);
return;
}
}
}
},
updateUserTyping: this.onUpdateUserTyping, updateUserTyping: this.onUpdateUserTyping,
updateChatUserTyping: this.onUpdateUserTyping, updateChatUserTyping: this.onUpdateUserTyping,
@ -332,7 +289,68 @@ export class AppProfileManager extends AppManager {
}); });
} }
public getChannelParticipants(id: ChatId, filter: ChannelParticipantsFilter = {_: 'channelParticipantsRecent'}, limit = 200, offset = 0) { public getParticipants(
id: ChatId,
filter: ChannelParticipantsFilter = {_: 'channelParticipantsRecent'},
limit = 200,
offset = 0
) {
if(this.appChatsManager.isChannel(id)) {
return this.getChannelParticipants(id, filter, limit, offset);
}
return Promise.resolve(this.getChatFull(id)).then((chatFull) => {
const chatParticipants = (chatFull as ChatFull.chatFull).participants;
if(chatParticipants._ !== 'chatParticipants') {
throw makeError('CHAT_PRIVATE');
}
if(filter._ === 'channelParticipantsSearch' && filter.q.trim()) {
const index = this.appUsersManager.createSearchIndex();
chatParticipants.participants.forEach((chatParticipant) => {
const userId = chatParticipant.user_id;
index.indexObject(userId, this.appUsersManager.getUserSearchText(userId));
});
const found = index.search(filter.q);
const filteredParticipants = chatParticipants.participants.filter((chatParticipant) => {
return found.has(chatParticipant.user_id);
});
return {...chatParticipants, participants: filteredParticipants};
}
return chatParticipants;
});
}
public getParticipant(id: ChatId, peerId: PeerId) {
if(this.appChatsManager.isChannel(id)) {
return this.getChannelParticipant(id, peerId);
}
return this.getParticipants(id).then((chatParticipants) => {
assumeType<ChatParticipants.chatParticipants>(chatParticipants);
const found = chatParticipants.participants.find((chatParticipant) => {
if(getParticipantPeerId(chatParticipant) === peerId) {
return chatParticipant;
}
});
if(!found) {
throw makeError('USER_NOT_PARTICIPANT');
}
return found;
});
}
public getChannelParticipants(
id: ChatId,
filter: ChannelParticipantsFilter = {_: 'channelParticipantsRecent'},
limit = 200,
offset = 0
) {
if(filter._ === 'channelParticipantsRecent') { if(filter._ === 'channelParticipantsRecent') {
const chat = this.appChatsManager.getChat(id); const chat = this.appChatsManager.getChat(id);
if(chat?.pFlags && ( if(chat?.pFlags && (
@ -647,6 +665,67 @@ export class AppProfileManager extends AppManager {
return peerId + (threadId ? `_${threadId}` : ''); return peerId + (threadId ? `_${threadId}` : '');
} }
public getPeerTypings(peerId: PeerId, threadId?: number) {
return this.typingsInPeer[this.getTypingsKey(peerId, threadId)];
}
private onUpdateChatParticipants = (update: Update.updateChatParticipants) => {
const participants = update.participants;
if(participants._ !== 'chatParticipants') {
return;
}
const chatId = participants.chat_id;
const chatFull = this.chatsFull[chatId] as ChatFull.chatFull;
if(chatFull !== undefined) {
chatFull.participants = participants;
this.rootScope.dispatchEvent('chat_full_update', chatId);
}
};
private onUpdateChatParticipantAdd = (update: Update.updateChatParticipantAdd) => {
const chatFull = this.chatsFull[update.chat_id] as ChatFull.chatFull;
if(chatFull === undefined) {
return;
}
const _participants = chatFull.participants as ChatParticipants.chatParticipants;
const participants = _participants.participants || [];
for(let i = 0, length = participants.length; i < length; i++) {
if(participants[i].user_id === update.user_id) {
return;
}
}
participants.push({
_: 'chatParticipant',
user_id: update.user_id,
inviter_id: update.inviter_id,
date: tsNow(true)
});
_participants.version = update.version;
this.rootScope.dispatchEvent('chat_full_update', update.chat_id);
};
private onUpdateChatParticipantDelete = (update: Update.updateChatParticipantDelete) => {
const chatFull = this.chatsFull[update.chat_id] as ChatFull.chatFull;
if(chatFull === undefined) {
return;
}
const _participants = chatFull.participants as ChatParticipants.chatParticipants;
const participants = _participants.participants || [];
for(let i = 0, length = participants.length; i < length; i++) {
if(participants[i].user_id === update.user_id) {
participants.splice(i, 1);
_participants.version = update.version;
this.rootScope.dispatchEvent('chat_full_update', update.chat_id);
return;
}
}
};
private onUpdateUserTyping = (update: Update.updateUserTyping | Update.updateChatUserTyping | Update.updateChannelUserTyping) => { private onUpdateUserTyping = (update: Update.updateUserTyping | Update.updateChatUserTyping | Update.updateChannelUserTyping) => {
const fromId = (update as Update.updateUserTyping).user_id ? const fromId = (update as Update.updateUserTyping).user_id ?
(update as Update.updateUserTyping).user_id.toPeerId() : (update as Update.updateUserTyping).user_id.toPeerId() :
@ -741,8 +820,4 @@ export class AppProfileManager extends AppManager {
this.rootScope.dispatchEvent('peer_block', {peerId, blocked: update.blocked}); this.rootScope.dispatchEvent('peer_block', {peerId, blocked: update.blocked});
}; };
public getPeerTypings(peerId: PeerId, threadId?: number) {
return this.typingsInPeer[this.getTypingsKey(peerId, threadId)];
}
} }

View File

@ -6,7 +6,7 @@
import type {MyDocument} from './appDocsManager'; import type {MyDocument} from './appDocsManager';
import type {DownloadOptions} from '../mtproto/apiFileManager'; import type {DownloadOptions} from '../mtproto/apiFileManager';
import {Document, InputFileLocation, InputStickerSet, MessagesAllStickers, MessagesFavedStickers, MessagesFeaturedStickers, MessagesFoundStickerSets, MessagesRecentStickers, MessagesStickers, MessagesStickerSet, PhotoSize, StickerPack, StickerSet, StickerSetCovered, Update} from '../../layer'; import {Document, InputFileLocation, InputStickerSet, MessagesAllStickers, MessagesFavedStickers, MessagesFeaturedStickers, MessagesFoundStickerSets, MessagesRecentStickers, MessagesStickers, MessagesStickerSet, PhotoSize, StickerPack, StickerSet, StickerSetCovered, Update, VideoSize} from '../../layer';
import {Modify} from '../../types'; import {Modify} from '../../types';
import AppStorage from '../storage'; import AppStorage from '../storage';
import DATABASE_STATE from '../../config/databases/state'; import DATABASE_STATE from '../../config/databases/state';
@ -20,6 +20,7 @@ import ctx from '../../environment/ctx';
import {getEnvironment} from '../../environment/utils'; import {getEnvironment} from '../../environment/utils';
import getDocumentInput from './utils/docs/getDocumentInput'; import getDocumentInput from './utils/docs/getDocumentInput';
import getStickerEffectThumb from './utils/stickers/getStickerEffectThumb'; import getStickerEffectThumb from './utils/stickers/getStickerEffectThumb';
import tsNow from '../../helpers/tsNow';
const CACHE_TIME = 3600e3; const CACHE_TIME = 3600e3;
@ -406,7 +407,7 @@ export class AppStickersManager extends AppManager {
public preloadSticker(docId: DocId, isPremiumEffect?: boolean) { public preloadSticker(docId: DocId, isPremiumEffect?: boolean) {
const doc = this.appDocsManager.getDoc(docId); const doc = this.appDocsManager.getDoc(docId);
return this.apiFileManager.downloadMedia({media: doc, thumb: isPremiumEffect ? doc.video_thumbs?.[0] : undefined}); return this.apiFileManager.downloadMedia({media: doc, thumb: isPremiumEffect ? doc.video_thumbs?.[0] as Extract<VideoSize, VideoSize.videoSize> : undefined});
} }
private saveStickerSet(res: Omit<MessagesStickerSet.messagesStickerSet, '_'>, id: DocId) { private saveStickerSet(res: Omit<MessagesStickerSet.messagesStickerSet, '_'>, id: DocId) {
@ -592,7 +593,7 @@ export class AppStickersManager extends AppManager {
}); });
if(res) { if(res) {
set.installed_date = Date.now() / 1000 | 0; set.installed_date = tsNow(true);
this.rootScope.dispatchEvent('stickers_installed', set); this.rootScope.dispatchEvent('stickers_installed', set);
return true; return true;
} }

View File

@ -431,7 +431,7 @@ export class AppUsersManager extends AppManager {
return index.search(query).has(user.id); return index.search(query).has(user.id);
} }
private createSearchIndex() { public createSearchIndex() {
return new SearchIndex<UserId>({ return new SearchIndex<UserId>({
clearBadChars: true, clearBadChars: true,
ignoreCase: true, ignoreCase: true,

View File

@ -35,8 +35,8 @@ import DEBUG from '../../config/debug';
// const key2 = [('00000' + sentCount).slice(-5), key].join('-'); // const key2 = [('00000' + sentCount).slice(-5), key].join('-');
// let byManager = stats[manager] ??= {}; // const byManager = stats[manager] ??= {};
// let byMethod = byManager[method] ??= {times: [], byArgs: {}}; // const byMethod = byManager[method] ??= {times: [], byArgs: {}};
// const perf = performance.now(); // const perf = performance.now();
// promise.catch(noop).finally(() => { // promise.catch(noop).finally(() => {
@ -53,8 +53,9 @@ import DEBUG from '../../config/debug';
// } // }
// setInterval(() => { // setInterval(() => {
// // console.log(dT(), '[PROXY] stats', stats, sentCount, sentMethods, sentMethods2); // console.log(dT(), '[PROXY] stats', stats, sentCount, sentMethods, sentMethods2);
// sentCount = 0; // sentCount = 0;
// stats = {};
// sentMethods = {}; // sentMethods = {};
// sentMethods2 = {}; // sentMethods2 = {};
// }, 2000); // }, 2000);

View File

@ -365,7 +365,7 @@ export class UiNotificationsManager {
avatarContext.textBaseline = 'middle'; avatarContext.textBaseline = 'middle';
avatarContext.textAlign = 'center'; avatarContext.textAlign = 'center';
avatarContext.fillStyle = 'white'; avatarContext.fillStyle = 'white';
avatarContext.fillText(abbreviation.text, avatarCanvas.width / 2, avatarCanvas.height * (window.devicePixelRatio > 1 ? .5625 : 5)); avatarContext.fillText(abbreviation.text, avatarCanvas.width / 2, avatarCanvas.height * (window.devicePixelRatio > 1 || true ? .5625 : .5));
notification.image = avatarCanvas.toDataURL(); notification.image = avatarCanvas.toDataURL();
} }

View File

@ -63,7 +63,14 @@ export default function hasRights(chat: Exclude<Chat, Chat.chatEmpty>, action: C
case 'send_media': case 'send_media':
case 'send_messages': case 'send_messages':
case 'send_polls': case 'send_polls':
case 'send_stickers': { case 'send_stickers':
case 'send_photos':
case 'send_videos':
case 'send_roundvideos':
case 'send_audios':
case 'send_voices':
case 'send_docs':
case 'send_plain': {
if(!isThread && chat.pFlags.left) { if(!isThread && chat.pFlags.left) {
return false; return false;
} }

View File

@ -8,7 +8,12 @@ import type {Document, PhotoSize, VideoSize} from '../../../../layer';
import type {DownloadOptions} from '../../../mtproto/apiFileManager'; import type {DownloadOptions} from '../../../mtproto/apiFileManager';
import getDocumentInputFileLocation from './getDocumentInputFileLocation'; import getDocumentInputFileLocation from './getDocumentInputFileLocation';
export default function getDocumentDownloadOptions(doc: Document.document, thumb?: PhotoSize.photoSize | VideoSize, queueId?: number, onlyCache?: boolean): DownloadOptions { export default function getDocumentDownloadOptions(
doc: Document.document,
thumb?: PhotoSize.photoSize | Extract<VideoSize, VideoSize.videoSize>,
queueId?: number,
onlyCache?: boolean
): DownloadOptions {
const inputFileLocation = getDocumentInputFileLocation(doc, thumb?.type); const inputFileLocation = getDocumentInputFileLocation(doc, thumb?.type);
let mimeType: MTMimeType; let mimeType: MTMimeType;

View File

@ -1,6 +1,8 @@
import {Document, Message, MessageAction, MessageExtendedMedia, MessageMedia, Photo, WebPage} from '../../../../layer'; import {Document, Game, Message, MessageAction, MessageExtendedMedia, MessageMedia, Photo, WebPage} from '../../../../layer';
export default function getMediaFromMessage(message: Message) { export default function getMediaFromMessage(message: Message, onlyInner: true): Photo.photo | Document.document;
export default function getMediaFromMessage(message: Message, onlyInner?: false): Photo.photo | Document.document | Game.game | WebPage.webPage;
export default function getMediaFromMessage(message: Message, onlyInner = false): Photo.photo | Document.document | Game.game | WebPage.webPage {
if(!message) return; if(!message) return;
let media: any; let media: any;
@ -15,8 +17,9 @@ export default function getMediaFromMessage(message: Message) {
} }
media = (messageMedia as MessageMedia.messageMediaDocument).document || media = (messageMedia as MessageMedia.messageMediaDocument).document ||
(messageMedia as MessageMedia.messageMediaPhoto).photo; (messageMedia as MessageMedia.messageMediaPhoto).photo ||
(onlyInner ? undefined : (messageMedia as MessageMedia.messageMediaGame).game || messageMedia);
} }
return media as Photo.photo | Document.document; return media as any;
} }

View File

@ -1,5 +1,6 @@
import {MyDocument} from '../../appDocsManager'; import {MyDocument} from '../../appDocsManager';
import {VideoSize} from '../../../../layer';
export default function getStickerEffectThumb(doc: MyDocument) { export default function getStickerEffectThumb(doc: MyDocument) {
return doc.video_thumbs?.[0]; return doc.video_thumbs?.[0] as Extract<VideoSize, VideoSize.videoSize>;
} }

View File

@ -64,7 +64,7 @@ export type DownloadOptions = {
export type DownloadMediaOptions = { export type DownloadMediaOptions = {
media: Photo.photo | Document.document | WebDocument, media: Photo.photo | Document.document | WebDocument,
thumb?: PhotoSize | VideoSize, thumb?: PhotoSize | Extract<VideoSize, VideoSize.videoSize>,
queueId?: number, queueId?: number,
onlyCache?: boolean, onlyCache?: boolean,
downloadId?: string downloadId?: string

View File

@ -37,6 +37,7 @@ import pause from '../../helpers/schedulers/pause';
import ApiManagerMethods from './api_methods'; import ApiManagerMethods from './api_methods';
import {getEnvironment} from '../../environment/utils'; import {getEnvironment} from '../../environment/utils';
import toggleStorages from '../../helpers/toggleStorages'; import toggleStorages from '../../helpers/toggleStorages';
import tsNow from '../../helpers/tsNow';
/* class RotatableArray<T> { /* class RotatableArray<T> {
public array: Array<T> = []; public array: Array<T> = [];
@ -250,7 +251,7 @@ export class ApiManager extends ApiManagerMethods {
public async setUserAuth(userAuth: UserAuth | UserId) { public async setUserAuth(userAuth: UserAuth | UserId) {
if(typeof(userAuth) === 'string' || typeof(userAuth) === 'number') { if(typeof(userAuth) === 'string' || typeof(userAuth) === 'number') {
userAuth = {dcID: 0, date: Date.now() / 1000 | 0, id: userAuth.toPeerId(false)}; userAuth = {dcID: 0, date: tsNow(true), id: userAuth.toPeerId(false)};
} }
this.rootScope.dispatchEvent('user_auth', userAuth); this.rootScope.dispatchEvent('user_auth', userAuth);

File diff suppressed because one or more lines are too long

View File

@ -28,6 +28,7 @@ export type BroadcastEvents = {
'chat_full_update': ChatId, 'chat_full_update': ChatId,
'chat_update': ChatId, 'chat_update': ChatId,
'chat_toggle_forum': {chatId: ChatId, enabled: boolean}, 'chat_toggle_forum': {chatId: ChatId, enabled: boolean},
'chat_participant': Update.updateChannelParticipant,
'channel_update': ChatId, 'channel_update': ChatId,
@ -179,10 +180,7 @@ export class RootScope extends EventListenerBase<BroadcastEventsListeners> {
this.addEventListener('premium_toggle_private', ({isNew, isPremium}) => { this.addEventListener('premium_toggle_private', ({isNew, isPremium}) => {
this.premium = isPremium; this.premium = isPremium;
this.dispatchEventSingle('premium_toggle', isPremium);
if(!isNew) {
this.dispatchEventSingle('premium_toggle', isPremium);
}
}); });
this.addEventListener('connection_status_change', (status) => { this.addEventListener('connection_status_change', (status) => {

View File

@ -163,6 +163,17 @@ const onFirstMount = () => {
}).then((code) => { }).then((code) => {
// console.log('got code', code); // console.log('got code', code);
if(code._ === 'auth.sentCodeSuccess') {
const {authorization} = code;
if(authorization._ === 'auth.authorization') {
rootScope.managers.apiManager.setUser(authorization.user);
import('./pageIm').then((m) => {
m.default.mount();
});
}
}
import('./pageAuthCode').then((m) => m.default.mount(Object.assign(code, {phone_number: phone_number}))); import('./pageAuthCode').then((m) => m.default.mount(Object.assign(code, {phone_number: phone_number})));
}).catch((err) => { }).catch((err) => {
toggle(); toggle();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -382,7 +382,7 @@
height: 100%; height: 100%;
&-background { &-background {
rect { .audio-waveform-bar {
opacity: .3; opacity: .3;
@include hover() { @include hover() {
@ -408,7 +408,7 @@
margin-top: 1px; margin-top: 1px;
} }
rect { &-bar {
fill: var(--primary-color); fill: var(--primary-color);
//overflow: visible!important; //overflow: visible!important;
} }
@ -599,7 +599,7 @@
} }
} }
&:not(.is-out) .audio-toggle:not(.playing) + .audio-waveform-container .audio-waveform-background rect { &:not(.is-out) .audio-toggle:not(.playing) + .audio-waveform-container .audio-waveform-background .audio-waveform-bar {
opacity: 1; opacity: 1;
} }
} }

View File

@ -2437,6 +2437,7 @@ $bubble-border-radius-big: 12px;
} }
.audio-transcribed-text { .audio-transcribed-text {
margin-bottom: .75rem;
margin-top: .25rem; margin-top: .25rem;
&.is-error { &.is-error {
@ -2464,9 +2465,9 @@ $bubble-border-radius-big: 12px;
} }
} }
.document-message + .audio-transcribed-text { // .document-message + .audio-transcribed-text {
margin-bottom: .75rem; // margin-bottom: .75rem;
} // }
} }
@keyframes audio-dots { @keyframes audio-dots {
@ -2949,7 +2950,7 @@ $bubble-border-radius-big: 12px;
.audio { .audio {
&-waveform { &-waveform {
rect { &-bar {
fill: var(--message-out-primary-color); fill: var(--message-out-primary-color);
&.active { &.active {
@ -2974,7 +2975,7 @@ $bubble-border-radius-big: 12px;
} }
&.is-unread { &.is-unread {
rect { .audio-waveform-bar {
fill: var(--message-out-primary-color); fill: var(--message-out-primary-color);
} }

View File

@ -61,7 +61,9 @@
border-radius: inherit; border-radius: inherit;
} }
.media-photo, .media-video, .media-poster { .media-photo,
.media-video,
.media-poster {
object-fit: cover; object-fit: cover;
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -114,4 +116,23 @@
white-space: pre-wrap; white-space: pre-wrap;
} }
} }
&-cant-send {
display: none;
margin: 0 auto;
color: var(--secondary-text-color);
}
&.cant-send {
width: 100% !important;
height: 3rem;
.scrollable-y {
display: none;
}
.inline-helper-cant-send {
display: block;
}
}
} }

View File

@ -107,6 +107,8 @@
pointer-events: none; pointer-events: none;
line-height: var(--line-height); line-height: var(--line-height);
@include text-overflow(false);
@include animation-level(0) { @include animation-level(0) {
transition: none; transition: none;
} }
@ -385,7 +387,17 @@
position: absolute; position: absolute;
@include animation-level(2) { @include animation-level(2) {
transition: border-color .1s, transform .1s cubic-bezier(.22, .75, .7, 1.44); transition: border-color .1s, transform .1s cubic-bezier(.22, .75, .7, 1.3);
}
}
}
&-restriction {
.checkbox-toggle {
background-color: var(--danger-color);
&:before {
border-color: var(--danger-color);
} }
} }
} }

View File

@ -91,6 +91,14 @@ $row-border-radius: $border-radius-medium;
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
} }
&-row {
&.with-delimiter {
.row-title:first-child {
border-right: 1px solid var(--border-color);
}
}
}
} }
&-title-right, &-title-right,
@ -321,4 +329,10 @@ $row-border-radius: $border-radius-medium;
color: var(--primary-color); color: var(--primary-color);
} }
} }
&.accordion-toggler {
.checkbox-field-toggle {
pointer-events: all !important;
}
}
} }

View File

@ -274,6 +274,18 @@
} }
.popup-create-contact { .popup-create-contact {
.popup-container {
padding: 0 1rem 1rem !important;
}
.popup-header {
padding: 0 !important;
}
.btn-primary {
margin-bottom: 0 !important;
}
.name-fields { .name-fields {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -50,9 +50,10 @@
.checkbox-field { .checkbox-field {
display: flex; display: flex;
align-items: center; align-items: center;
height: 3rem; min-height: 3rem;
height: auto;
margin: 0; margin: 0;
padding: 0 1.125rem; padding: .25rem 1.125rem;
.checkbox-box { .checkbox-box {
left: auto; left: auto;

View File

@ -2102,3 +2102,34 @@ hr {
height: 100%; height: 100%;
} }
} }
.accordion {
overflow: hidden;
height: 0;
&.is-expanded {
height: var(--max-height);
}
@include animation-level(2) {
transition: height var(--transition-standard-in);
}
&-icon {
transform: rotate(0deg) translateY(4px);
color: var(--secondary-text-color);
font-size: 1.25rem;
display: inline-block;
line-height: 0;
@include animation-level(2) {
transition: transform var(--transition-standard-in);
}
}
&-toggler-expanded {
.accordion-icon {
transform: translateY(4px) rotate(180deg);
}
}
}