251 lines
6.8 KiB
TypeScript
251 lines
6.8 KiB
TypeScript
import {
|
|
TelegramClient, sessions, Api as GramJs, connection,
|
|
} from '../../../lib/gramjs';
|
|
import { Logger as GramJsLogger } from '../../../lib/gramjs/extensions/index';
|
|
import { TwoFaParams } from '../../../lib/gramjs/client/2fa';
|
|
|
|
import { ApiMediaFormat, ApiOnProgress, OnApiUpdate } from '../../types';
|
|
|
|
import {
|
|
DEBUG, DEBUG_GRAMJS, UPLOAD_WORKERS, IS_TEST,
|
|
} from '../../../config';
|
|
import {
|
|
onRequestPhoneNumber, onRequestCode, onRequestPassword, onRequestRegistration,
|
|
onAuthError, onAuthReady, onCurrentUserUpdate, onRequestQrCode,
|
|
} from './auth';
|
|
import { updater } from '../updater';
|
|
import { setMessageBuilderCurrentUserId } from '../apiBuilders/messages';
|
|
import downloadMediaWithClient from './media';
|
|
import { buildApiUserFromFull } from '../apiBuilders/users';
|
|
import localDb from '../localDb';
|
|
|
|
GramJsLogger.setLevel(DEBUG_GRAMJS ? 'debug' : 'warn');
|
|
|
|
const gramJsUpdateEventBuilder = { build: (update: object) => update };
|
|
|
|
let onUpdate: OnApiUpdate;
|
|
let client: TelegramClient;
|
|
let isConnected = false;
|
|
|
|
export async function init(sessionId: string, _onUpdate: OnApiUpdate) {
|
|
onUpdate = _onUpdate;
|
|
|
|
if (DEBUG) {
|
|
// eslint-disable-next-line no-console
|
|
console.log('>>> START INIT API');
|
|
}
|
|
|
|
const session = IS_TEST
|
|
? new sessions.LocalStorageSession(sessionId)
|
|
: new sessions.CacheApiSession(sessionId);
|
|
|
|
client = new TelegramClient(
|
|
session,
|
|
process.env.TELEGRAM_T_API_ID,
|
|
process.env.TELEGRAM_T_API_HASH,
|
|
{
|
|
useWSS: true,
|
|
additionalDcsDisabled: IS_TEST,
|
|
} as any,
|
|
);
|
|
|
|
client.addEventHandler(handleGramJsUpdate, gramJsUpdateEventBuilder);
|
|
client.addEventHandler(updater, gramJsUpdateEventBuilder);
|
|
|
|
try {
|
|
if (DEBUG) {
|
|
// eslint-disable-next-line no-console
|
|
console.log('[GramJs/client] CONNECTING');
|
|
}
|
|
|
|
await client.start({
|
|
phoneNumber: onRequestPhoneNumber,
|
|
phoneCode: onRequestCode,
|
|
password: onRequestPassword,
|
|
firstAndLastNames: onRequestRegistration,
|
|
qrCode: onRequestQrCode,
|
|
onError: onAuthError,
|
|
});
|
|
|
|
const newSessionId = await session.save();
|
|
|
|
if (DEBUG) {
|
|
// eslint-disable-next-line no-console
|
|
console.log('>>> FINISH INIT API');
|
|
// eslint-disable-next-line no-console
|
|
console.log('[GramJs/client] CONNECTED as ', newSessionId);
|
|
}
|
|
|
|
onAuthReady(newSessionId);
|
|
onUpdate({ '@type': 'updateApiReady' });
|
|
|
|
void fetchCurrentUser();
|
|
} catch (err) {
|
|
if (DEBUG) {
|
|
// eslint-disable-next-line no-console
|
|
console.log('[GramJs/client] CONNECTING ERROR', err);
|
|
}
|
|
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
export async function destroy() {
|
|
await client.destroy();
|
|
}
|
|
|
|
function handleGramJsUpdate(update: any) {
|
|
if (update instanceof connection.UpdateConnectionState) {
|
|
isConnected = update.state === connection.UpdateConnectionState.connected;
|
|
} else if (update instanceof GramJs.UpdatesTooLong) {
|
|
void handleTerminatedSession();
|
|
}
|
|
}
|
|
|
|
export async function invokeRequest<T extends GramJs.AnyRequest>(
|
|
request: T,
|
|
shouldHandleUpdates = false,
|
|
shouldThrow = false,
|
|
): Promise<T['__response'] | undefined> {
|
|
if (!isConnected) {
|
|
if (DEBUG) {
|
|
// eslint-disable-next-line no-console
|
|
console.warn(`[GramJs/client] INVOKE ${request.className} ERROR: Client is not connected`);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
try {
|
|
if (DEBUG) {
|
|
// eslint-disable-next-line no-console
|
|
console.log(`[GramJs/client] INVOKE ${request.className}`);
|
|
}
|
|
|
|
const result = await client.invoke(request);
|
|
|
|
if (DEBUG) {
|
|
// eslint-disable-next-line no-console
|
|
console.log(`[GramJs/client] INVOKE RESPONSE ${request.className}`, result);
|
|
}
|
|
|
|
if (shouldHandleUpdates) {
|
|
type ResultWithUpdates = typeof result & { updates?: GramJs.Updates | GramJs.UpdatesCombined };
|
|
|
|
let updatesContainer;
|
|
if (result instanceof GramJs.Updates || result instanceof GramJs.UpdatesCombined) {
|
|
updatesContainer = result;
|
|
} else if ('updates' in result && (
|
|
(result as ResultWithUpdates).updates instanceof GramJs.Updates
|
|
|| (result as ResultWithUpdates).updates instanceof GramJs.UpdatesCombined
|
|
)) {
|
|
updatesContainer = (result as ResultWithUpdates).updates;
|
|
}
|
|
|
|
if (updatesContainer) {
|
|
injectUpdateEntities(updatesContainer);
|
|
|
|
updatesContainer.updates.forEach((update) => {
|
|
updater(update, request);
|
|
});
|
|
} else if (result instanceof GramJs.UpdatesTooLong) {
|
|
// TODO Implement
|
|
} else {
|
|
updater(result as GramJs.TypeUpdates, request);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
} catch (err) {
|
|
if (DEBUG) {
|
|
// eslint-disable-next-line no-console
|
|
console.log(`[GramJs/client] INVOKE ERROR ${request.className}`);
|
|
// eslint-disable-next-line no-console
|
|
console.error(err);
|
|
}
|
|
|
|
dispatchErrorUpdate(err, request);
|
|
|
|
if (shouldThrow) {
|
|
throw err;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
export function downloadMedia(
|
|
args: { url: string; mediaFormat: ApiMediaFormat; start?: number; end?: number },
|
|
onProgress?: ApiOnProgress,
|
|
) {
|
|
return downloadMediaWithClient(args, client, isConnected, onProgress);
|
|
}
|
|
|
|
export function uploadFile(file: File, onProgress?: ApiOnProgress) {
|
|
return client.uploadFile({ file, onProgress, workers: UPLOAD_WORKERS });
|
|
}
|
|
|
|
export function updateTwoFaSettings(params: TwoFaParams) {
|
|
return client.updateTwoFaSettings(params);
|
|
}
|
|
|
|
export async function fetchCurrentUser() {
|
|
const userFull = await invokeRequest(new GramJs.users.GetFullUser({
|
|
id: new GramJs.InputUserSelf(),
|
|
}));
|
|
|
|
if (!userFull || !(userFull.user instanceof GramJs.User)) {
|
|
return;
|
|
}
|
|
|
|
localDb.users[userFull.user.id] = userFull.user;
|
|
const currentUser = buildApiUserFromFull(userFull);
|
|
|
|
setMessageBuilderCurrentUserId(currentUser.id);
|
|
onCurrentUserUpdate(currentUser);
|
|
}
|
|
|
|
export function dispatchErrorUpdate<T extends GramJs.AnyRequest>(err: Error, request: T) {
|
|
const isSlowMode = err.message.startsWith('A wait of') && (
|
|
request instanceof GramJs.messages.SendMessage
|
|
|| request instanceof GramJs.messages.SendMedia
|
|
|| request instanceof GramJs.messages.SendMultiMedia
|
|
);
|
|
|
|
const { message } = err;
|
|
|
|
onUpdate({
|
|
'@type': 'error',
|
|
error: {
|
|
message,
|
|
isSlowMode,
|
|
},
|
|
});
|
|
}
|
|
|
|
function injectUpdateEntities(result: GramJs.Updates | GramJs.UpdatesCombined) {
|
|
const entities = [...result.users, ...result.chats];
|
|
|
|
result.updates.forEach((update) => {
|
|
if (entities) {
|
|
// eslint-disable-next-line no-underscore-dangle
|
|
(update as any)._entities = entities;
|
|
}
|
|
});
|
|
}
|
|
|
|
async function handleTerminatedSession() {
|
|
try {
|
|
await invokeRequest(new GramJs.users.GetFullUser({
|
|
id: new GramJs.InputUserSelf(),
|
|
}), undefined, true);
|
|
} catch (err) {
|
|
if (err.message === 'AUTH_KEY_UNREGISTERED') {
|
|
onUpdate({
|
|
'@type': 'updateConnectionState',
|
|
connectionState: 'connectionStateBroken',
|
|
});
|
|
}
|
|
}
|
|
}
|