Browse Source

Emoji animation sound

master
Eduard Kuzmenko 2 years ago
parent
commit
ffd3e231d4
  1. 4
      .env
  2. 32
      src/components/wrappers.ts
  3. 7
      src/helpers/fixBase64String.ts
  4. 93
      src/lib/appManagers/appStickersManager.ts
  5. 51
      src/lib/mtproto/appConfig.d.ts
  6. 12
      src/lib/mtproto/mtprotoworker.ts
  7. 15
      src/lib/mtproto/referenceDatabase.ts
  8. 4
      src/lib/rootScope.ts
  9. 3
      src/pages/pageSignQR.ts

4
.env

@ -1,5 +1,5 @@
API_ID=1025907 API_ID=1025907
API_HASH=452b0359b988148995f22ff0f4229750 API_HASH=452b0359b988148995f22ff0f4229750
VERSION=1.0.4 VERSION=1.0.4
VERSION_FULL=1.0.4 (73) VERSION_FULL=1.0.4 (74)
BUILD=73 BUILD=74

32
src/components/wrappers.ts

@ -1338,15 +1338,39 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
let sendInteractionThrottled: () => void; let sendInteractionThrottled: () => void;
attachClickEvent(div, (e) => { appStickersManager.preloadAnimatedEmojiStickerAnimation(emoji);
cancelEvent(e);
let animation = LottieLoader.getAnimation(div); attachClickEvent(div, async(e) => {
const animation = LottieLoader.getAnimation(div);
if(animation.paused) { if(animation.paused) {
const doc = appStickersManager.getAnimatedEmojiSoundDocument(emoji);
if(doc) {
const audio = document.createElement('audio');
try {
await appDocsManager.downloadDoc(doc);
const cacheContext = appDownloadManager.getCacheContext(doc);
audio.src = cacheContext.url;
await onMediaLoad(audio);
audio.addEventListener('ended', () => {
audio.src = '';
}, {once: true});
audio.play();
} catch(err) {
}
}
animation.autoplay = true; animation.autoplay = true;
animation.restart(); animation.restart();
} }
cancelEvent(e);
const doc = appStickersManager.getAnimatedEmojiSticker(emoji, true); const doc = appStickersManager.getAnimatedEmojiSticker(emoji, true);
if(!doc) { if(!doc) {
return; return;
@ -1375,7 +1399,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
animation.addEventListener('enterFrame', (frameNo) => { animation.addEventListener('enterFrame', (frameNo) => {
if(frameNo === animation.maxFrame) { if(frameNo === animation.maxFrame) {
animation.remove(); animation.remove();
// animationDiv.remove(); animationDiv.remove();
appImManager.chat.bubbles.scrollable.container.removeEventListener('scroll', onScroll); appImManager.chat.bubbles.scrollable.container.removeEventListener('scroll', onScroll);
} }
}); });

7
src/helpers/fixBase64String.ts

@ -0,0 +1,7 @@
export default function fixBase64String(str: string, toUrl: boolean) {
if(toUrl) {
return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
} else {
return str.replace(/-/g, '+').replace(/_/g, '/');
}
}

93
src/lib/appManagers/appStickersManager.ts

@ -19,6 +19,7 @@ import mediaSizes from '../../helpers/mediaSizes';
import { getEmojiToneIndex } from '../../vendor/emoji'; import { getEmojiToneIndex } from '../../vendor/emoji';
import RichTextProcessor from '../richtextprocessor'; import RichTextProcessor from '../richtextprocessor';
import assumeType from '../../helpers/assumeType'; import assumeType from '../../helpers/assumeType';
import fixBase64String from '../../helpers/fixBase64String';
const CACHE_TIME = 3600e3; const CACHE_TIME = 3600e3;
@ -29,6 +30,8 @@ const LOCAL_IDS_SET = new Set([
EMOJI_ANIMATIONS_SET_LOCAL_ID EMOJI_ANIMATIONS_SET_LOCAL_ID
]); ]);
// let TEST_FILE_REFERENCE_REFRESH = true;
export type MyStickerSetInput = { export type MyStickerSetInput = {
id: StickerSet.stickerSet['id'], id: StickerSet.stickerSet['id'],
access_hash?: StickerSet.stickerSet['access_hash'] access_hash?: StickerSet.stickerSet['access_hash']
@ -39,14 +42,21 @@ export type MyMessagesStickerSet = MessagesStickerSet.messagesStickerSet;
export class AppStickersManager { export class AppStickersManager {
private storage = new AppStorage<Record<Long, MyMessagesStickerSet>, typeof DATABASE_STATE>(DATABASE_STATE, 'stickerSets'); private storage = new AppStorage<Record<Long, MyMessagesStickerSet>, typeof DATABASE_STATE>(DATABASE_STATE, 'stickerSets');
private getStickerSetPromises: {[setId: Long]: Promise<MyMessagesStickerSet>} = {}; private getStickerSetPromises: {[setId: Long]: Promise<MyMessagesStickerSet>};
private getStickersByEmoticonsPromises: {[emoticon: string]: Promise<Document[]>} = {}; private getStickersByEmoticonsPromises: {[emoticon: string]: Promise<Document[]>};
private greetingStickers: Document.document[]; private greetingStickers: Document.document[];
private getGreetingStickersTimeout: number; private getGreetingStickersTimeout: number;
private getGreetingStickersPromise: Promise<void>; private getGreetingStickersPromise: Promise<void>;
private sounds: Record<string, MyDocument>;
getAnimatedEmojiSoundsPromise: Promise<void>;
constructor() { constructor() {
this.getStickerSetPromises = {};
this.getStickersByEmoticonsPromises = {};
this.sounds = {};
this.getAnimatedEmojiStickerSet(); this.getAnimatedEmojiStickerSet();
rootScope.addMultipleEventsListeners({ rootScope.addMultipleEventsListeners({
@ -143,12 +153,59 @@ export class AppStickersManager {
public getAnimatedEmojiStickerSet() { public getAnimatedEmojiStickerSet() {
return Promise.all([ return Promise.all([
this.getStickerSet({id: EMOJI_SET_LOCAL_ID}, {saveById: true}), this.getStickerSet({id: EMOJI_SET_LOCAL_ID}, {saveById: true}),
this.getStickerSet({id: EMOJI_ANIMATIONS_SET_LOCAL_ID}, {saveById: true}) this.getStickerSet({id: EMOJI_ANIMATIONS_SET_LOCAL_ID}, {saveById: true}),
this.getAnimatedEmojiSounds()
]).then(([emoji, animations]) => { ]).then(([emoji, animations]) => {
return {emoji, animations}; return {emoji, animations};
}); });
} }
public getAnimatedEmojiSounds(overwrite?: boolean) {
if(this.getAnimatedEmojiSoundsPromise && !overwrite) return this.getAnimatedEmojiSoundsPromise;
return this.getAnimatedEmojiSoundsPromise = apiManager.getAppConfig(overwrite).then(appConfig => {
for(const emoji in appConfig.emojies_sounds) {
const sound = appConfig.emojies_sounds[emoji];
const bytesStr = atob(fixBase64String(sound.file_reference_base64, false));
const bytes = new Uint8Array(bytesStr.length);
for(let i = 0, length = bytes.length; i < length; ++i) {
bytes[i] = bytesStr[i].charCodeAt(0);
}
// if(TEST_FILE_REFERENCE_REFRESH) {
// bytes[0] = bytes[1] = bytes[2] = bytes[3] = bytes[4] = 0;
// sound.access_hash += '999';
// }
const doc = appDocsManager.saveDoc({
_: 'document',
pFlags: {},
flags: 0,
id: sound.id,
access_hash: sound.access_hash,
attributes: [/* {
_: 'documentAttributeAudio',
duration: 1,
pFlags: {}
} */],
date: 0,
dc_id: rootScope.config.this_dc,
file_reference: bytes,
mime_type: 'audio/mp3',
size: 1
// size: 101010 // test loading everytime
}, {
type: 'emojiesSounds'
});
this.sounds[emoji] = doc;
}
// if(TEST_FILE_REFERENCE_REFRESH) {
// TEST_FILE_REFERENCE_REFRESH = false;
// }
});
}
public async getRecentStickers(): Promise<Modify<MessagesRecentStickers.messagesRecentStickers, { public async getRecentStickers(): Promise<Modify<MessagesRecentStickers.messagesRecentStickers, {
stickers: Document[] stickers: Document[]
}>> { }>> {
@ -164,17 +221,25 @@ export class AppStickersManager {
return res; return res;
} }
private cleanEmoji(emoji: string) {
return emoji.replace(/\ufe0f/g, '').replace(/🏻|🏼|🏽|🏾|🏿/g, '');
}
public getAnimatedEmojiSticker(emoji: string, isAnimation?: boolean) { public getAnimatedEmojiSticker(emoji: string, isAnimation?: boolean) {
const stickerSet = this.storage.getFromCache(isAnimation ? EMOJI_ANIMATIONS_SET_LOCAL_ID : EMOJI_SET_LOCAL_ID); const stickerSet = this.storage.getFromCache(isAnimation ? EMOJI_ANIMATIONS_SET_LOCAL_ID : EMOJI_SET_LOCAL_ID);
if(!stickerSet || !stickerSet.documents) return undefined; if(!stickerSet || !stickerSet.documents) return undefined;
emoji = emoji.replace(/\ufe0f/g, '').replace(/🏻|🏼|🏽|🏾|🏿/g, ''); emoji = this.cleanEmoji(emoji);
const pack = stickerSet.packs.find(p => p.emoticon === emoji); const pack = stickerSet.packs.find(p => p.emoticon === emoji);
return pack ? appDocsManager.getDoc(pack.documents[0]) : undefined; return pack ? appDocsManager.getDoc(pack.documents[0]) : undefined;
} }
public getAnimatedEmojiSoundDocument(emoji: string) {
return this.sounds[this.cleanEmoji(emoji)];
}
public preloadAnimatedEmojiSticker(emoji: string, width?: number, height?: number) { public preloadAnimatedEmojiSticker(emoji: string, width?: number, height?: number) {
return this.getAnimatedEmojiStickerSet().then(() => { const preloadEmojiPromise = this.getAnimatedEmojiStickerSet().then(() => {
const doc = this.getAnimatedEmojiSticker(emoji); const doc = this.getAnimatedEmojiSticker(emoji);
if(doc) { if(doc) {
return appDocsManager.downloadDoc(doc) return appDocsManager.downloadDoc(doc)
@ -199,6 +264,24 @@ export class AppStickersManager {
}); });
} }
}); });
return Promise.all([
preloadEmojiPromise,
this.preloadAnimatedEmojiStickerAnimation(emoji)
]);
}
public preloadAnimatedEmojiStickerAnimation(emoji: string) {
return this.getAnimatedEmojiStickerSet().then(() => {
const doc = this.getAnimatedEmojiSticker(emoji, true);
if(doc) {
const soundDoc = this.getAnimatedEmojiSoundDocument(emoji);
return Promise.all([
appDocsManager.downloadDoc(doc),
soundDoc ? appDocsManager.downloadDoc(soundDoc) : undefined
]);
}
});
} }
public saveStickerSet(res: Omit<MessagesStickerSet.messagesStickerSet, '_'>, id: DocId) { public saveStickerSet(res: Omit<MessagesStickerSet.messagesStickerSet, '_'>, id: DocId) {

51
src/lib/mtproto/appConfig.d.ts vendored

@ -0,0 +1,51 @@
export interface MTAppConfig {
test?: number;
emojies_animated_zoom?: number;
emojies_send_dice?: string[];
emojies_send_dice_success?: EmojiesSendDiceSuccess;
emojies_sounds?: EmojiesSounds;
gif_search_branding?: string;
gif_search_emojies?: string[];
stickers_emoji_suggest_only_api?: boolean;
stickers_emoji_cache_time?: number;
groupcall_video_participants_max?: number;
youtube_pip?: string;
qr_login_camera?: boolean;
qr_login_code?: string;
dialog_filters_enabled?: boolean;
dialog_filters_tooltip?: boolean;
ignore_restriction_reasons?: string[];
autoarchive_setting_available?: boolean;
pending_suggestions?: any[];
autologin_token?: string;
autologin_domains?: string[];
round_video_encoding?: RoundVideoEncoding;
chat_read_mark_expire_period?: number;
chat_read_mark_size_threshold?: number;
reactions_default?: string;
reactions_uniq_max?: number;
}
export interface EmojiesSendDiceSuccess {
[k]: EmojiesSendDiceSuccessDetails
}
export interface EmojiesSendDiceSuccessDetails {
value?: number;
frame_start?: number;
}
export type EmojiesSounds = Record<string, EmojiSound>;
export interface EmojiSound {
id?: string;
access_hash?: string;
file_reference_base64?: string;
}
export interface RoundVideoEncoding {
diameter?: number;
video_bitrate?: number;
audio_bitrate?: number;
max_size?: number;
}

12
src/lib/mtproto/mtprotoworker.ts

@ -7,7 +7,7 @@
import type { LocalStorageProxyTask, LocalStorageProxyTaskResponse } from '../localStorage'; import type { LocalStorageProxyTask, LocalStorageProxyTaskResponse } from '../localStorage';
//import type { LocalStorageProxyDeleteTask, LocalStorageProxySetTask } from '../storage'; //import type { LocalStorageProxyDeleteTask, LocalStorageProxySetTask } from '../storage';
import type { Awaited, InvokeApiOptions, WorkerTaskVoidTemplate } from '../../types'; import type { Awaited, InvokeApiOptions, WorkerTaskVoidTemplate } from '../../types';
import type { Config, InputFile, MethodDeclMap, User } from '../../layer'; import type { Config, InputFile, JSONValue, MethodDeclMap, User } from '../../layer';
import MTProtoWorker from 'worker-loader!./mtproto.worker'; import MTProtoWorker from 'worker-loader!./mtproto.worker';
//import './mtproto.worker'; //import './mtproto.worker';
import { isObject } from '../../helpers/object'; import { isObject } from '../../helpers/object';
@ -32,6 +32,7 @@ import { CacheStorageDbName } from '../cacheStorage';
import { pause } from '../../helpers/schedulers/pause'; import { pause } from '../../helpers/schedulers/pause';
import IS_WEBP_SUPPORTED from '../../environment/webpSupport'; import IS_WEBP_SUPPORTED from '../../environment/webpSupport';
import type { ApiError } from './apiManager'; import type { ApiError } from './apiManager';
import { MTAppConfig } from './appConfig';
type Task = { type Task = {
taskId: number, taskId: number,
@ -105,6 +106,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
private postMessagesWaiting: any[][] = []; private postMessagesWaiting: any[][] = [];
private getConfigPromise: Promise<Config.config>; private getConfigPromise: Promise<Config.config>;
private getAppConfigPromise: Promise<MTAppConfig>;
constructor() { constructor() {
super(); super();
@ -693,6 +695,14 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
return config; return config;
}); });
} }
public getAppConfig(overwrite?: boolean): Promise<MTAppConfig> {
if(this.getAppConfigPromise && !overwrite) return this.getAppConfigPromise;
return this.getAppConfigPromise = this.invokeApi('help.getAppConfig').then(config => {
rootScope.appConfig = config;
return config;
});
}
} }
const apiManagerProxy = new ApiManagerProxy(); const apiManagerProxy = new ApiManagerProxy();

15
src/lib/mtproto/referenceDatabase.ts

@ -6,6 +6,7 @@
import { RefreshReferenceTask, RefreshReferenceTaskResponse } from "./apiFileManager"; import { RefreshReferenceTask, RefreshReferenceTaskResponse } from "./apiFileManager";
import appMessagesManager from "../appManagers/appMessagesManager"; import appMessagesManager from "../appManagers/appMessagesManager";
import appStickersManager from "../appManagers/appStickersManager";
import { Photo } from "../../layer"; import { Photo } from "../../layer";
import { bytesToHex } from "../../helpers/bytes"; import { bytesToHex } from "../../helpers/bytes";
import { deepEqual } from "../../helpers/object"; import { deepEqual } from "../../helpers/object";
@ -14,7 +15,7 @@ import apiManager from "./mtprotoworker";
import assumeType from "../../helpers/assumeType"; import assumeType from "../../helpers/assumeType";
import { logger } from "../logger"; import { logger } from "../logger";
export type ReferenceContext = ReferenceContext.referenceContextProfilePhoto | ReferenceContext.referenceContextMessage; export type ReferenceContext = ReferenceContext.referenceContextProfilePhoto | ReferenceContext.referenceContextMessage | ReferenceContext.referenceContextEmojiesSounds;
export namespace ReferenceContext { export namespace ReferenceContext {
export type referenceContextProfilePhoto = { export type referenceContextProfilePhoto = {
type: 'profilePhoto', type: 'profilePhoto',
@ -26,6 +27,10 @@ export namespace ReferenceContext {
peerId: PeerId, peerId: PeerId,
messageId: number messageId: number
}; };
export type referenceContextEmojiesSounds = {
type: 'emojiesSounds'
};
} }
export type ReferenceBytes = Photo.photo['file_reference']; export type ReferenceBytes = Photo.photo['file_reference'];
@ -38,6 +43,7 @@ class ReferenceDatabase {
//private references: Map<ReferenceBytes, number[]> = new Map(); //private references: Map<ReferenceBytes, number[]> = new Map();
private links: {[hex: string]: ReferenceBytes} = {}; private links: {[hex: string]: ReferenceBytes} = {};
private log = logger('RD', undefined, true); private log = logger('RD', undefined, true);
private refreshEmojiesSoundsPromise: Promise<any>;
constructor() { constructor() {
apiManager.addTaskListener('refreshReference', (task: RefreshReferenceTask) => { apiManager.addTaskListener('refreshReference', (task: RefreshReferenceTask) => {
@ -125,6 +131,13 @@ class ReferenceDatabase {
// }); // });
} }
case 'emojiesSounds': {
promise = this.refreshEmojiesSoundsPromise || appStickersManager.getAnimatedEmojiSounds(true).then(() => {
this.refreshEmojiesSoundsPromise = undefined;
});
break;
}
default: { default: {
this.log.warn('refreshReference: not implemented context', context); this.log.warn('refreshReference: not implemented context', context);
return Promise.reject(); return Promise.reject();

4
src/lib/rootScope.ts

@ -4,7 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import type { Message, StickerSet, Update, NotifyPeer, PeerNotifySettings, ConstructorDeclMap, Config, PollResults, Poll, WebPage, GroupCall, GroupCallParticipant, PhoneCall } from "../layer"; import type { Message, StickerSet, Update, NotifyPeer, PeerNotifySettings, ConstructorDeclMap, Config, PollResults, Poll, WebPage, GroupCall, GroupCallParticipant, PhoneCall, MethodDeclMap } from "../layer";
import type { MyDocument } from "./appManagers/appDocsManager"; import type { MyDocument } from "./appManagers/appDocsManager";
import type { AppMessagesManager, Dialog, MessagesStorage, MyMessage } from "./appManagers/appMessagesManager"; import type { AppMessagesManager, Dialog, MessagesStorage, MyMessage } from "./appManagers/appMessagesManager";
import type { MyDialogFilter } from "./storages/filters"; import type { MyDialogFilter } from "./storages/filters";
@ -23,6 +23,7 @@ import type Chat from "../components/chat/chat";
import { NULL_PEER_ID, UserAuth } from "./mtproto/mtproto_config"; import { NULL_PEER_ID, UserAuth } from "./mtproto/mtproto_config";
import EventListenerBase from "../helpers/eventListenerBase"; import EventListenerBase from "../helpers/eventListenerBase";
import { MOUNT_CLASS_TO } from "../config/debug"; import { MOUNT_CLASS_TO } from "../config/debug";
import { MTAppConfig } from "./mtproto/appConfig";
export type BroadcastEvents = { export type BroadcastEvents = {
'chat_full_update': ChatId, 'chat_full_update': ChatId,
@ -183,6 +184,7 @@ export class RootScope extends EventListenerBase<{
message_length_max: 4096, message_length_max: 4096,
caption_length_max: 1024, caption_length_max: 1024,
}; };
public appConfig: MTAppConfig;
public themeColor: string; public themeColor: string;
private _themeColorElem: Element; private _themeColorElem: Element;

3
src/pages/pageSignQR.ts

@ -19,6 +19,7 @@ import rootScope from '../lib/rootScope';
import { putPreloader } from '../components/misc'; import { putPreloader } from '../components/misc';
import getLanguageChangeButton from '../components/languageChangeButton'; import getLanguageChangeButton from '../components/languageChangeButton';
import { pause } from '../helpers/schedulers/pause'; import { pause } from '../helpers/schedulers/pause';
import fixBase64String from '../helpers/fixBase64String';
const FETCH_INTERVAL = 3; const FETCH_INTERVAL = 3;
@ -105,7 +106,7 @@ let onFirstMount = async() => {
prevToken = loginToken.token; prevToken = loginToken.token;
let encoded = bytesToBase64(loginToken.token); let encoded = bytesToBase64(loginToken.token);
let url = "tg://login?token=" + encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=+$/, ""); let url = "tg://login?token=" + fixBase64String(encoded, true);
const style = window.getComputedStyle(document.documentElement); const style = window.getComputedStyle(document.documentElement);
const surfaceColor = style.getPropertyValue('--surface-color').trim(); const surfaceColor = style.getPropertyValue('--surface-color').trim();

Loading…
Cancel
Save