no comments

This commit is contained in:
Eduard Kuzmenko 2021-02-12 19:24:20 +04:00
parent 8808953135
commit 2e0102780c
19 changed files with 316 additions and 111 deletions

View File

@ -19,64 +19,64 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"jsbn": "^1.1.0", "jsbn": "^1.1.0",
"webpack-dev-server": "^3.11.0" "webpack-dev-server": "^3.11.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.12.1", "@babel/cli": "^7.12.13",
"@babel/core": "^7.12.3", "@babel/core": "^7.12.13",
"@babel/plugin-proposal-class-properties": "^7.12.1", "@babel/plugin-proposal-class-properties": "^7.12.13",
"@babel/plugin-transform-typescript": "^7.12.1", "@babel/plugin-transform-typescript": "^7.12.13",
"@babel/polyfill": "^7.12.1", "@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.12.1", "@babel/preset-env": "^7.12.13",
"@babel/preset-es2015": "^7.0.0-beta.53", "@babel/preset-es2015": "^7.0.0-beta.53",
"@babel/preset-typescript": "^7.12.1", "@babel/preset-typescript": "^7.12.13",
"@cryptography/aes": "^0.1.1", "@cryptography/aes": "^0.1.1",
"@cryptography/sha1": "^0.1.0", "@cryptography/sha1": "^0.1.0",
"@cryptography/sha256": "^0.2.0", "@cryptography/sha256": "^0.2.0",
"@peculiar/webcrypto": "^1.1.3", "@peculiar/webcrypto": "^1.1.6",
"@types/aes-js": "^3.1.1", "@types/aes-js": "^3.1.1",
"@types/chrome": "0.0.91", "@types/chrome": "0.0.91",
"@types/jest": "^24.9.1", "@types/jest": "^24.9.1",
"@types/serviceworker-webpack-plugin": "^1.0.1", "@types/serviceworker-webpack-plugin": "^1.0.1",
"aes-js": "^3.1.2", "aes-js": "^3.1.2",
"autoprefixer": "^9.8.0", "autoprefixer": "^9.8.6",
"babel-jest": "^24.9.0", "babel-jest": "^24.9.0",
"babel-loader": "^8.1.0", "babel-loader": "^8.2.2",
"babel-preset-es2015": "^6.24.1", "babel-preset-es2015": "^6.24.1",
"compression": "^1.7.4", "compression": "^1.7.4",
"compression-webpack-plugin": "^3.1.0", "compression-webpack-plugin": "^3.1.0",
"core-js": "^3.6.5", "core-js": "^3.8.3",
"css-loader": "^3.5.3", "css-loader": "^3.6.0",
"express": "^4.17.1", "express": "^4.17.1",
"fast-png": "^5.0.2", "fast-png": "^5.0.3",
"handlebars": "^4.7.6", "handlebars": "^4.7.6",
"handlebars-loader": "^1.7.1", "handlebars-loader": "^1.7.1",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"ifdef-loader": "^2.1.5", "ifdef-loader": "^2.1.5",
"install": "^0.13.0", "install": "^0.13.0",
"jest": "^24.9.0", "jest": "^24.9.0",
"media-query-plugin": "^1.3.1", "media-query-plugin": "^1.4.0",
"mini-css-extract-plugin": "^0.9.0", "mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.14.1", "node-sass": "^4.14.1",
"npm": "^6.14.10", "npm": "^6.14.11",
"on-build-webpack": "^0.1.0", "on-build-webpack": "^0.1.0",
"optimize-css-assets-webpack-plugin": "^5.0.3", "optimize-css-assets-webpack-plugin": "^5.0.4",
"pako": "^1.0.11", "pako": "^1.0.11",
"postcss": "^7.0.32", "postcss": "^7.0.35",
"postcss-loader": "^3.0.0", "postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0", "postcss-preset-env": "^6.7.0",
"qr-code-styling": "^1.0.1", "qr-code-styling": "^1.3.4",
"resolve-url-loader": "^3.1.2", "resolve-url-loader": "^3.1.2",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"serviceworker-webpack-plugin": "^1.0.1", "serviceworker-webpack-plugin": "^1.0.1",
"style-loader": "^1.2.1", "style-loader": "^1.3.0",
"terser-webpack-plugin": "^3.0.2", "terser-webpack-plugin": "^3.1.0",
"ts-jest": "^24.3.0", "ts-jest": "^24.3.0",
"ts-loader": "^6.2.2", "ts-loader": "^6.2.2",
"typescript": "^3.9.7", "typescript": "^3.9.7",
"url-loader": "^2.3.0", "url-loader": "^2.3.0",
"webpack": "^4.43.0", "webpack": "^4.46.0",
"webpack-cli": "^3.3.11", "webpack-cli": "^3.3.12",
"webpack-merge": "^4.2.2", "webpack-merge": "^4.2.2",
"worker-loader": "^2.0.0" "worker-loader": "^2.0.0"
} }

View File

@ -1,22 +1,23 @@
import rootScope from "../../lib/rootScope"; import rootScope from "../../lib/rootScope";
import { generatePathData } from "../../helpers/dom"; //import { generatePathData } from "../../helpers/dom";
import { MyMessage } from "../../lib/appManagers/appMessagesManager"; import { MyMessage } from "../../lib/appManagers/appMessagesManager";
import Chat from "./chat"; import Chat from "./chat";
type Group = {bubble: HTMLDivElement, mid: number, timestamp: number}[]; type Group = {bubble: HTMLElement, mid: number, timestamp: number}[];
type BubbleGroup = {timestamp: number, fromId: number, mid: number, group: Group}; type BubbleGroup = {timestamp: number, fromId: number, mid: number, group: Group};
export default class BubbleGroups { export default class BubbleGroups {
private bubbles: Array<BubbleGroup> = []; // map to group private bubbles: Array<BubbleGroup> = []; // map to group
private detailsMap: Map<HTMLElement, BubbleGroup> = new Map();
private groups: Array<Group> = []; private groups: Array<Group> = [];
//updateRAFs: Map<HTMLDivElement[], number> = new Map(); //updateRAFs: Map<HTMLElement[], number> = new Map();
private newGroupDiff = 121; // * 121 in scheduled messages private newGroupDiff = 121; // * 121 in scheduled messages
constructor(private chat: Chat) { constructor(private chat: Chat) {
} }
removeBubble(bubble: HTMLDivElement, mid: number) { removeBubble(bubble: HTMLElement) {
const details = this.bubbles.findAndSplice(g => g.mid === mid); const details = this.detailsMap.get(bubble);
if(details && details.group.length) { if(details && details.group.length) {
details.group.findAndSplice(d => d.bubble === bubble); details.group.findAndSplice(d => d.bubble === bubble);
if(!details.group.length) { if(!details.group.length) {
@ -27,7 +28,7 @@ export default class BubbleGroups {
} }
} }
addBubble(bubble: HTMLDivElement, message: MyMessage, reverse: boolean) { addBubble(bubble: HTMLElement, message: MyMessage, reverse: boolean) {
//return; //return;
const timestamp = message.date; const timestamp = message.date;
@ -41,7 +42,7 @@ export default class BubbleGroups {
} }
// try to find added // try to find added
//this.removeBubble(message.mid); this.removeBubble(bubble);
const insertObject = {bubble, mid, timestamp}; const insertObject = {bubble, mid, timestamp};
if(this.bubbles.length) { if(this.bubbles.length) {
@ -105,28 +106,24 @@ export default class BubbleGroups {
//console.log('[BUBBLE]: addBubble', bubble, message.mid, fromId, reverse, group); //console.log('[BUBBLE]: addBubble', bubble, message.mid, fromId, reverse, group);
if(mid > 0) { const bubbleGroup = {timestamp, fromId, mid: message.mid, group};
let insertIndex = 0; let insertIndex = 0;
for(; insertIndex < this.bubbles.length; ++insertIndex) { for(; insertIndex < this.bubbles.length; ++insertIndex) {
if(this.bubbles[insertIndex].mid < mid) { if(this.bubbles[insertIndex].mid < mid) {
break; break;
}
} }
this.bubbles.splice(insertIndex, 0, {timestamp, fromId, mid: message.mid, group});
} else {
this.bubbles.unshift({timestamp, fromId, mid: message.mid, group});
} }
//this.bubbles.push({timestamp, fromId, mid: message.mid, group}); this.bubbles.splice(insertIndex, 0, {timestamp, fromId, mid: message.mid, group});
this.updateGroup(group); this.updateGroup(group);
this.detailsMap.set(bubble, bubbleGroup);
} }
setClipIfNeeded(bubble: HTMLDivElement, remove = false) { /* setClipIfNeeded(bubble: HTMLDivElement, remove = false) {
//console.log('setClipIfNeeded', bubble, remove); //console.log('setClipIfNeeded', bubble, remove);
const className = bubble.className; const className = bubble.className;
if(className.includes('is-message-empty')/* && !className.includes('is-reply') */ if(className.includes('is-message-empty') && (className.includes('photo') || className.includes('video'))) {
&& (className.includes('photo') || className.includes('video'))) {
let container = bubble.querySelector('.bubble__media-container') as SVGSVGElement; let container = bubble.querySelector('.bubble__media-container') as SVGSVGElement;
//console.log('setClipIfNeeded', bubble, remove, container); //console.log('setClipIfNeeded', bubble, remove, container);
if(!container) return; if(!container) return;
@ -171,7 +168,7 @@ export default class BubbleGroups {
}); });
} catch(err) {} } catch(err) {}
} }
} } */
updateGroup(group: Group) { updateGroup(group: Group) {
/* if(this.updateRAFs.has(group)) { /* if(this.updateRAFs.has(group)) {
@ -192,25 +189,25 @@ export default class BubbleGroups {
if(group.length === 1) { if(group.length === 1) {
first.classList.add('is-group-first', 'is-group-last'); first.classList.add('is-group-first', 'is-group-last');
this.setClipIfNeeded(first); //this.setClipIfNeeded(first);
return; return;
} else { } else {
first.classList.remove('is-group-last'); first.classList.remove('is-group-last');
first.classList.add('is-group-first'); first.classList.add('is-group-first');
this.setClipIfNeeded(first, true); //this.setClipIfNeeded(first, true);
} }
const length = group.length - 1; const length = group.length - 1;
for(let i = 1; i < length; ++i) { for(let i = 1; i < length; ++i) {
const bubble = group[i].bubble; const bubble = group[i].bubble;
bubble.classList.remove('is-group-last', 'is-group-first'); bubble.classList.remove('is-group-last', 'is-group-first');
this.setClipIfNeeded(bubble, true); //this.setClipIfNeeded(bubble, true);
} }
const last = group[group.length - 1].bubble; const last = group[group.length - 1].bubble;
last.classList.remove('is-group-first'); last.classList.remove('is-group-first');
last.classList.add('is-group-last'); last.classList.add('is-group-last');
this.setClipIfNeeded(last); //this.setClipIfNeeded(last);
//})); //}));
} }
@ -224,6 +221,7 @@ export default class BubbleGroups {
cleanup() { cleanup() {
this.bubbles = []; this.bubbles = [];
this.groups = []; this.groups = [];
this.detailsMap.clear();
/* for(let value of this.updateRAFs.values()) { /* for(let value of this.updateRAFs.values()) {
window.cancelAnimationFrame(value); window.cancelAnimationFrame(value);
} }

View File

@ -152,12 +152,9 @@ export default class ChatBubbles {
const message = this.chat.getMessage(mid); const message = this.chat.getMessage(mid);
//bubble.remove(); //bubble.remove();
this.bubbleGroups.removeBubble(bubble, message.mid);
this.setBubblePosition(bubble, message, false); this.setBubblePosition(bubble, message, false);
//this.log('history_update', this.bubbles[mid], mid, message); //this.log('history_update', this.bubbles[mid], mid, message);
this.bubbleGroups.addBubble(bubble, message, false);
if(this.scrollingToNewBubble) { if(this.scrollingToNewBubble) {
this.scrollToNewLastBubble(); this.scrollToNewLastBubble();
} }
@ -268,8 +265,6 @@ export default class ChatBubbles {
//}); //});
bubble.dataset.mid = '' + mid; bubble.dataset.mid = '' + mid;
this.bubbleGroups.removeBubble(bubble, tempId);
} }
if(this.unreadOut.has(tempId)) { if(this.unreadOut.has(tempId)) {
@ -423,7 +418,7 @@ export default class ChatBubbles {
const msgIdsByPeer = e; const msgIdsByPeer = e;
if(!(this.peerId in msgIdsByPeer)) return; if(!(this.peerId in msgIdsByPeer)) return;
const msgIds = msgIdsByPeer[this.peerId]; const msgIds = (msgIdsByPeer[this.peerId] as number[]).slice().sort((a, b) => b - a);
this.renderNewMessagesByIds(msgIds); this.renderNewMessagesByIds(msgIds);
}); });
@ -1147,7 +1142,7 @@ export default class ChatBubbles {
this.firstUnreadBubble = null; this.firstUnreadBubble = null;
} }
this.bubbleGroups.removeBubble(bubble, mid); this.bubbleGroups.removeBubble(bubble);
if(this.unreadedObserver) { if(this.unreadedObserver) {
this.unreadedObserver.unobserve(bubble); this.unreadedObserver.unobserve(bubble);
} }
@ -1691,6 +1686,8 @@ export default class ChatBubbles {
dateMessage.container.append(bubble); dateMessage.container.append(bubble);
} }
} }
this.bubbleGroups.addBubble(bubble, message, reverse);
} }
// * will change .cleaned in cleanup() and new instance will be created // * will change .cleaned in cleanup() and new instance will be created
@ -1779,16 +1776,8 @@ export default class ChatBubbles {
bubble.remove(); // * for positionElementByIndex bubble.remove(); // * for positionElementByIndex
} */ } */
if(!sameMid || updatePosition) {
this.bubbleGroups.removeBubble(bubble, originalMid);
}
if(!sameMid) { if(!sameMid) {
delete this.bubbles[originalMid]; delete this.bubbles[originalMid];
if(!updatePosition) {
this.bubbleGroups.addBubble(bubble, message, reverse);
}
} }
//bubble.innerHTML = ''; //bubble.innerHTML = '';
@ -1815,9 +1804,7 @@ export default class ChatBubbles {
if(updatePosition) { if(updatePosition) {
this.renderMessagesQueue(message, bubble, reverse, loadPromises); this.renderMessagesQueue(message, bubble, reverse, loadPromises);
if(!message.pFlags.is_single) { // * Ignore 'Discussion started' if(message.pFlags.is_single) { // * Ignore 'Discussion started'
this.bubbleGroups.addBubble(bubble, message, reverse);
} else {
bubble.classList.add('is-group-last'); bubble.classList.add('is-group-last');
} }
} }
@ -2452,13 +2439,7 @@ export default class ChatBubbles {
bubble.classList.add(isOut ? 'is-out' : 'is-in'); bubble.classList.add(isOut ? 'is-out' : 'is-in');
if(updatePosition) { if(updatePosition) {
if(!isThreadStarter) {
this.bubbleGroups.addBubble(bubble, message, reverse);
}
this.renderMessagesQueue(message, bubble, reverse, loadPromises); this.renderMessagesQueue(message, bubble, reverse, loadPromises);
} else {
this.bubbleGroups.updateGroupByMessageId(message.mid);
} }
if(withReplies) { if(withReplies) {

View File

@ -215,7 +215,7 @@ export default class ChatContextMenu {
icon: 'link', icon: 'link',
text: 'Copy Link', text: 'Copy Link',
onClick: this.onCopyLinkClick, onClick: this.onCopyLinkClick,
verify: () => this.appPeersManager.isChannel(this.peerId) verify: () => this.appPeersManager.isChannel(this.peerId) && !this.message.pFlags.is_outgoing
}, { }, {
icon: 'pin', icon: 'pin',
text: 'Pin', text: 'Pin',

View File

@ -3313,7 +3313,7 @@ export class AppMessagesManager {
} as any, } as any,
id: this.generateMessageId(message.id, true), id: this.generateMessageId(message.id, true),
date: message.date, date: message.date,
from_id: message.from_id, from_id: {_: 'peerUser', user_id: 0}/* message.from_id */,
peer_id: message.peer_id, peer_id: message.peer_id,
action: { action: {
_: 'messageActionCustomAction', _: 'messageActionCustomAction',

View File

@ -156,7 +156,7 @@ export class AppStateManager extends EventListenerBase<{
if(state) { if(state) {
if(state.version !== STATE_VERSION) { if(state.version !== STATE_VERSION) {
state = copy(STATE_INIT); state = copy(STATE_INIT);
} else if((state.stateCreatedTime + REFRESH_EVERY) < time/* && false */) { } else if((state.stateCreatedTime + REFRESH_EVERY) < time/* || true *//* && false */) {
if(DEBUG) { if(DEBUG) {
this.log('will refresh state', state.stateCreatedTime, time); this.log('will refresh state', state.stateCreatedTime, time);
} }

View File

@ -118,7 +118,9 @@ export class AppUsersManager {
this.pushContact(userId); this.pushContact(userId);
}); });
this.contactsFillPromise = Promise.resolve(this.contactsList); if(this.contactsList.size) {
this.contactsFillPromise = Promise.resolve(this.contactsList);
}
} }
}); });
} }
@ -605,7 +607,7 @@ export class AppUsersManager {
return apiManager.invokeApi('contacts.getTopPeers', { return apiManager.invokeApi('contacts.getTopPeers', {
correspondents: true, correspondents: true,
offset: 0, offset: 0,
limit: 30, limit: 15,
hash: 0, hash: 0,
}).then((result) => { }).then((result) => {
let peerIds: number[] = []; let peerIds: number[] = [];

View File

@ -16,7 +16,7 @@ import { addPadding, bytesFromBigInt } from '../mtproto/bin_utils';
import { bytesToWordss, bytesFromWordss, bytesToHex, bytesFromHex } from '../../helpers/bytes'; import { bytesToWordss, bytesFromWordss, bytesToHex, bytesFromHex } from '../../helpers/bytes';
import { nextRandomInt } from '../../helpers/random'; import { nextRandomInt } from '../../helpers/random';
export function longToBytes(sLong: string) { export function longToBytes(sLong: string): Array<number> {
/* let perf = performance.now(); /* let perf = performance.now();
for(let i = 0; i < 1000000; ++i) { for(let i = 0; i < 1000000; ++i) {
bytesFromWords({words: longToInts(sLong), sigBytes: 8}).reverse(); bytesFromWords({words: longToInts(sLong), sigBytes: 8}).reverse();

View File

@ -157,12 +157,12 @@ export class ApiManager {
// mtpGetNetworker // mtpGetNetworker
public getNetworker(dcId: number, options: InvokeApiOptions = {}): Promise<MTPNetworker> { public getNetworker(dcId: number, options: InvokeApiOptions = {}): Promise<MTPNetworker> {
const connectionType: ConnectionType = options.fileDownload ? 'download' : (options.fileUpload ? 'upload' : 'client'); //const connectionType: ConnectionType = options.fileDownload ? 'download' : (options.fileUpload ? 'upload' : 'client');
//const connectionType: ConnectionType = 'client'; const connectionType: ConnectionType = 'client';
/// #if MTPROTO_HTTP_UPLOAD /// #if MTPROTO_HTTP_UPLOAD
// @ts-ignore // @ts-ignore
const transportType: TransportType = connectionType === 'upload' && isSafari ? 'https' : 'websocket'; const transportType: TransportType = connectionType === 'upload' && isSafari && false ? 'https' : 'websocket';
//const transportType: TransportType = connectionType !== 'client' ? 'https' : 'websocket'; //const transportType: TransportType = connectionType !== 'client' ? 'https' : 'websocket';
/// #else /// #else
// @ts-ignore // @ts-ignore
@ -183,7 +183,7 @@ export class ApiManager {
} }
const networkers = cache[dcId]; const networkers = cache[dcId];
if(networkers.length >= /* 1 */(connectionType === 'client' || transportType === 'https' ? 1 : (connectionType === 'download' ? 3 : 3))) { if(networkers.length >= /* 1 */(connectionType === 'client' || transportType === 'https' ? 1 : (connectionType === 'download' ? 3 : 1))) {
let i = networkers.length - 1, found = false; let i = networkers.length - 1, found = false;
for(; i >= 0; --i) { for(; i >= 0; --i) {
if(networkers[i].isOnline) { if(networkers[i].isOnline) {

View File

@ -34,3 +34,33 @@ export const Database = {
export const DEBUG = process.env.NODE_ENV !== 'production' || Modes.debug; export const DEBUG = process.env.NODE_ENV !== 'production' || Modes.debug;
export const MOUNT_CLASS_TO: any = DEBUG ? (typeof(window) !== 'undefined' ? window : self) : null; export const MOUNT_CLASS_TO: any = DEBUG ? (typeof(window) !== 'undefined' ? window : self) : null;
export const superDebug = (object: any, key: string) => {
var d = object[key];
var beforeStr = '', afterStr = '';
for(var r of d) {
beforeStr += r.before.hex + '\n';
afterStr += r.after.hex + '\n';
}
beforeStr = beforeStr.trim();
afterStr = afterStr.trim();
//var beforeStr = d.map(r => r.before.hex).join('\n');
//var afterStr = d.map(r => r.after.hex).join('\n');
var dada = (name: string, str: string) => {
var a = document.createElement('a');
a.target = '_blank';
a.download = name + '.txt';
a.href = URL.createObjectURL(new Blob([str], {
type: 'text/plain'
}));
document.body.append(a);
a.click();
};
dada(key + '_' + 'before', beforeStr);
dada(key + '_' + 'after', afterStr);
}
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.superDebug = superDebug);

View File

@ -124,7 +124,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
//const worker = window; //const worker = window;
worker.addEventListener('message', (e) => { worker.addEventListener('message', (e) => {
if(!this.worker) { if(!this.worker) {
this.worker = worker; this.worker = worker as any;
this.log('set webWorker'); this.log('set webWorker');
if(USE_WORKER_AS_WORKER) { if(USE_WORKER_AS_WORKER) {

View File

@ -100,8 +100,9 @@ export default class MTPNetworker {
/// #endif /// #endif
private seqNo: number = 0; private seqNo: number = 0;
private prevSessionId: Array<number> = []; private prevSessionId: Uint8Array;
private sessionId: Array<number> = []; private sessionId: Uint8Array;
private serverSalt: Uint8Array;
private lastResendReq: { private lastResendReq: {
req_msg_id: string, req_msg_id: string,
@ -119,9 +120,12 @@ export default class MTPNetworker {
private tt = 0; private tt = 0;
//public onConnectionStatusChange: (online: boolean) => void; //public onConnectionStatusChange: (online: boolean) => void;
private debugRequests: Array<{before: Uint8Array, after: Uint8Array}> = [];
constructor(public dcId: number, private authKey: number[], private authKeyId: Uint8Array, constructor(public dcId: number, private authKey: number[], private authKeyId: Uint8Array,
private serverSalt: number[], private transport: MTTransport, options: InvokeApiOptions = {}) { serverSalt: number[], private transport: MTTransport, options: InvokeApiOptions = {}) {
this.authKeyUint8 = convertToUint8Array(this.authKey); this.authKeyUint8 = convertToUint8Array(this.authKey);
this.serverSalt = convertToUint8Array(serverSalt);
this.isFileUpload = !!options.fileUpload; this.isFileUpload = !!options.fileUpload;
this.isFileDownload = !!options.fileDownload; this.isFileDownload = !!options.fileDownload;
@ -179,7 +183,7 @@ export default class MTPNetworker {
public updateSession() { public updateSession() {
this.seqNo = 0; this.seqNo = 0;
this.prevSessionId = this.sessionId; this.prevSessionId = this.sessionId;
this.sessionId = [...new Uint8Array(8).randomize()]; this.sessionId = new Uint8Array(8).randomize();
} }
public updateSentMessage(sentMessageId: string) { public updateSentMessage(sentMessageId: string) {
@ -231,7 +235,7 @@ export default class MTPNetworker {
const message = { const message = {
msg_id: messageId, msg_id: messageId,
seq_no: seqNo, seq_no: seqNo,
body: serializer.getBytes() body: serializer.getBytes(true)
}; };
if(Modes.debug) { if(Modes.debug) {
@ -250,7 +254,7 @@ export default class MTPNetworker {
const message = { const message = {
msg_id: messageId, msg_id: messageId,
seq_no: seqNo, seq_no: seqNo,
body: serializer.getBytes() body: serializer.getBytes(true)
}; };
if(Modes.debug) { if(Modes.debug) {
@ -335,7 +339,7 @@ export default class MTPNetworker {
} }
private sendPingDelayDisconnect = () => { private sendPingDelayDisconnect = () => {
if(this.pingPromise) return; if(this.pingPromise || true) return;
if(!this.isOnline) { if(!this.isOnline) {
if((this.transport as Socket).connected) { if((this.transport as Socket).connected) {
@ -667,28 +671,28 @@ export default class MTPNetworker {
this.scheduleRequest(delay); this.scheduleRequest(delay);
} }
// * correct, fully checked
public async getMsgKey(dataWithPadding: ArrayBuffer, isOut: boolean) { public async getMsgKey(dataWithPadding: ArrayBuffer, isOut: boolean) {
const authKey = this.authKeyUint8;
const x = isOut ? 0 : 8 const x = isOut ? 0 : 8
const msgKeyLargePlain = bufferConcat(authKey.subarray(88 + x, 88 + x + 32), dataWithPadding); const msgKeyLargePlain = bufferConcat(this.authKeyUint8.subarray(88 + x, 88 + x + 32), dataWithPadding);
const msgKeyLarge = await CryptoWorker.sha256Hash(msgKeyLargePlain); const msgKeyLarge = await CryptoWorker.sha256Hash(msgKeyLargePlain);
const msgKey = new Uint8Array(msgKeyLarge).subarray(8, 24); const msgKey = new Uint8Array(msgKeyLarge).subarray(8, 24);
return msgKey; return msgKey;
}; };
// * correct, fully checked
public getAesKeyIv(msgKey: Uint8Array | number[], isOut: boolean): Promise<[Uint8Array, Uint8Array]> { public getAesKeyIv(msgKey: Uint8Array | number[], isOut: boolean): Promise<[Uint8Array, Uint8Array]> {
const authKey = this.authKeyUint8;
const x = isOut ? 0 : 8; const x = isOut ? 0 : 8;
const sha2aText = new Uint8Array(52); const sha2aText = new Uint8Array(52);
const sha2bText = new Uint8Array(52); const sha2bText = new Uint8Array(52);
const promises: Array<Promise<number[]>> = []; const promises: Array<Promise<number[]>> = [];
sha2aText.set(msgKey, 0); sha2aText.set(msgKey, 0);
sha2aText.set(authKey.subarray(x, x + 36), 16); sha2aText.set(this.authKeyUint8.subarray(x, x + 36), 16);
promises.push(CryptoWorker.sha256Hash(sha2aText)); promises.push(CryptoWorker.sha256Hash(sha2aText));
sha2bText.set(authKey.subarray(40 + x, 40 + x + 36), 0); sha2bText.set(this.authKeyUint8.subarray(40 + x, 40 + x + 36), 0);
sha2bText.set(msgKey, 36); sha2bText.set(msgKey, 36);
promises.push(CryptoWorker.sha256Hash(sha2bText)); promises.push(CryptoWorker.sha256Hash(sha2bText));
@ -900,7 +904,7 @@ export default class MTPNetworker {
return { return {
bytes: encryptedBytes, bytes: encryptedBytes,
msgKey: msgKey msgKey
}; };
} }
@ -934,6 +938,14 @@ export default class MTPNetworker {
data.storeInt(message.body.length, 'message_data_length'); data.storeInt(message.body.length, 'message_data_length');
data.storeRawBytes(message.body, 'message_data'); data.storeRawBytes(message.body, 'message_data');
const des = new TLDeserialization(data.getBuffer().slice(16));
const desSalt = des.fetchLong();
const desSessionId = des.fetchLong();
if(!this.isOnline) {
this.log.error('trying to send message when offline', message, new Uint8Array(des.buffer), desSalt, desSessionId);
}
/* const messageDataLength = message.body.length; /* const messageDataLength = message.body.length;
let canBeLength = 0; // bytes let canBeLength = 0; // bytes
canBeLength += 8; canBeLength += 8;
@ -950,13 +962,15 @@ export default class MTPNetworker {
} */ } */
const paddingLength = (16 - (data.offset % 16)) + 16 * (1 + nextRandomInt(5)); const paddingLength = (16 - (data.offset % 16)) + 16 * (1 + nextRandomInt(5));
const padding = [...new Uint8Array(paddingLength).randomize()]; const padding = (message as any).padding || new Uint8Array(paddingLength).randomize().fill(0);
/* const padding = [167, 148, 207, 226, 86, 192, 193, 57, 124, 153, 174, 145, 159, 1, 5, 70, 127, 157, /* const padding = [167, 148, 207, 226, 86, 192, 193, 57, 124, 153, 174, 145, 159, 1, 5, 70, 127, 157,
51, 241, 46, 85, 141, 212, 139, 234, 213, 164, 197, 116, 245, 70, 184, 40, 40, 201, 233, 211, 150, 51, 241, 46, 85, 141, 212, 139, 234, 213, 164, 197, 116, 245, 70, 184, 40, 40, 201, 233, 211, 150,
94, 57, 84, 1, 135, 108, 253, 34, 139, 222, 208, 71, 214, 90, 67, 36, 28, 167, 148, 207, 226, 86, 192, 193, 57, 124, 153, 174, 145, 159, 1, 5, 70, 127, 157, 94, 57, 84, 1, 135, 108, 253, 34, 139, 222, 208, 71, 214, 90, 67, 36, 28, 167, 148, 207, 226, 86, 192, 193, 57, 124, 153, 174, 145, 159, 1, 5, 70, 127, 157,
51, 241, 46, 85, 141, 212, 139, 234, 213, 164, 197, 116, 245, 70, 184, 40, 40, 201, 233, 211, 150, 51, 241, 46, 85, 141, 212, 139, 234, 213, 164, 197, 116, 245, 70, 184, 40, 40, 201, 233, 211, 150,
94, 57, 84, 1, 135, 108, 253, 34, 139, 222, 208, 71, 214, 90, 67, 36, 28].slice(0, paddingLength); */ 94, 57, 84, 1, 135, 108, 253, 34, 139, 222, 208, 71, 214, 90, 67, 36, 28].slice(0, paddingLength); */
(message as any).padding = padding;
const dataWithPadding = bufferConcat(dataBuffer, padding); const dataWithPadding = bufferConcat(dataBuffer, padding);
// this.log('Adding padding', dataBuffer, padding, dataWithPadding) // this.log('Adding padding', dataBuffer, padding, dataWithPadding)
// this.log('auth_key_id', bytesToHex(self.authKeyID)) // this.log('auth_key_id', bytesToHex(self.authKeyID))
@ -969,6 +983,7 @@ export default class MTPNetworker {
this.log('Send encrypted: body length:', (message.body as ArrayBuffer).byteLength, paddingLength, dataWithPadding); this.log('Send encrypted: body length:', (message.body as ArrayBuffer).byteLength, paddingLength, dataWithPadding);
} */ } */
// * full next block is correct
return this.getEncryptedMessage(dataWithPadding).then((encryptedResult) => { return this.getEncryptedMessage(dataWithPadding).then((encryptedResult) => {
/* if(DEBUG) { /* if(DEBUG) {
this.log('Got encrypted out message', encryptedResult); this.log('Got encrypted out message', encryptedResult);
@ -983,10 +998,12 @@ export default class MTPNetworker {
const requestData = request.getBytes(true); const requestData = request.getBytes(true);
//if(message.fileUpload) { if(this.isFileNetworker) {
//this.log('Send encrypted: requestData length:', requestData.length, requestData.length % 16, paddingLength % 16, paddingLength, data.offset, encryptedResult.msgKey.length % 16, encryptedResult.bytes.length % 16); //this.log('Send encrypted: requestData length:', requestData.length, requestData.length % 16, paddingLength % 16, paddingLength, data.offset, encryptedResult.msgKey.length % 16, encryptedResult.bytes.length % 16);
//this.log('Send encrypted: messageId:', message.msg_id, requestData.length); //this.log('Send encrypted: messageId:', message.msg_id, requestData.length);
//} //this.log('Send encrypted:', message, new Uint8Array(bufferConcat(des.buffer, padding)), requestData, this.serverSalt.hex, this.sessionId.hex/* new Uint8Array(des.buffer) */);
this.debugRequests.push({before: new Uint8Array(bufferConcat(des.buffer, padding)), after: requestData});
}
return requestData; return requestData;
}); });
@ -1137,7 +1154,7 @@ export default class MTPNetworker {
return { return {
response, response,
messageId, messageId,
sessionId: sessionId, sessionId,
seqNo seqNo
}; };
}); });
@ -1151,7 +1168,7 @@ export default class MTPNetworker {
['dc' + this.dcId + '_server_salt']: bytesToHex(serverSalt) ['dc' + this.dcId + '_server_salt']: bytesToHex(serverSalt)
}); });
this.serverSalt = serverSalt; this.serverSalt = new Uint8Array(serverSalt);
} }
// ! таймаут очень сильно тормозит скорость работы сокета (даже нулевой) // ! таймаут очень сильно тормозит скорость работы сокета (даже нулевой)

View File

@ -1,5 +1,6 @@
import { bytesToHex } from '../../helpers/bytes'; import { bytesToHex } from '../../helpers/bytes';
import { bigint, bigStringInt, isObject } from './bin_utils'; import { bigint, bigStringInt, isObject } from './bin_utils';
import { MOUNT_CLASS_TO } from './mtproto_config';
/// #if MTPROTO_WORKER /// #if MTPROTO_WORKER
// @ts-ignore // @ts-ignore
import { gzipUncompress } from '../crypto/crypto_utils'; import { gzipUncompress } from '../crypto/crypto_utils';
@ -789,5 +790,6 @@ class TLDeserialization {
} }
} }
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.TLDeserialization = TLDeserialization);
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.TLSerialization = TLSerialization);
export { TLDeserialization, TLSerialization }; export { TLDeserialization, TLSerialization };

View File

@ -4,13 +4,15 @@ export class IntermediatePacketCodec implements Codec {
public tag = 0xee; public tag = 0xee;
public obfuscateTag = new Uint8Array([this.tag, this.tag, this.tag, this.tag]); public obfuscateTag = new Uint8Array([this.tag, this.tag, this.tag, this.tag]);
//private lol = 0;
public encodePacket(data: Uint8Array) { public encodePacket(data: Uint8Array) {
if((data.length % 4) !== 0) { if((data.length % 4) !== 0) {
console.error('Encode error!', data.length, data); console.error('Encode error!', data.length, data);
} }
const len = data.length; const len = data.length;
const header = new Uint8Array(new Uint32Array([len]).buffer); const header = new Uint8Array(new Int32Array([/* ++this.lol >= 25 ? 0x80000001 : */len]).buffer);
//console.log('got nobody cause im braindead', header, len, /* data, */data.buffer.byteLength === data.length); //console.log('got nobody cause im braindead', header, len, /* data, */data.buffer.byteLength === data.length);
return header.concat(data); return header.concat(data);

View File

@ -95,8 +95,8 @@ export default class Obfuscation {
/* this.enc = new aesjs.ModeOfOperation.ctr(encKey, new aesjs.Counter(encIv as any)); /* this.enc = new aesjs.ModeOfOperation.ctr(encKey, new aesjs.Counter(encIv as any));
this.dec = new aesjs.ModeOfOperation.ctr(decKey, new aesjs.Counter(decIv as any)); */ this.dec = new aesjs.ModeOfOperation.ctr(decKey, new aesjs.Counter(decIv as any)); */
/* console.log('encKey', encKey, encIv); console.log('encKey', encKey.hex, encIv.hex);
console.log('decKey', decKey, decIv); */ console.log('decKey', decKey.hex, decIv.hex);
this.encNew = new CTR(encKey, encIv); this.encNew = new CTR(encKey, encIv);
this.decNew = new CTR(decKey, decIv); this.decNew = new CTR(decKey, decIv);

View File

@ -34,6 +34,8 @@ export default class Socket extends MTTransport {
//private lol: Uint8Array[] = []; //private lol: Uint8Array[] = [];
//private dd: () => void; //private dd: () => void;
//private debugPayloads: MTPNetworker['debugRequests'] = [];
constructor(dcId: number, url: string, logSuffix: string, public retryTimeout: number) { constructor(dcId: number, url: string, logSuffix: string, public retryTimeout: number) {
super(dcId, url); super(dcId, url);
@ -220,6 +222,8 @@ export default class Socket extends MTTransport {
const enc = this.obfuscation.encode(toEncode); const enc = this.obfuscation.encode(toEncode);
//this.log('send after obf:', enc.hex); //this.log('send after obf:', enc.hex);
//this.debugPayloads.push({before: body.slice(), after: enc});
this.debug && this.log.debug('-> body length to send:', enc.length, this.ws.bufferedAmount); this.debug && this.log.debug('-> body length to send:', enc.length, this.ws.bufferedAmount);
/* if(this.ws.bufferedAmount) { /* if(this.ws.bufferedAmount) {
this.log.error('bufferedAmount:', this.ws.bufferedAmount); this.log.error('bufferedAmount:', this.ws.bufferedAmount);

92
t.py
View File

@ -1,8 +1,16 @@
from telethon import TelegramClient, events, sync from telethon import TelegramClient, events, sync
from telethon.network import ConnectionTcpAbridged from telethon.network import ConnectionTcpAbridged
from telethon.network.mtprotostate import MTProtoState
from telethon.crypto.authkey import AuthKey
from telethon.network.connection.tcpobfuscated import ConnectionTcpObfuscated
from telethon.network.connection.tcpintermediate import IntermediatePacketCodec
import sys import struct
print(sys.path) import logging
logging.basicConfig(level=logging.DEBUG)
# import sys
# print(sys.path)
# These example values won't work. You must get your own api_id and # These example values won't work. You must get your own api_id and
# api_hash from https://my.telegram.org, under API Development. # api_hash from https://my.telegram.org, under API Development.
@ -11,4 +19,82 @@ api_hash = '452b0359b988148995f22ff0f4229750'
client = TelegramClient('session_name', api_id, api_hash, connection=ConnectionTcpAbridged) client = TelegramClient('session_name', api_id, api_hash, connection=ConnectionTcpAbridged)
#client.start() #client.start()
client.connect() #client.connect()
authKey = AuthKey(bytes.fromhex("2c45ed62da34d41bc82b6ef660760d3b01cb80028b0c363633f1c40ec80d5f4ad9d4ddee646736b6a9465fd7258b8936dedde6a86c69b4a03277ed6c543ccfd31fa8fa9f9449d686b4822b54542c91255231ecfa6aed4a9896cdcc3d5491e7f1b7529cbff75597f262813ced7eb3b5ac1e7e6794ca7aa00c5ea4d1d714e7c276fb63e5f8a1742cf4707eace8a6ec2ed4dcceceabdc7fc92bd099c811931e0da337f1b0271d8ba33e7a4a22fa1e4a913d8220edb0dd3a810ed408f03fe5d7f2d5dd43db4272d314b20a22376c03b0a05423600f4d012e7196ce9b3c45b28a75dbfa053222f5c86d1c6a706a2fd68ca270fd9a6449df1fa15a21837d34f4cf4e95"))
state = MTProtoState(authKey, loggers=client._log)
state.salt = struct.unpack('q', bytes.fromhex("d1b37eebb4997ca9"))[0]
state.id = struct.unpack('q', bytes.fromhex("502368ee46d39313"))[0]
#print(state.id)
""" with open('file_part.txt', 'r') as file:
data = file.read()
messageData = bytes.fromhex(data)
#messageData = bytes.fromhex("146f8265b5342560290000000c000000ec77be7aaa40f90300000000")
#print(data)
encrypted = state.encrypt_message_data(messageData)
file.close()
with open('file_part_encrypted.txt', 'w') as file:
file.write(encrypted.hex())
file.close()
#print(encrypted) """
ConnectionTcpObfuscated.packet_codec = IntermediatePacketCodec
connection = ConnectionTcpObfuscated("", "", 2, loggers=client._log)
obfuscatedIo = connection.obfuscated_io(connection)
#print(encrypted)
#exit()
#encoded = connection.packet_codec.encode_packet(connection.packet_codec, bytes.fromhex("deadbeef"))
#print(encoded)
#exit()
name = "debugPayloads"
#name = "debugRequests"
with open(name + '_before.txt', 'r') as fileBefore:
data = fileBefore.read()
lines = data.splitlines()
with open(name + '_after.txt', 'r') as fileAfter:
data = fileAfter.read()
linesAfter = data.splitlines()
length = len(lines)
for i in range(length):
print("processing line %i, of %i", i, length)
lineBefore = lines[i]
lineAfter = linesAfter[i]
messageData = bytes.fromhex(lineBefore)
messageData = connection.packet_codec.encode_packet(connection.packet_codec, messageData)
encrypted = obfuscatedIo._encrypt.encrypt(messageData)
#encrypted = state.encrypt_message_data(messageData) # need to comment padding inside
#print(encrypted.hex())
#print(len(encrypted.hex()))
#print(len(lineAfter))
#print(encrypted.hex() == lineAfter)
#break
if encrypted.hex() != lineAfter:
print("lol")
with open('difference_before.txt', 'w') as file:
file.write(lineAfter)
file.close()
with open('difference_after.txt', 'w') as file:
file.write(encrypted.hex())
file.close()
#print(encrypted.hex())
#print(lineAfter)
exit()

83
t2.py Normal file
View File

@ -0,0 +1,83 @@
from telethon import TelegramClient, events, sync
from telethon.network import ConnectionTcpAbridged
from telethon.network.mtprotostate import MTProtoState
from telethon.crypto.authkey import AuthKey
import struct
import logging
logging.basicConfig(level=logging.DEBUG)
# import sys
# print(sys.path)
# These example values won't work. You must get your own api_id and
# api_hash from https://my.telegram.org, under API Development.
api_id = 1025907
api_hash = '452b0359b988148995f22ff0f4229750'
client = TelegramClient('session_name', api_id, api_hash, connection=ConnectionTcpAbridged)
#client.start()
#client.connect()
authKey = AuthKey(bytes.fromhex("2c45ed62da34d41bc82b6ef660760d3b01cb80028b0c363633f1c40ec80d5f4ad9d4ddee646736b6a9465fd7258b8936dedde6a86c69b4a03277ed6c543ccfd31fa8fa9f9449d686b4822b54542c91255231ecfa6aed4a9896cdcc3d5491e7f1b7529cbff75597f262813ced7eb3b5ac1e7e6794ca7aa00c5ea4d1d714e7c276fb63e5f8a1742cf4707eace8a6ec2ed4dcceceabdc7fc92bd099c811931e0da337f1b0271d8ba33e7a4a22fa1e4a913d8220edb0dd3a810ed408f03fe5d7f2d5dd43db4272d314b20a22376c03b0a05423600f4d012e7196ce9b3c45b28a75dbfa053222f5c86d1c6a706a2fd68ca270fd9a6449df1fa15a21837d34f4cf4e95"))
state = MTProtoState(authKey, loggers=client._log)
state.salt = struct.unpack('q', bytes.fromhex("d1b37eebb4997ca9"))[0]
state.id = struct.unpack('q', bytes.fromhex("502368ee46d39313"))[0]
print(state.id)
""" with open('file_part.txt', 'r') as file:
data = file.read()
messageData = bytes.fromhex(data)
#messageData = bytes.fromhex("146f8265b5342560290000000c000000ec77be7aaa40f90300000000")
#print(data)
encrypted = state.encrypt_message_data(messageData)
file.close()
with open('file_part_encrypted.txt', 'w') as file:
file.write(encrypted.hex())
file.close()
#print(encrypted) """
with open('debugRequests_before.txt', 'r') as fileBefore:
data = fileBefore.read()
lines = data.splitlines()
with open('debugRequests_after.txt', 'r') as fileAfter:
data = fileAfter.read()
linesAfter = data.splitlines()
length = len(lines)
for i in range(length):
print("processing line %i, of %i", i, length)
lineBefore = lines[i]
messageData = bytes.fromhex(lineBefore)
encrypted = state.encrypt_message_data(messageData) # need to comment padding inside
lineAfter = linesAfter[i]
#print(len(encrypted.hex()))
#print(len(lineAfter))
#print(encrypted.hex() == lineAfter)
#break
if encrypted.hex() != lineAfter:
print("lol")
with open('difference_before.txt', 'w') as file:
file.write(lineAfter)
file.close()
with open('difference_after.txt', 'w') as file:
file.write(encrypted.hex())
file.close()
#print(encrypted.hex())
#print(lineAfter)
exit()

View File

@ -8,8 +8,8 @@ const fs = require('fs');
const allowedIPs = ['194.58.97.147', '195.66.140.39', '127.0.0.1', '176.100.8.254']; const allowedIPs = ['194.58.97.147', '195.66.140.39', '127.0.0.1', '176.100.8.254'];
const devMode = process.env.NODE_ENV !== 'production'; const devMode = process.env.NODE_ENV !== 'production';
const useLocal = false; const useLocal = true;
const useLocalNotLocal = false; const useLocalNotLocal = true;
if(devMode) { if(devMode) {
console.log('DEVMODE IS ON!'); console.log('DEVMODE IS ON!');
@ -103,7 +103,7 @@ module.exports = {
allowedHosts: useLocal ? undefined : [ allowedHosts: useLocal ? undefined : [
'tweb.enko.club' 'tweb.enko.club'
], ],
host: useLocalNotLocal ? '192.168.93.183' : (useLocal ? undefined : '0.0.0.0'), host: useLocalNotLocal ? '192.168.93.209' : (useLocal ? undefined : '0.0.0.0'),
public: useLocal ? undefined : 'tweb.enko.club', public: useLocal ? undefined : 'tweb.enko.club',
//host: '192.168.0.105', // '0.0.0.0' //host: '192.168.0.105', // '0.0.0.0'
//host: 'tweb.enko.club', // '0.0.0.0' //host: 'tweb.enko.club', // '0.0.0.0'