feat: app config manager
This commit is contained in:
parent
e6c7af6ed2
commit
27e14472ff
18 changed files with 1317 additions and 110 deletions
25
packages/core/scripts/generate-app-config.cjs
Normal file
25
packages/core/scripts/generate-app-config.cjs
Normal file
|
@ -0,0 +1,25 @@
|
|||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const spec = require('@mtcute/tl/app-config.json')
|
||||
|
||||
const OUT_FILE = path.join(__dirname, '../src/highlevel/types/misc/app-config.ts')
|
||||
|
||||
const out = fs.createWriteStream(OUT_FILE)
|
||||
out.write(`// This file is generated automatically, do not modify!
|
||||
/* eslint-disable */
|
||||
|
||||
export interface AppConfigSchema {
|
||||
`)
|
||||
|
||||
const indent = (str) => str.split('\n').map((x) => ' ' + x).join('\n')
|
||||
|
||||
for (const [key, { type, description }] of Object.entries(spec)) {
|
||||
out.write(indent(description) + '\n')
|
||||
out.write(indent(`${key}?: ${type}`) + '\n')
|
||||
}
|
||||
|
||||
out.write(' [key: string]: unknown\n')
|
||||
out.write('}\n')
|
||||
|
||||
out.close()
|
|
@ -16,6 +16,7 @@ import {
|
|||
} from '../utils/index.js'
|
||||
import { LogManager } from '../utils/logger.js'
|
||||
import { ITelegramClient } from './client.types.js'
|
||||
import { AppConfigManager } from './managers/app-config-manager.js'
|
||||
import { ITelegramStorageProvider } from './storage/provider.js'
|
||||
import { TelegramStorageManager, TelegramStorageManagerExtraOptions } from './storage/storage.js'
|
||||
import { UpdatesManager } from './updates/manager.js'
|
||||
|
@ -52,6 +53,7 @@ export class BaseTelegramClient implements ITelegramClient {
|
|||
provider: this.params.storage,
|
||||
...this.params.storageOptions,
|
||||
})
|
||||
readonly appConfig = new AppConfigManager(this)
|
||||
|
||||
private _prepare = asyncResettable(async () => {
|
||||
await this.mt.prepare()
|
||||
|
|
|
@ -4,6 +4,7 @@ import type { ConnectionKind, RpcCallOptions } from '../network/index.js'
|
|||
import type { MustEqual, PublicPart } from '../types/utils.js'
|
||||
import type { Logger } from '../utils/logger.js'
|
||||
import type { StringSessionData } from '../utils/string-session.js'
|
||||
import type { AppConfigManager } from './managers/app-config-manager.js'
|
||||
import type { TelegramStorageManager } from './storage/storage.js'
|
||||
import type { RawUpdateHandler } from './updates/types.js'
|
||||
|
||||
|
@ -14,6 +15,7 @@ import type { RawUpdateHandler } from './updates/types.js'
|
|||
export interface ITelegramClient {
|
||||
readonly log: Logger
|
||||
readonly storage: PublicPart<TelegramStorageManager>
|
||||
readonly appConfig: PublicPart<AppConfigManager>
|
||||
|
||||
prepare(): Promise<void>
|
||||
connect(): Promise<void>
|
||||
|
|
57
packages/core/src/highlevel/managers/app-config-manager.ts
Normal file
57
packages/core/src/highlevel/managers/app-config-manager.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
|
||||
import { MtTypeAssertionError } from '../../types/errors.js'
|
||||
import { Reloadable } from '../../utils/reloadable.js'
|
||||
import { tlJsonToJson } from '../../utils/tl-json.js'
|
||||
import { BaseTelegramClient } from '../base.js'
|
||||
import { AppConfigSchema } from '../types/misc/app-config.js'
|
||||
|
||||
export class AppConfigManager {
|
||||
constructor(private client: BaseTelegramClient) {}
|
||||
|
||||
private _reloadable = new Reloadable<tl.help.RawAppConfig>({
|
||||
reload: this._reload.bind(this),
|
||||
getExpiresAt: () => 3_600_000,
|
||||
disableAutoReload: true,
|
||||
})
|
||||
|
||||
private async _reload(old?: tl.help.RawAppConfig) {
|
||||
const res = await this.client.call({
|
||||
_: 'help.getAppConfig',
|
||||
hash: old?.hash ?? 0,
|
||||
})
|
||||
|
||||
if (res._ === 'help.appConfigNotModified') return old!
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
private _object?: AppConfigSchema
|
||||
async get(): Promise<AppConfigSchema> {
|
||||
if (!this._reloadable.isStale && this._object) return this._object
|
||||
|
||||
const obj = tlJsonToJson((await this._reloadable.get()).config)
|
||||
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
throw new MtTypeAssertionError('appConfig', 'object', typeof obj)
|
||||
}
|
||||
|
||||
this._object = obj as AppConfigSchema
|
||||
|
||||
return this._object
|
||||
}
|
||||
|
||||
async getField<K extends keyof AppConfigSchema>(field: K): Promise<AppConfigSchema[K]>
|
||||
async getField<K extends keyof AppConfigSchema>(
|
||||
field: K,
|
||||
fallback: NonNullable<AppConfigSchema[K]>,
|
||||
): Promise<NonNullable<AppConfigSchema[K]>>
|
||||
async getField<K extends keyof AppConfigSchema>(
|
||||
field: K,
|
||||
fallback?: NonNullable<AppConfigSchema[K]>,
|
||||
): Promise<AppConfigSchema[K]> {
|
||||
const obj = await this.get()
|
||||
|
||||
return obj[field] ?? fallback
|
||||
}
|
||||
}
|
896
packages/core/src/highlevel/types/misc/app-config.ts
Normal file
896
packages/core/src/highlevel/types/misc/app-config.ts
Normal file
|
@ -0,0 +1,896 @@
|
|||
// This file is generated automatically, do not modify!
|
||||
/* eslint-disable */
|
||||
|
||||
export interface AppConfigSchema {
|
||||
/**
|
||||
* <a href="https://corefork.telegram.org/api/animated-emojis">Animated
|
||||
* emojis</a> and
|
||||
* <a href="https://corefork.telegram.org/api/dice">animated
|
||||
* dice</a> should be scaled by this factor before being shown
|
||||
* to the user (float)
|
||||
*/
|
||||
emojies_animated_zoom?: number
|
||||
/**
|
||||
* Whether app clients should start a keepalive service to keep
|
||||
* the app running and fetch updates even when the app is
|
||||
* closed (boolean)
|
||||
*/
|
||||
keep_alive_service?: boolean
|
||||
/**
|
||||
* Whether app clients should start a background TCP connection
|
||||
* for MTProto update fetching (boolean)
|
||||
*/
|
||||
background_connection?: boolean
|
||||
/**
|
||||
* A list of supported
|
||||
* <a href="https://corefork.telegram.org/api/dice">animated
|
||||
* dice</a> stickers (array of strings).
|
||||
*/
|
||||
emojies_send_dice?: string[]
|
||||
/**
|
||||
* For
|
||||
* <a href="https://corefork.telegram.org/api/dice">animated
|
||||
* dice</a> emojis other than the basic <img class="emoji"
|
||||
* src="//telegram.org/img/emoji/40/F09F8EB2.png" width="20"
|
||||
* height="20" alt="🎲">, indicates the winning dice value and
|
||||
* the final frame of the animated sticker, at which to show
|
||||
* the fireworks <img class="emoji"
|
||||
* src="//telegram.org/img/emoji/40/F09F8E86.png" width="20"
|
||||
* height="20" alt="🎆"> (object with emoji keys and object
|
||||
* values, containing <code>value</code> and
|
||||
* <code>frame_start</code> float values)
|
||||
*/
|
||||
emojies_send_dice_success?: Record<
|
||||
string,
|
||||
{
|
||||
value: number
|
||||
frame_start: number
|
||||
}
|
||||
>
|
||||
/**
|
||||
* A map of soundbites to be played when the user clicks on the
|
||||
* specified
|
||||
* <a href="https://corefork.telegram.org/api/animated-emojis">animated
|
||||
* emoji</a>; the
|
||||
* <a href="https://corefork.telegram.org/api/file_reference">file
|
||||
* reference field</a> should be base64-decoded before
|
||||
* <a href="https://corefork.telegram.org/api/files">downloading
|
||||
* the file</a> (map of
|
||||
* <a href="https://corefork.telegram.org/api/files">file
|
||||
* IDs</a> ({@link RawInputDocument}.id), with emoji string
|
||||
* keys)
|
||||
*/
|
||||
emojies_sounds?: Record<
|
||||
string,
|
||||
{
|
||||
id: string
|
||||
access_hash: string
|
||||
file_reference_base64: string
|
||||
}
|
||||
>
|
||||
/**
|
||||
* Specifies the name of the service providing GIF search
|
||||
* through
|
||||
* <a href="#mtproto-configuration">gif_search_username</a>
|
||||
* (string)
|
||||
*/
|
||||
gif_search_branding?: string
|
||||
/**
|
||||
* Specifies a list of emojis that should be suggested as
|
||||
* search term in a bar above the GIF search box (array of
|
||||
* string emojis)
|
||||
*/
|
||||
gif_search_emojies?: string[]
|
||||
/**
|
||||
* Specifies that the app should not display
|
||||
* <a href="https://corefork.telegram.org/api/stickers#sticker-suggestions">local
|
||||
* sticker suggestions »</a> for emojis at all and just use the
|
||||
* result of {@link messages.RawGetStickersRequest} (bool)
|
||||
*/
|
||||
stickers_emoji_suggest_only_api?: boolean
|
||||
/**
|
||||
* Specifies the validity period of the local cache of
|
||||
* {@link messages.RawGetStickersRequest}, also relevant when
|
||||
* generating the
|
||||
* <a href="https://corefork.telegram.org/api/offsets#hash-generation">pagination
|
||||
* hash</a> when invoking the method. (integer)
|
||||
*/
|
||||
stickers_emoji_cache_time?: number
|
||||
/**
|
||||
* Whether the Settings->Devices menu should show an option
|
||||
* to scan a
|
||||
* <a href="https://corefork.telegram.org/api/qr-login">QR
|
||||
* login code</a> (boolean)
|
||||
*/
|
||||
qr_login_camera?: boolean
|
||||
/**
|
||||
* Whether the login screen should show a
|
||||
* <a href="https://corefork.telegram.org/api/qr-login">QR code
|
||||
* login option</a>, possibly as default login method (string,
|
||||
* "disabled", "primary" or "secondary")
|
||||
*/
|
||||
qr_login_code?: 'disabled' | 'primary' | 'secondary'
|
||||
/**
|
||||
* Whether clients should show an option for managing
|
||||
* <a href="https://corefork.telegram.org/api/folders">dialog
|
||||
* filters AKA folders</a> (boolean)
|
||||
*/
|
||||
dialog_filters_enabled?: boolean
|
||||
/**
|
||||
* Whether clients should actively show a tooltip, inviting the
|
||||
* user to configure
|
||||
* <a href="https://corefork.telegram.org/api/folders">dialog
|
||||
* filters AKA folders</a>; typically this happens when the
|
||||
* chat list is long enough to start getting cluttered.
|
||||
* (boolean)
|
||||
*/
|
||||
dialog_filters_tooltip?: boolean
|
||||
/**
|
||||
* Whether clients <em>can</em> invoke
|
||||
* {@link account.RawSetGlobalPrivacySettingsRequest} with
|
||||
* {@link RawGlobalPrivacySettings}, to automatically archive
|
||||
* and mute new incoming chats from non-contacts. (boolean)
|
||||
*/
|
||||
autoarchive_setting_available?: boolean
|
||||
/**
|
||||
* Contains a list of suggestions that should be actively shown
|
||||
* as a tooltip to the user. (Array of strings, possible values
|
||||
* shown <a href="#suggestions">in the suggestions section
|
||||
* »</a>.
|
||||
*/
|
||||
pending_suggestions?: string[]
|
||||
/**
|
||||
* Maximum number of
|
||||
* <a href="https://corefork.telegram.org/api/forum#forum-topics">topics</a>
|
||||
* that can be pinned in a single
|
||||
* <a href="https://corefork.telegram.org/api/forum">forum</a>.
|
||||
* (integer)
|
||||
*/
|
||||
topics_pinned_limit?: number
|
||||
/**
|
||||
* The ID of the official
|
||||
* <a href="https://corefork.telegram.org/api/antispam">native
|
||||
* antispam bot</a>, that will automatically delete spam
|
||||
* messages if enabled as specified in the
|
||||
* <a href="https://corefork.telegram.org/api/antispam">native
|
||||
* antispam documentation »</a>.
|
||||
*
|
||||
*
|
||||
* When fetching the admin list of a supergroup using
|
||||
* {@link channels.RawGetParticipantsRequest}, if native
|
||||
* antispam functionality in the specified supergroup, the bot
|
||||
* should be manually added to the admin list displayed to the
|
||||
* user. (numeric string that represents a Telegram user/bot
|
||||
* ID, should be casted to an int64)
|
||||
*/
|
||||
telegram_antispam_user_id?: string
|
||||
/**
|
||||
* Minimum number of group members required to enable
|
||||
* <a href="https://corefork.telegram.org/api/antispam">native
|
||||
* antispam functionality</a>. (integer)
|
||||
*/
|
||||
telegram_antispam_group_size_min?: number
|
||||
/**
|
||||
* List of phone number prefixes for anonymous
|
||||
* <a href="https://fragment.com/">Fragment</a> phone numbers.
|
||||
* (array of strings).
|
||||
*/
|
||||
fragment_prefixes?: string[]
|
||||
/**
|
||||
* Minimum number of participants required to hide the
|
||||
* participants list of a supergroup using
|
||||
* {@link channels.RawToggleParticipantsHiddenRequest}.
|
||||
* (integer)
|
||||
*/
|
||||
hidden_members_group_size_min?: number
|
||||
/**
|
||||
* A list of domains that support automatic login with manual
|
||||
* user confirmation,
|
||||
* <a href="https://corefork.telegram.org/api/url-authorization#link-url-authorization">click
|
||||
* here for more info on URL authorization »</a>. (array of
|
||||
* strings)
|
||||
*/
|
||||
url_auth_domains?: string[]
|
||||
/**
|
||||
* A list of Telegram domains that support automatic login with
|
||||
* no user confirmation,
|
||||
* <a href="https://corefork.telegram.org/api/url-authorization#link-url-authorization">click
|
||||
* here for more info on URL authorization »</a>. (array of
|
||||
* strings)
|
||||
*/
|
||||
autologin_domains?: string[]
|
||||
/**
|
||||
* A list of Telegram domains that can always be opened without
|
||||
* additional user confirmation, when clicking on in-app links
|
||||
* where the URL is not fully displayed (i.e.
|
||||
* {@link RawMessageEntityTextUrl} entities). (array of
|
||||
* strings)Note that when opening
|
||||
* <a href="https://corefork.telegram.org/api/links#named-mini-app-links">named
|
||||
* Mini App links</a> for the first time, confirmation should
|
||||
* still be requested from the user, even if the domain of the
|
||||
* containing deep link is whitelisted (i.e.
|
||||
* <code>t.me/<bot_username>/<short_name>?startapp=<start_parameter></code>,
|
||||
* where <code>t.me</code> is whitelisted). Confirmation
|
||||
* should <strong>always</strong> be asked, even if we already
|
||||
* opened the
|
||||
* <a href="https://corefork.telegram.org/api/links#named-mini-app-links">named
|
||||
* Mini App</a> before, if the link is not visible (i.e.
|
||||
* {@link RawMessageEntityTextUrl} text links, inline buttons
|
||||
* etc.).
|
||||
*/
|
||||
whitelisted_domains?: string[]
|
||||
/**
|
||||
* Contains a set of recommended codec parameters for round
|
||||
* videos. (object, as described in the example)
|
||||
*/
|
||||
round_video_encoding?: {
|
||||
diameter: number
|
||||
video_bitrate: number
|
||||
audio_bitrate: number
|
||||
max_size: number
|
||||
}
|
||||
/**
|
||||
* Per-user read receipts, fetchable using
|
||||
* {@link messages.RawGetMessageReadParticipantsRequest}, will
|
||||
* be available in groups with an amount of participants less
|
||||
* or equal to <code>chat_read_mark_size_threshold</code>.
|
||||
* (integer)
|
||||
*/
|
||||
chat_read_mark_size_threshold?: number
|
||||
/**
|
||||
* To protect user privacy, read receipts for chats are only
|
||||
* stored for <code>chat_read_mark_expire_period</code> seconds
|
||||
* after the message was sent. (integer)
|
||||
*/
|
||||
chat_read_mark_expire_period?: number
|
||||
/**
|
||||
* To protect user privacy, read receipts for private chats are
|
||||
* only stored for <code>pm_read_date_expire_period</code>
|
||||
* seconds after the message was sent. (integer)
|
||||
*/
|
||||
pm_read_date_expire_period?: number
|
||||
/**
|
||||
* Maximum number of participants in a group call (livestreams
|
||||
* allow ∞ participants) (integer)
|
||||
*/
|
||||
groupcall_video_participants_max?: number
|
||||
/**
|
||||
* Maximum number of unique reactions for any given message:
|
||||
* for example, if there are 2000 <img class="emoji"
|
||||
* src="//telegram.org/img/emoji/40/F09F918D.png" width="20"
|
||||
* height="20" alt="👍"> and 1000 custom emoji <img
|
||||
* class="emoji" src="//telegram.org/img/emoji/40/F09F9881.png"
|
||||
* width="20" height="20" alt="😁"> reactions and
|
||||
* reactions_uniq_max = 2, you can't add a <img class="emoji"
|
||||
* src="//telegram.org/img/emoji/40/F09F918E.png" width="20"
|
||||
* height="20" alt="👎"> reaction, because that would raise the
|
||||
* number of unique reactions to 3 > 2. (integer)
|
||||
*/
|
||||
reactions_uniq_max?: number
|
||||
/**
|
||||
* Maximum number of reactions that can be marked as allowed in
|
||||
* a chat using {@link RawChatReactionsSome}. (integer)
|
||||
*/
|
||||
reactions_in_chat_max?: number
|
||||
/**
|
||||
* Maximum number of reactions that can be added to a single
|
||||
* message by a non-Premium user. (integer)
|
||||
*/
|
||||
reactions_user_max_default?: number
|
||||
/**
|
||||
* Maximum number of reactions that can be added to a single
|
||||
* message by a Premium user. (integer)
|
||||
*/
|
||||
reactions_user_max_premium?: number
|
||||
/**
|
||||
* Default emoji status stickerset ID. (integer)
|
||||
*
|
||||
*
|
||||
* Note that the stickerset can be fetched using
|
||||
* {@link RawInputStickerSetEmojiDefaultStatuses}.
|
||||
*/
|
||||
default_emoji_statuses_stickerset_id?: number
|
||||
/**
|
||||
* The maximum duration in seconds of
|
||||
* <a href="https://corefork.telegram.org/api/ringtones">uploadable
|
||||
* notification sounds »</a> (integer)
|
||||
*/
|
||||
ringtone_duration_max?: number
|
||||
/**
|
||||
* The maximum post-conversion size in bytes of
|
||||
* <a href="https://corefork.telegram.org/api/ringtones">uploadable
|
||||
* notification sounds »</a>
|
||||
*/
|
||||
ringtone_size_max?: number
|
||||
/**
|
||||
* The maximum number of
|
||||
* <a href="https://corefork.telegram.org/api/ringtones">saveable
|
||||
* notification sounds »</a>
|
||||
*/
|
||||
ringtone_saved_count_max?: number
|
||||
/**
|
||||
* The maximum number of
|
||||
* <a href="https://corefork.telegram.org/api/custom-emoji">custom
|
||||
* emojis</a> that may be present in a message. (integer)
|
||||
*/
|
||||
message_animated_emoji_max?: number
|
||||
/**
|
||||
* Defines how many
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium
|
||||
* stickers</a> to show in the sticker suggestion popup when
|
||||
* entering an emoji into the text field, see the
|
||||
* <a href="https://corefork.telegram.org/api/stickers#sticker-suggestions">sticker
|
||||
* docs for more info</a> (integer, defaults to 0)
|
||||
*/
|
||||
stickers_premium_by_emoji_num?: number
|
||||
/**
|
||||
* For
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium
|
||||
* users</a>, used to define the suggested sticker list, see
|
||||
* the
|
||||
* <a href="https://corefork.telegram.org/api/stickers#sticker-suggestions">sticker
|
||||
* docs for more info</a> (integer, defaults to 2)
|
||||
*/
|
||||
stickers_normal_by_emoji_per_premium_num?: number
|
||||
/**
|
||||
* The user can't purchase
|
||||
* <a href="https://corefork.telegram.org/api/premium">Telegram
|
||||
* Premium</a>. The app must also hide all Premium features,
|
||||
* including stars for other users, et cetera. (boolean)
|
||||
*/
|
||||
premium_purchase_blocked?: boolean
|
||||
/**
|
||||
* The maximum number of
|
||||
* <a href="https://corefork.telegram.org/api/channel">channels
|
||||
* and supergroups</a> a
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may join (integer)
|
||||
*/
|
||||
channels_limit_default?: number
|
||||
/**
|
||||
* The maximum number of
|
||||
* <a href="https://corefork.telegram.org/api/channel">channels
|
||||
* and supergroups</a> a
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may join (integer)
|
||||
*/
|
||||
channels_limit_premium?: number
|
||||
/**
|
||||
* The maximum number of GIFs a
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may save (integer)
|
||||
*/
|
||||
saved_gifs_limit_default?: number
|
||||
/**
|
||||
* The maximum number of GIFs a
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may save (integer)
|
||||
*/
|
||||
saved_gifs_limit_premium?: number
|
||||
/**
|
||||
* The maximum number of stickers a
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may
|
||||
* <a href="https://corefork.telegram.org/api/stickers#favorite-stickersets">add
|
||||
* to Favorites »</a> (integer)
|
||||
*/
|
||||
stickers_faved_limit_default?: number
|
||||
/**
|
||||
* The maximum number of stickers a
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may
|
||||
* <a href="https://corefork.telegram.org/api/stickers#favorite-stickersets">add
|
||||
* to Favorites »</a> (integer)
|
||||
*/
|
||||
stickers_faved_limit_premium?: number
|
||||
/**
|
||||
* The maximum number of
|
||||
* <a href="https://corefork.telegram.org/api/folders">folders</a>
|
||||
* a
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may create (integer)
|
||||
*/
|
||||
dialog_filters_limit_default?: number
|
||||
/**
|
||||
* The maximum number of
|
||||
* <a href="https://corefork.telegram.org/api/folders">folders</a>
|
||||
* a
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may create (integer)
|
||||
*/
|
||||
dialog_filters_limit_premium?: number
|
||||
/**
|
||||
* The maximum number of chats a
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may add to a
|
||||
* <a href="https://corefork.telegram.org/api/folders">folder</a>
|
||||
* (integer)
|
||||
*/
|
||||
dialog_filters_chats_limit_default?: number
|
||||
/**
|
||||
* The maximum number of chats a
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may add to a
|
||||
* <a href="https://corefork.telegram.org/api/folders">folder</a>
|
||||
* (integer)
|
||||
*/
|
||||
dialog_filters_chats_limit_premium?: number
|
||||
/**
|
||||
* The maximum number of chats a
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may pin (integer)
|
||||
*/
|
||||
dialogs_pinned_limit_default?: number
|
||||
/**
|
||||
* The maximum number of chats a
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may pin (integer)
|
||||
*/
|
||||
dialogs_pinned_limit_premium?: number
|
||||
/**
|
||||
* The maximum number of chats a
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may pin in a folder (integer)
|
||||
*/
|
||||
dialogs_folder_pinned_limit_default?: number
|
||||
/**
|
||||
* The maximum number of chats a
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may pin in a folder (integer)
|
||||
*/
|
||||
dialogs_folder_pinned_limit_premium?: number
|
||||
/**
|
||||
* The maximum number of public
|
||||
* <a href="https://corefork.telegram.org/api/channel">channels
|
||||
* or supergroups</a> a
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may create (integer)
|
||||
*/
|
||||
channels_public_limit_default?: number
|
||||
/**
|
||||
* The maximum number of public
|
||||
* <a href="https://corefork.telegram.org/api/channel">channels
|
||||
* or supergroups</a> a
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* user may create (integer)
|
||||
*/
|
||||
channels_public_limit_premium?: number
|
||||
/**
|
||||
* The maximum UTF-8 length of media captions sendable by
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users (integer)
|
||||
*/
|
||||
caption_length_limit_default?: number
|
||||
/**
|
||||
* The maximum UTF-8 length of media captions sendable by
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users (integer)
|
||||
*/
|
||||
caption_length_limit_premium?: number
|
||||
/**
|
||||
* The maximum number of file parts uploadable by
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users (integer, the maximum file size can be extrapolated by
|
||||
* multiplying this value by <code>524288</code>, the biggest
|
||||
* possible chunk size)
|
||||
*/
|
||||
upload_max_fileparts_default?: number
|
||||
/**
|
||||
* The maximum number of file parts uploadable by
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users (integer, the maximum file size can be extrapolated by
|
||||
* multiplying this value by <code>524288</code>, the biggest
|
||||
* possible chunk size)
|
||||
*/
|
||||
upload_max_fileparts_premium?: number
|
||||
/**
|
||||
* The maximum UTF-8 length of bios of
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users (integer)
|
||||
*/
|
||||
about_length_limit_default?: number
|
||||
/**
|
||||
* The maximum UTF-8 length of bios of
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users (integer)
|
||||
*/
|
||||
about_length_limit_premium?: number
|
||||
/**
|
||||
* Array of string identifiers, indicating the order of
|
||||
* <a href="https://corefork.telegram.org/api/premium">Telegram
|
||||
* Premium</a> features in the Telegram Premium promotion
|
||||
* popup,
|
||||
* <a href="https://corefork.telegram.org/api/premium#telegram-premium-features">see
|
||||
* here for the possible values »</a>
|
||||
*/
|
||||
premium_promo_order?: string[]
|
||||
/**
|
||||
* Contains the username of the official
|
||||
* <a href="https://corefork.telegram.org/api/premium">Telegram
|
||||
* Premium</a> bot that may be used to buy a
|
||||
* <a href="https://corefork.telegram.org/api/premium">Telegram
|
||||
* Premium</a> subscription, see
|
||||
* <a href="https://corefork.telegram.org/api/premium">here for
|
||||
* detailed instructions »</a> (string)
|
||||
*/
|
||||
premium_bot_username?: string
|
||||
/**
|
||||
* Contains an
|
||||
* <a href="https://corefork.telegram.org/api/payments">invoice
|
||||
* slug</a> that may be used to buy a
|
||||
* <a href="https://corefork.telegram.org/api/premium">Telegram
|
||||
* Premium</a> subscription, see
|
||||
* <a href="https://corefork.telegram.org/api/premium">here for
|
||||
* detailed instructions »</a> (string)
|
||||
*/
|
||||
premium_invoice_slug?: string
|
||||
/**
|
||||
* Whether a gift icon should be shown in the attachment menu
|
||||
* in private chats with users, offering the current user to
|
||||
* gift a
|
||||
* <a href="https://corefork.telegram.org/api/premium">Telegram
|
||||
* Premium</a> subscription to the other user in the chat.
|
||||
* (boolean)
|
||||
*/
|
||||
premium_gift_attach_menu_icon?: boolean
|
||||
/**
|
||||
* Whether a gift icon should be shown in the text bar in
|
||||
* private chats with users (ie like the <code>/</code> icon in
|
||||
* chats with bots), offering the current user to gift a
|
||||
* <a href="https://corefork.telegram.org/api/premium">Telegram
|
||||
* Premium</a> subscription to the other user in the chat. Can
|
||||
* only be true if <code>premium_gift_attach_menu_icon</code>
|
||||
* is also true. (boolean)
|
||||
*/
|
||||
premium_gift_text_field_icon?: boolean
|
||||
/**
|
||||
* Users that import a folder using a
|
||||
* <a href="https://corefork.telegram.org/api/links#chat-folder-links">chat
|
||||
* folder deep link »</a> should retrieve additions made to the
|
||||
* folder by invoking
|
||||
* {@link chatlists.RawGetChatlistUpdatesRequest} at most every
|
||||
* <code>chatlist_update_period</code> seconds. (integer)
|
||||
*/
|
||||
chatlist_update_period?: number
|
||||
/**
|
||||
* Maximum number of per-folder
|
||||
* <a href="https://corefork.telegram.org/api/links#chat-folder-links">chat
|
||||
* folder deep links »</a> that can be created by
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
chatlist_invites_limit_default?: number
|
||||
/**
|
||||
* Maximum number of per-folder
|
||||
* <a href="https://corefork.telegram.org/api/links#chat-folder-links">chat
|
||||
* folder deep links »</a> that can be created by
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
chatlist_invites_limit_premium?: number
|
||||
/**
|
||||
* Maximum number of
|
||||
* <a href="https://corefork.telegram.org/api/links#chat-folder-links">shareable
|
||||
* folders</a>
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users may have. (integer)
|
||||
*/
|
||||
chatlists_joined_limit_default?: number
|
||||
/**
|
||||
* Maximum number of
|
||||
* <a href="https://corefork.telegram.org/api/links#chat-folder-links">shareable
|
||||
* folders</a>
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users may have. (integer)
|
||||
*/
|
||||
chatlists_joined_limit_premium?: number
|
||||
/**
|
||||
* A soft limit, specifying the maximum number of files that
|
||||
* should be downloaded in parallel from the same DC, for files
|
||||
* smaller than 20MB. (integer)
|
||||
*/
|
||||
small_queue_max_active_operations_count?: number
|
||||
/**
|
||||
* A soft limit, specifying the maximum number of files that
|
||||
* should be downloaded in parallel from the same DC, for files
|
||||
* bigger than 20MB. (integer)
|
||||
*/
|
||||
large_queue_max_active_operations_count?: number
|
||||
/**
|
||||
* An
|
||||
* <a href="https://corefork.telegram.org/api/auth#confirming-login">unconfirmed
|
||||
* session »</a> will be autoconfirmed this many seconds after
|
||||
* login. (integer)
|
||||
*/
|
||||
authorization_autoconfirm_period?: number
|
||||
/**
|
||||
* The exact list of users that viewed the story will be hidden
|
||||
* from the poster this many seconds after the story expires.
|
||||
* (integer)This limit applies <strong>only</strong> to
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users,
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users can <strong>always</strong> access the viewer list.
|
||||
*/
|
||||
story_viewers_expire_period?: number
|
||||
/**
|
||||
* The maximum number of active
|
||||
* <a href="https://corefork.telegram.org/api/stories">stories</a>
|
||||
* for
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users (integer).
|
||||
*/
|
||||
story_expiring_limit_default?: number
|
||||
/**
|
||||
* The maximum number of active
|
||||
* <a href="https://corefork.telegram.org/api/stories">stories</a>
|
||||
* for
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users (integer).
|
||||
*/
|
||||
story_expiring_limit_premium?: number
|
||||
/**
|
||||
* The maximum UTF-8 length of story captions for
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
story_caption_length_limit_premium?: number
|
||||
/**
|
||||
* The maximum UTF-8 length of story captions for
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
story_caption_length_limit_default?: number
|
||||
/**
|
||||
* Indicates whether users can post stories. (string)One of:
|
||||
* <li><code>enabled</code> - Any user can post stories.</li>
|
||||
* <li><code>premium</code> - Only users with a
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* subscription can post stories.</li>
|
||||
* <li><code>disabled</code> - Users can't post stories.</li>
|
||||
*
|
||||
*/
|
||||
stories_posting?: string
|
||||
/**
|
||||
* Enabling
|
||||
* <a href="https://corefork.telegram.org/api/stories#stealth-mode">stories
|
||||
* stealth mode</a> with the <code>past</code> flag will erase
|
||||
* views of any story opened in the past
|
||||
* <code>stories_stealth_past_period</code> seconds. (integer)
|
||||
*/
|
||||
stories_stealth_past_period?: number
|
||||
/**
|
||||
* Enabling
|
||||
* <a href="https://corefork.telegram.org/api/stories#stealth-mode">stories
|
||||
* stealth mode</a> with the <code>future</code> flag will hide
|
||||
* views of any story opened in the next
|
||||
* <code>stories_stealth_future_period</code> seconds.
|
||||
* (integer)
|
||||
*/
|
||||
stories_stealth_future_period?: number
|
||||
/**
|
||||
* After enabling
|
||||
* <a href="https://corefork.telegram.org/api/stories#stealth-mode">stories
|
||||
* stealth mode</a>, this many seconds must elapse before the
|
||||
* user is allowed to enable it again. (integer)
|
||||
*/
|
||||
stories_stealth_cooldown_period?: number
|
||||
/**
|
||||
* Maximum number of stories that can be sent in a week by
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
stories_sent_weekly_limit_default?: number
|
||||
/**
|
||||
* Maximum number of stories that can be sent in a week by
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
stories_sent_weekly_limit_premium?: number
|
||||
/**
|
||||
* Maximum number of stories that can be sent in a month by
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
stories_sent_monthly_limit_default?: number
|
||||
/**
|
||||
* Maximum number of stories that can be sent in a month by
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
stories_sent_monthly_limit_premium?: number
|
||||
/**
|
||||
* Maximum number of
|
||||
* <a href="https://corefork.telegram.org/api/stories#media-areas">story
|
||||
* reaction media areas »</a> that can be added to a story by
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
stories_suggested_reactions_limit_default?: number
|
||||
/**
|
||||
* Maximum number of
|
||||
* <a href="https://corefork.telegram.org/api/stories#media-areas">story
|
||||
* reaction media areas »</a> that can be added to a story by
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
stories_suggested_reactions_limit_premium?: number
|
||||
/**
|
||||
* Username of the inline bot to use to generate venue location
|
||||
* tags for stories, see
|
||||
* <a href="https://corefork.telegram.org/api/stories#location-tags">here
|
||||
* »</a> for more info. (string)
|
||||
*/
|
||||
stories_venue_search_username?: string
|
||||
/**
|
||||
* ID of the official Telegram user that will post stories
|
||||
* about new Telegram features: stories posted by this user
|
||||
* should be shown on the
|
||||
* <a href="https://corefork.telegram.org/api/stories#watching-stories">active
|
||||
* or active and hidden stories bar</a> just like for contacts,
|
||||
* even if the user was removed from the contact list.
|
||||
* (integer, defaults to <code>777000</code>)
|
||||
*/
|
||||
stories_changelog_user_id?: number
|
||||
/**
|
||||
* Whether
|
||||
* <a href="https://corefork.telegram.org/api/entities">styled
|
||||
* text entities</a> and links in story text captions can be
|
||||
* used by all users (<code>enabled</code>), only
|
||||
* [Premium](/api/premium users) (<code>premium</code>), or no
|
||||
* one (<code>disabled</code>). (string)This field is used both
|
||||
* when posting stories, to indicate to the user whether they
|
||||
* can use entities, and when viewing stories, to hide entities
|
||||
* (client-side) on stories posted by users whose
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* subscription has expired (if <code>stories_entities ==
|
||||
* "premium"</code> and {@link RawUser}.<code>premium</code> is
|
||||
* not set, or if <code>stories_entities == "disabled"</code>).
|
||||
*
|
||||
*/
|
||||
stories_entities?: string
|
||||
/**
|
||||
* Whether
|
||||
* <a href="https://corefork.telegram.org/api/giveaways">giveaways</a>
|
||||
* can be started by the current user. (boolean)
|
||||
*/
|
||||
giveaway_gifts_purchase_available?: boolean
|
||||
/**
|
||||
* The maximum number of users that can be specified when
|
||||
* making a
|
||||
* <a href="https://corefork.telegram.org/api/giveaways">direct
|
||||
* giveaway</a>. (integer)
|
||||
*/
|
||||
giveaway_add_peers_max?: number
|
||||
/**
|
||||
* The maximum number of countries that can be specified when
|
||||
* restricting the set of participating countries in a
|
||||
* <a href="https://corefork.telegram.org/api/giveaways">giveaway</a>.
|
||||
* (itneger)
|
||||
*/
|
||||
giveaway_countries_max?: number
|
||||
/**
|
||||
* The number of
|
||||
* <a href="https://corefork.telegram.org/api/boost">boosts</a>
|
||||
* that will be gained by a channel for each winner of a
|
||||
* <a href="https://corefork.telegram.org/api/giveaways">giveaway</a>.
|
||||
* (integer)
|
||||
*/
|
||||
giveaway_boosts_per_premium?: number
|
||||
/**
|
||||
* The maximum duration in seconds of a
|
||||
* <a href="https://corefork.telegram.org/api/giveaways">giveaway</a>.
|
||||
* (integer)
|
||||
*/
|
||||
giveaway_period_max?: number
|
||||
/**
|
||||
* Maximum
|
||||
* <a href="https://corefork.telegram.org/api/boost">boost
|
||||
* level</a> for channels. (integer)
|
||||
*/
|
||||
boosts_channel_level_max?: number
|
||||
/**
|
||||
* The number of additional
|
||||
* <a href="https://corefork.telegram.org/api/boost">boost
|
||||
* slots</a> that the current user will receive when
|
||||
* <a href="https://corefork.telegram.org/api/premium#gifting-telegram-premium">gifting
|
||||
* a Telegram Premium subscription</a>.
|
||||
*/
|
||||
boosts_per_sent_gift?: number
|
||||
/**
|
||||
* The maximum number of
|
||||
* <a href="https://corefork.telegram.org/api/transcribe">speech
|
||||
* recognition »</a> calls per week for
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
transcribe_audio_trial_weekly_number?: number
|
||||
/**
|
||||
* The maximum allowed duration of media in seconds for
|
||||
* <a href="https://corefork.telegram.org/api/transcribe">speech
|
||||
* recognition »</a> for
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
transcribe_audio_trial_duration_max?: number
|
||||
/**
|
||||
* The maximum number of similar channels that can be
|
||||
* recommended by
|
||||
* {@link channels.RawGetChannelRecommendationsRequest} to
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
recommended_channels_limit_default?: number
|
||||
/**
|
||||
* The maximum number of similar channels that can be
|
||||
* recommended by
|
||||
* {@link channels.RawGetChannelRecommendationsRequest} to
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
recommended_channels_limit_premium?: number
|
||||
/**
|
||||
* Maximum UTF-8 length of {@link RawInputReplyToMessage}.
|
||||
* (integer)
|
||||
*/
|
||||
quote_length_max?: number
|
||||
/**
|
||||
* After reaching at least this
|
||||
* <a href="https://corefork.telegram.org/api/boost">boost
|
||||
* level »</a>, channels gain the ability to change their
|
||||
* <a href="https://corefork.telegram.org/api/colors">message
|
||||
* accent palette emoji »</a>. (integer)
|
||||
*/
|
||||
channel_bg_icon_level_min?: number
|
||||
/**
|
||||
* After reaching at least this
|
||||
* <a href="https://corefork.telegram.org/api/boost">boost
|
||||
* level »</a>, channels gain the ability to change their
|
||||
* <a href="https://corefork.telegram.org/api/colors">profile
|
||||
* accent palette emoji »</a>. (integer)
|
||||
*/
|
||||
channel_profile_bg_icon_level_min?: number
|
||||
/**
|
||||
* After reaching at least this
|
||||
* <a href="https://corefork.telegram.org/api/boost">boost
|
||||
* level »</a>, channels gain the ability to change their
|
||||
* <a href="https://corefork.telegram.org/api/emoji-status">status
|
||||
* emoji »</a>. (integer)
|
||||
*/
|
||||
channel_emoji_status_level_min?: number
|
||||
/**
|
||||
* After reaching at least this
|
||||
* <a href="https://corefork.telegram.org/api/boost">boost
|
||||
* level »</a>, channels gain the ability to set a
|
||||
* <a href="https://corefork.telegram.org/api/wallpapers#channel-wallpapers">fill
|
||||
* channel wallpaper, see here » for more info</a>. (integer)
|
||||
*/
|
||||
channel_wallpaper_level_min?: number
|
||||
/**
|
||||
* After reaching at least this
|
||||
* <a href="https://corefork.telegram.org/api/boost">boost
|
||||
* level »</a>, channels gain the ability to set any custom
|
||||
* <a href="https://corefork.telegram.org/api/wallpapers">wallpaper</a>,
|
||||
* not just
|
||||
* <a href="https://corefork.telegram.org/api/wallpapers">fill
|
||||
* channel wallpapers, see here » for more info</a>. (integer)
|
||||
*/
|
||||
channel_custom_wallpaper_level_min?: number
|
||||
/**
|
||||
* Maximum number of pinned dialogs in
|
||||
* <a href="https://corefork.telegram.org/api/saved-messages">saved
|
||||
* messages</a> for
|
||||
* non-<a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
saved_dialogs_pinned_limit_default?: number
|
||||
/**
|
||||
* Maximum number of pinned dialogs in
|
||||
* <a href="https://corefork.telegram.org/api/saved-messages">saved
|
||||
* messages</a> for
|
||||
* <a href="https://corefork.telegram.org/api/premium">Premium</a>
|
||||
* users. (integer)
|
||||
*/
|
||||
saved_dialogs_pinned_limit_premium?: number
|
||||
[key: string]: unknown
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
export * from './app-config.js'
|
||||
export * from './entities.js'
|
||||
export * from './input-privacy-rule.js'
|
||||
export * from './sticker-set.js'
|
||||
|
|
|
@ -1167,7 +1167,7 @@ export class UpdatesManager {
|
|||
const config = client.mt.network.config.getNow()
|
||||
|
||||
if (config) {
|
||||
client.mt.network.config.setConfig({
|
||||
client.mt.network.config.setData({
|
||||
...config,
|
||||
dcOptions: upd.dcOptions,
|
||||
})
|
||||
|
|
12
packages/core/src/highlevel/worker/app-config.ts
Normal file
12
packages/core/src/highlevel/worker/app-config.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { PublicPart } from '../../types/utils.js'
|
||||
import { AppConfigManager } from '../managers/app-config-manager.js'
|
||||
import { WorkerInvoker } from './invoker.js'
|
||||
|
||||
export class AppConfigManagerProxy implements PublicPart<AppConfigManager> {
|
||||
constructor(readonly invoker: WorkerInvoker) {}
|
||||
|
||||
private _bind = this.invoker.makeBinder<AppConfigManager>('app-config')
|
||||
|
||||
readonly get = this._bind('get')
|
||||
readonly getField = this._bind('getField')
|
||||
}
|
|
@ -4,6 +4,7 @@ import { LogManager } from '../../utils/logger.js'
|
|||
import { ITelegramClient } from '../client.types.js'
|
||||
import { PeersIndex } from '../types/peers/peers-index.js'
|
||||
import { RawUpdateHandler } from '../updates/types.js'
|
||||
import { AppConfigManagerProxy } from './app-config.js'
|
||||
import { WorkerInvoker } from './invoker.js'
|
||||
import { connectToWorker } from './platform/connect.js'
|
||||
import { ClientMessageHandler, SomeWorker, WorkerCustomMethods } from './protocol.js'
|
||||
|
@ -64,6 +65,7 @@ export class TelegramWorkerPort<Custom extends WorkerCustomMethods> implements I
|
|||
private _bind = this._invoker.makeBinder<ITelegramClient>('client')
|
||||
|
||||
readonly storage = new TelegramStorageProxy(this._invoker)
|
||||
readonly appConfig = new AppConfigManagerProxy(this._invoker)
|
||||
|
||||
private _destroyed = false
|
||||
destroy(terminate = false): void {
|
||||
|
|
|
@ -4,45 +4,39 @@ import { tl } from '@mtcute/tl'
|
|||
|
||||
import { SerializedError } from './errors.js'
|
||||
|
||||
export type WorkerInboundMessage =
|
||||
| {
|
||||
type: 'invoke'
|
||||
id: number
|
||||
target:
|
||||
| 'custom'
|
||||
| 'client'
|
||||
| 'storage'
|
||||
| 'storage-self'
|
||||
| 'storage-peers'
|
||||
method: string
|
||||
args: unknown[]
|
||||
void: boolean
|
||||
}
|
||||
export type WorkerInboundMessage = {
|
||||
type: 'invoke'
|
||||
id: number
|
||||
target: 'custom' | 'client' | 'storage' | 'storage-self' | 'storage-peers' | 'app-config'
|
||||
method: string
|
||||
args: unknown[]
|
||||
void: boolean
|
||||
}
|
||||
|
||||
export type WorkerOutboundMessage =
|
||||
| { type: 'server_update'; update: tl.TypeUpdates }
|
||||
| {
|
||||
type: 'update'
|
||||
update: tl.TypeUpdate
|
||||
users: Map<number, tl.TypeUser>
|
||||
chats: Map<number, tl.TypeChat>
|
||||
hasMin: boolean
|
||||
}
|
||||
type: 'update'
|
||||
update: tl.TypeUpdate
|
||||
users: Map<number, tl.TypeUser>
|
||||
chats: Map<number, tl.TypeChat>
|
||||
hasMin: boolean
|
||||
}
|
||||
| { type: 'error'; error: unknown }
|
||||
| {
|
||||
type: 'log'
|
||||
color: number
|
||||
level: number
|
||||
tag: string
|
||||
fmt: string
|
||||
args: unknown[]
|
||||
}
|
||||
type: 'log'
|
||||
color: number
|
||||
level: number
|
||||
tag: string
|
||||
fmt: string
|
||||
args: unknown[]
|
||||
}
|
||||
| {
|
||||
type: 'result'
|
||||
id: number
|
||||
result?: unknown
|
||||
error?: SerializedError
|
||||
}
|
||||
type: 'result'
|
||||
id: number
|
||||
result?: unknown
|
||||
error?: SerializedError
|
||||
}
|
||||
|
||||
export type SomeWorker = NodeWorker | Worker | SharedWorker
|
||||
|
||||
|
|
|
@ -33,6 +33,9 @@ export function makeTelegramWorker<T extends WorkerCustomMethods>(params: Telegr
|
|||
case 'storage-peers':
|
||||
target = client.storage.peers
|
||||
break
|
||||
case 'app-config':
|
||||
target = client.appConfig
|
||||
break
|
||||
|
||||
default: {
|
||||
respond({
|
||||
|
|
|
@ -48,7 +48,7 @@ describe('ConfigManager', () => {
|
|||
const cm = new ConfigManager(getConfig)
|
||||
expect(cm.isStale).toBe(true)
|
||||
|
||||
cm.setConfig(config)
|
||||
cm.setData(config)
|
||||
expect(cm.isStale).toBe(false)
|
||||
|
||||
vi.setSystemTime(300_000)
|
||||
|
@ -69,8 +69,14 @@ describe('ConfigManager', () => {
|
|||
const cm = new ConfigManager(getConfig)
|
||||
await cm.update()
|
||||
|
||||
vi.setSystemTime(300_000)
|
||||
getConfig.mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
...config,
|
||||
expires: 600,
|
||||
}),
|
||||
)
|
||||
getConfig.mockClear()
|
||||
await vi.advanceTimersByTimeAsync(301_000)
|
||||
await Promise.all([cm.update(), cm.update()])
|
||||
|
||||
expect(getConfig).toHaveBeenCalledOnce()
|
||||
|
@ -79,11 +85,11 @@ describe('ConfigManager', () => {
|
|||
it('should call listeners on config update', async () => {
|
||||
const cm = new ConfigManager(getConfig)
|
||||
const listener = vi.fn()
|
||||
cm.onConfigUpdate(listener)
|
||||
cm.onReload(listener)
|
||||
await cm.update()
|
||||
|
||||
vi.setSystemTime(300_000)
|
||||
cm.offConfigUpdate(listener)
|
||||
cm.onReload(listener)
|
||||
await cm.update()
|
||||
|
||||
expect(listener).toHaveBeenCalledOnce()
|
||||
|
|
|
@ -1,74 +1,19 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
|
||||
import { Reloadable } from '../utils/reloadable.js'
|
||||
|
||||
/**
|
||||
* Config manager is responsible for keeping
|
||||
* the current server configuration up-to-date
|
||||
* and providing methods to find the best DC
|
||||
* option for the current session.
|
||||
*/
|
||||
export class ConfigManager {
|
||||
constructor(private _update: () => Promise<tl.RawConfig>) {}
|
||||
|
||||
private _destroyed = false
|
||||
private _config?: tl.RawConfig
|
||||
private _cdnConfig?: tl.RawCdnConfig
|
||||
|
||||
private _updateTimeout?: NodeJS.Timeout
|
||||
private _updatingPromise?: Promise<void>
|
||||
|
||||
private _listeners: ((config: tl.RawConfig) => void)[] = []
|
||||
|
||||
get isStale(): boolean {
|
||||
return !this._config || this._config.expires <= Date.now() / 1000
|
||||
}
|
||||
|
||||
update(force = false): Promise<void> {
|
||||
if (!force && !this.isStale) return Promise.resolve()
|
||||
if (this._updatingPromise) return this._updatingPromise
|
||||
|
||||
return (this._updatingPromise = this._update().then((config) => {
|
||||
if (this._destroyed) return
|
||||
this._updatingPromise = undefined
|
||||
|
||||
this.setConfig(config)
|
||||
}))
|
||||
}
|
||||
|
||||
setConfig(config: tl.RawConfig): void {
|
||||
this._config = config
|
||||
|
||||
if (this._updateTimeout) clearTimeout(this._updateTimeout)
|
||||
this._updateTimeout = setTimeout(
|
||||
() => void this.update().catch(() => {}),
|
||||
(config.expires - Date.now() / 1000) * 1000,
|
||||
)
|
||||
|
||||
for (const cb of this._listeners) cb(config)
|
||||
}
|
||||
|
||||
onConfigUpdate(cb: (config: tl.RawConfig) => void): void {
|
||||
this._listeners.push(cb)
|
||||
}
|
||||
|
||||
offConfigUpdate(cb: (config: tl.RawConfig) => void): void {
|
||||
const idx = this._listeners.indexOf(cb)
|
||||
if (idx >= 0) this._listeners.splice(idx, 1)
|
||||
}
|
||||
|
||||
getNow(): tl.RawConfig | undefined {
|
||||
return this._config
|
||||
}
|
||||
|
||||
async get(): Promise<tl.RawConfig> {
|
||||
if (this.isStale) await this.update()
|
||||
|
||||
return this._config!
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (this._updateTimeout) clearTimeout(this._updateTimeout)
|
||||
this._listeners.length = 0
|
||||
this._destroyed = true
|
||||
export class ConfigManager extends Reloadable<tl.RawConfig> {
|
||||
constructor(update: () => Promise<tl.RawConfig>) {
|
||||
super({
|
||||
reload: update,
|
||||
getExpiresAt: (data) => data.expires * 1000,
|
||||
})
|
||||
}
|
||||
|
||||
async findOption(params: {
|
||||
|
@ -81,7 +26,7 @@ export class ConfigManager {
|
|||
}): Promise<tl.RawDcOption | undefined> {
|
||||
if (this.isStale) await this.update()
|
||||
|
||||
const options = this._config!.dcOptions.filter((opt) => {
|
||||
const options = this._data!.dcOptions.filter((opt) => {
|
||||
if (opt.tcpoOnly) return false // unsupported
|
||||
if (opt.ipv6 && !params.allowIpv6) return false
|
||||
if (opt.mediaOnly && !params.allowMedia) return false
|
||||
|
|
|
@ -3,7 +3,14 @@ import { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
|||
|
||||
import { StorageManager } from '../storage/storage.js'
|
||||
import { MtArgumentError, MtcuteError, MtTimeoutError, MtUnsupportedError } from '../types/index.js'
|
||||
import { ControllablePromise, createControllablePromise, DcOptions, ICryptoProvider, Logger, sleep } from '../utils/index.js'
|
||||
import {
|
||||
ControllablePromise,
|
||||
createControllablePromise,
|
||||
DcOptions,
|
||||
ICryptoProvider,
|
||||
Logger,
|
||||
sleep,
|
||||
} from '../utils/index.js'
|
||||
import { assertTypeIs } from '../utils/type-assertions.js'
|
||||
import { ConfigManager } from './config-manager.js'
|
||||
import { MultiSessionConnection } from './multi-session-connection.js'
|
||||
|
@ -462,7 +469,7 @@ export class NetworkManager {
|
|||
this._updateHandler = params.onUpdate
|
||||
|
||||
this._onConfigChanged = this._onConfigChanged.bind(this)
|
||||
config.onConfigUpdate(this._onConfigChanged)
|
||||
config.onReload(this._onConfigChanged)
|
||||
}
|
||||
|
||||
private async _findDcOptions(dcId: number): Promise<DcOptions> {
|
||||
|
@ -627,8 +634,7 @@ export class NetworkManager {
|
|||
} else {
|
||||
if (auth.bot) {
|
||||
// bots may receive tmpSessions, which we should respect
|
||||
this.config.update(true)
|
||||
.catch((e: Error) => this.params.emitError(e))
|
||||
this.config.update(true).catch((e: Error) => this.params.emitError(e))
|
||||
}
|
||||
|
||||
user = auth
|
||||
|
@ -837,6 +843,6 @@ export class NetworkManager {
|
|||
for (const dc of this._dcConnections.values()) {
|
||||
dc.destroy()
|
||||
}
|
||||
this.config.offConfigUpdate(this._onConfigChanged)
|
||||
this.config.offReload(this._onConfigChanged)
|
||||
}
|
||||
}
|
||||
|
|
77
packages/core/src/utils/reloadable.ts
Normal file
77
packages/core/src/utils/reloadable.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
import { asyncResettable } from './function-utils.js'
|
||||
|
||||
export interface ReloadableParams<Data> {
|
||||
reload: (old?: Data) => Promise<Data>
|
||||
getExpiresAt: (data: Data) => number
|
||||
onError?: (err: unknown) => void
|
||||
disableAutoReload?: boolean
|
||||
}
|
||||
|
||||
export class Reloadable<Data> {
|
||||
constructor(readonly params: ReloadableParams<Data>) {}
|
||||
|
||||
protected _data?: Data
|
||||
protected _expiresAt = 0
|
||||
protected _listeners: ((data: Data) => void)[] = []
|
||||
protected _timeout?: NodeJS.Timeout
|
||||
|
||||
private _reload = asyncResettable(async () => {
|
||||
const data = await this.params.reload(this._data)
|
||||
this.setData(data)
|
||||
|
||||
this._listeners.forEach((cb) => cb(data))
|
||||
})
|
||||
|
||||
get isStale(): boolean {
|
||||
return !this._data || this._expiresAt <= Date.now()
|
||||
}
|
||||
|
||||
setData(data: Data): void {
|
||||
const expiresAt = this.params.getExpiresAt(data)
|
||||
|
||||
this._data = data
|
||||
this._expiresAt = expiresAt
|
||||
|
||||
if (this._timeout) clearTimeout(this._timeout)
|
||||
|
||||
if (!this.params.disableAutoReload) {
|
||||
this._timeout = setTimeout(() => {
|
||||
this._reload.reset()
|
||||
this.update().catch((err: unknown) => {
|
||||
this.params.onError?.(err)
|
||||
})
|
||||
}, expiresAt - Date.now())
|
||||
}
|
||||
}
|
||||
|
||||
update(force = false): Promise<void> {
|
||||
if (!force && !this.isStale) return Promise.resolve()
|
||||
|
||||
return this._reload.run()
|
||||
}
|
||||
|
||||
onReload(cb: (data: Data) => void): void {
|
||||
this._listeners.push(cb)
|
||||
}
|
||||
|
||||
offReload(cb: (data: Data) => void): void {
|
||||
const idx = this._listeners.indexOf(cb)
|
||||
if (idx >= 0) this._listeners.splice(idx, 1)
|
||||
}
|
||||
|
||||
getNow(): Data | undefined {
|
||||
return this._data
|
||||
}
|
||||
|
||||
async get(): Promise<Data> {
|
||||
await this.update()
|
||||
|
||||
return this._data!
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (this._timeout) clearTimeout(this._timeout)
|
||||
this._listeners.length = 0
|
||||
this._reload.reset()
|
||||
}
|
||||
}
|
1
packages/tl/app-config.json
Normal file
1
packages/tl/app-config.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -9,6 +9,7 @@ export const API_SCHEMA_JSON_FILE = join(__dirname, '../api-schema.json')
|
|||
export const API_SCHEMA_DIFF_JSON_FILE = join(__dirname, '../diff.json')
|
||||
export const MTP_SCHEMA_JSON_FILE = join(__dirname, '../mtp-schema.json')
|
||||
export const ERRORS_JSON_FILE = join(__dirname, '../raw-errors.json')
|
||||
export const APP_CONFIG_JSON_FILE = join(__dirname, '../app-config.json')
|
||||
|
||||
export const CORE_DOMAIN = 'https://core.telegram.org'
|
||||
export const COREFORK_DOMAIN = 'https://corefork.telegram.org'
|
||||
|
|
|
@ -7,6 +7,7 @@ import { createInterface } from 'readline'
|
|||
|
||||
import {
|
||||
camelToPascal,
|
||||
jsComment,
|
||||
PRIMITIVE_TO_TS,
|
||||
snakeToCamel,
|
||||
splitNameToNamespace,
|
||||
|
@ -16,6 +17,7 @@ import {
|
|||
|
||||
import {
|
||||
API_SCHEMA_JSON_FILE,
|
||||
APP_CONFIG_JSON_FILE,
|
||||
BLOGFORK_DOMAIN,
|
||||
CORE_DOMAIN,
|
||||
COREFORK_DOMAIN,
|
||||
|
@ -98,6 +100,13 @@ function extractDescription($: cheerio.CheerioAPI) {
|
|||
.trim()
|
||||
}
|
||||
|
||||
function htmlAll($: cheerio.CheerioAPI, search: cheerio.Cheerio<cheerio.Element>) {
|
||||
return search
|
||||
.get()
|
||||
.map((el) => $(el).html() ?? '')
|
||||
.join('')
|
||||
}
|
||||
|
||||
// from https://github.com/sindresorhus/cli-spinners/blob/main/spinners.json
|
||||
const PROGRESS_CHARS = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
||||
|
||||
|
@ -127,6 +136,168 @@ async function chooseDomainForDocs(headers: Record<string, string>): Promise<[nu
|
|||
return [maxLayer, maxDomain]
|
||||
}
|
||||
|
||||
function lastParensGroup(text: string): string | undefined {
|
||||
const groups = []
|
||||
let depth = 0
|
||||
let current = ''
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
if (text[i] === ')') depth--
|
||||
|
||||
if (depth > 0) {
|
||||
current += text[i]
|
||||
}
|
||||
|
||||
if (text[i] === '(') depth++
|
||||
|
||||
if (current && depth === 0) {
|
||||
groups.push(current)
|
||||
current = ''
|
||||
}
|
||||
}
|
||||
|
||||
return groups[groups.length - 1]
|
||||
}
|
||||
|
||||
async function fetchAppConfigDocumentation() {
|
||||
const headers = {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' +
|
||||
'Chrome/87.0.4280.88 Safari/537.36',
|
||||
}
|
||||
|
||||
const [, domain] = await chooseDomainForDocs(headers)
|
||||
|
||||
const page = await fetchRetry(`${domain}/api/config`, { headers })
|
||||
const $ = cheerio.load(page)
|
||||
|
||||
const fields = $('p:icontains(typical fields included)').nextUntil('h3')
|
||||
normalizeLinks(`${domain}/api/config`, fields)
|
||||
const fieldNames = fields.filter('h4')
|
||||
|
||||
const _example = $('p:icontains(example value)').next('pre').find('code')
|
||||
const example = JSON.parse(_example.text().trim()) as Record<string, unknown>
|
||||
|
||||
const result: Record<string, unknown> = {}
|
||||
|
||||
function valueToTypescript(value: unknown, record = false): string {
|
||||
if (value === undefined) return 'unknown'
|
||||
if (value === null) return 'null'
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
const types = new Set(value.map((v) => typeof v))
|
||||
|
||||
if (types.size === 1) {
|
||||
return valueToTypescript(value[0]) + '[]'
|
||||
}
|
||||
|
||||
return `(${value.map((v) => valueToTypescript(v)).join(' | ')})[]`
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
if (record) {
|
||||
const inner = Object.values(value)[0] as unknown
|
||||
|
||||
return `Record<string, ${valueToTypescript(inner)}>`
|
||||
}
|
||||
|
||||
return (
|
||||
'{\n' +
|
||||
Object.entries(value)
|
||||
.map(([k, v]) => ` ${k}: ${valueToTypescript(v)}`)
|
||||
.join('\n') +
|
||||
'\n}'
|
||||
)
|
||||
}
|
||||
|
||||
return typeof value
|
||||
}
|
||||
|
||||
function docsTypeToTypescript(field: string, type: string): string {
|
||||
let m
|
||||
|
||||
if ((m = type.match(/(.*), defaults to .+$/i))) {
|
||||
return docsTypeToTypescript(field, m[1])
|
||||
}
|
||||
|
||||
if ((m = type.match(/^(?:array of )(.+?)s?$/i))) {
|
||||
return docsTypeToTypescript(field, m[1]) + '[]'
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'integer':
|
||||
return 'number'
|
||||
case 'itneger':
|
||||
return 'number'
|
||||
case 'float':
|
||||
return 'number'
|
||||
case 'string':
|
||||
return 'string'
|
||||
case 'string emoji':
|
||||
return 'string'
|
||||
case 'boolean':
|
||||
return 'boolean'
|
||||
case 'bool':
|
||||
return 'boolean'
|
||||
}
|
||||
|
||||
if (type.match(/^object with .+? keys|^map of/i)) {
|
||||
return valueToTypescript(example[field], true)
|
||||
}
|
||||
|
||||
if (type.match(/^strings?, /)) {
|
||||
if (type.includes('or')) {
|
||||
const options = type.slice(8).split(/, | or /)
|
||||
|
||||
return options.map((o) => (o[0] === '"' ? o : JSON.stringify(o))).join(' | ')
|
||||
}
|
||||
|
||||
return 'string'
|
||||
}
|
||||
|
||||
if (type.includes(',')) {
|
||||
return docsTypeToTypescript(field, type.split(',')[0])
|
||||
}
|
||||
|
||||
if (type.match(/^numeric string/)) {
|
||||
return 'string'
|
||||
}
|
||||
|
||||
if (type.includes('as described')) {
|
||||
return valueToTypescript(example[field])
|
||||
}
|
||||
|
||||
console.log(`Failed to parse type at ${field}: ${type}`)
|
||||
|
||||
return valueToTypescript(example[field])
|
||||
}
|
||||
|
||||
for (const fieldName of fieldNames.toArray()) {
|
||||
const name = $(fieldName).text().trim()
|
||||
const description = htmlAll($, $(fieldName).nextUntil('h3, h4'))
|
||||
let type = 'unknown'
|
||||
|
||||
let typeStr = lastParensGroup(description)
|
||||
|
||||
if (!typeStr) {
|
||||
typeStr = description.match(/\s+\((.+?)(?:\)|\.|\)\.)$/)?.[1]
|
||||
}
|
||||
|
||||
if (typeStr) {
|
||||
type = docsTypeToTypescript(name, typeStr)
|
||||
} else if (name in example) {
|
||||
type = valueToTypescript(example[name])
|
||||
}
|
||||
|
||||
result[name] = {
|
||||
type,
|
||||
description: jsComment(description),
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export async function fetchDocumentation(
|
||||
schema: TlFullSchema,
|
||||
layer: number,
|
||||
|
@ -366,10 +537,11 @@ async function main() {
|
|||
console.log('1. Update documentation')
|
||||
console.log('2. Apply descriptions.yaml')
|
||||
console.log('3. Apply documentation to schema')
|
||||
console.log('4. Fetch app config documentation')
|
||||
|
||||
const act = parseInt(await input('[0-3] > '))
|
||||
const act = parseInt(await input('[0-4] > '))
|
||||
|
||||
if (isNaN(act) || act < 0 || act > 3) {
|
||||
if (isNaN(act) || act < 0 || act > 4) {
|
||||
console.log('Invalid action')
|
||||
continue
|
||||
}
|
||||
|
@ -412,15 +584,20 @@ async function main() {
|
|||
applyDocumentation(schema, cached)
|
||||
await writeFile(API_SCHEMA_JSON_FILE, JSON.stringify(packTlSchema(schema, layer)))
|
||||
}
|
||||
|
||||
if (act === 4) {
|
||||
const appConfig = await fetchAppConfigDocumentation()
|
||||
|
||||
console.log('Fetched app config documentation')
|
||||
await writeFile(APP_CONFIG_JSON_FILE, JSON.stringify(appConfig))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.url.startsWith('file:')) {
|
||||
// (A)
|
||||
const modulePath = fileURLToPath(import.meta.url)
|
||||
|
||||
if (process.argv[1] === modulePath) {
|
||||
// (B)
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
|
|
Loading…
Reference in a new issue