feat: updated to layer 186

This commit is contained in:
alina 🌸 2024-08-19 09:24:59 +03:00
parent 8e12103514
commit d5c96ca6fe
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
20 changed files with 290 additions and 39 deletions

View file

@ -736,6 +736,7 @@ withParams(params: RpcCallOptions): this\n`)
'onConnectionState', 'onConnectionState',
'getServerUpdateHandler', 'getServerUpdateHandler',
'changePrimaryDc', 'changePrimaryDc',
'getMtprotoMessageId',
].forEach((name) => { ].forEach((name) => {
output.write( output.write(
`TelegramClient.prototype.${name} = function(...args) {\n` `TelegramClient.prototype.${name} = function(...args) {\n`

View file

@ -1,5 +1,6 @@
import type { mtp } from '@mtcute/tl' import type { mtp } from '@mtcute/tl'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import type Long from 'long'
import type { MtClientOptions } from '../network/client.js' import type { MtClientOptions } from '../network/client.js'
import { MtClient } from '../network/client.js' import { MtClient } from '../network/client.js'
@ -368,4 +369,8 @@ export class BaseTelegramClient implements ITelegramClient {
changePrimaryDc(dcId: number): Promise<void> { changePrimaryDc(dcId: number): Promise<void> {
return this.mt.network.changePrimaryDc(dcId) return this.mt.network.changePrimaryDc(dcId)
} }
async getMtprotoMessageId(): Promise<Long> {
return this.mt.network.getMtprotoMessageId()
}
} }

View file

@ -186,6 +186,7 @@ import type { SendCopyParams } from './methods/messages/send-copy.js'
import { sendCopy } from './methods/messages/send-copy.js' import { sendCopy } from './methods/messages/send-copy.js'
import { sendMediaGroup } from './methods/messages/send-media-group.js' import { sendMediaGroup } from './methods/messages/send-media-group.js'
import { sendMedia } from './methods/messages/send-media.js' import { sendMedia } from './methods/messages/send-media.js'
import { sendPaidReaction } from './methods/messages/send-paid-reaction.js'
import type { QuoteParamsFrom } from './methods/messages/send-quote.js' import type { QuoteParamsFrom } from './methods/messages/send-quote.js'
import { quoteWithMedia, quoteWithMediaGroup, quoteWithText } from './methods/messages/send-quote.js' import { quoteWithMedia, quoteWithMediaGroup, quoteWithText } from './methods/messages/send-quote.js'
import { sendReaction } from './methods/messages/send-reaction.js' import { sendReaction } from './methods/messages/send-reaction.js'
@ -519,6 +520,7 @@ export interface TelegramClient extends ITelegramClient {
*/ */
on(name: 'delete_business_message', handler: ((upd: DeleteBusinessMessageUpdate) => void)): this on(name: 'delete_business_message', handler: ((upd: DeleteBusinessMessageUpdate) => void)): this
// eslint-disable-next-line ts/no-explicit-any
on(name: string, handler: (...args: any[]) => void): this on(name: string, handler: (...args: any[]) => void): this
/** /**
@ -4153,6 +4155,34 @@ export interface TelegramClient extends ITelegramClient {
*/ */
progressCallback?: (uploaded: number, total: number) => void progressCallback?: (uploaded: number, total: number) => void
}): Promise<Message> }): Promise<Message>
/**
* Send a paid reaction using Telegram Stars.
*
* **Available**: 👤 users only
*
* @returns
* Message to which the reaction was sent, if available.
* The message is normally available for users, but may not be available for bots in PMs.
*/
sendPaidReaction(
params: InputMessageId & {
/** Whether to send the reaction anonymously */
anonymous?: boolean
/**
* Number of reactions to send
*
* @default 1
*/
count?: number
/**
* Whether to dispatch the returned edit message event
* to the client's update handler.
*/
shouldDispatch?: true
}): Promise<MessageReactions>
/** Send a text in reply to a given quote */ /** Send a text in reply to a given quote */
quoteWithText( quoteWithText(
message: Message, message: Message,
@ -6179,6 +6209,9 @@ TelegramClient.prototype.sendMediaGroup = function (...args) {
TelegramClient.prototype.sendMedia = function (...args) { TelegramClient.prototype.sendMedia = function (...args) {
return sendMedia(this._client, ...args) return sendMedia(this._client, ...args)
} }
TelegramClient.prototype.sendPaidReaction = function (...args) {
return sendPaidReaction(this._client, ...args)
}
TelegramClient.prototype.quoteWithText = function (...args) { TelegramClient.prototype.quoteWithText = function (...args) {
return quoteWithText(this._client, ...args) return quoteWithText(this._client, ...args)
} }
@ -6532,6 +6565,9 @@ TelegramClient.prototype.getServerUpdateHandler = function (...args) {
TelegramClient.prototype.changePrimaryDc = function (...args) { TelegramClient.prototype.changePrimaryDc = function (...args) {
return this._client.changePrimaryDc(...args) return this._client.changePrimaryDc(...args)
} }
TelegramClient.prototype.getMtprotoMessageId = function (...args) {
return this._client.getMtprotoMessageId(...args)
}
TelegramClient.prototype.onServerUpdate = function () { TelegramClient.prototype.onServerUpdate = function () {
throw new Error('onServerUpdate is not available for TelegramClient, use .on() methods instead') throw new Error('onServerUpdate is not available for TelegramClient, use .on() methods instead')
} }

View file

@ -1,4 +1,5 @@
import type { tl } from '@mtcute/tl' import type { tl } from '@mtcute/tl'
import type Long from 'long'
import type { ConnectionKind, RpcCallOptions } from '../network/index.js' import type { ConnectionKind, RpcCallOptions } from '../network/index.js'
import type { MustEqual, PublicPart } from '../types/utils.js' import type { MustEqual, PublicPart } from '../types/utils.js'
@ -69,4 +70,5 @@ export interface ITelegramClient {
computeSrpParams(request: tl.account.RawPassword, password: string): Promise<tl.RawInputCheckPasswordSRP> computeSrpParams(request: tl.account.RawPassword, password: string): Promise<tl.RawInputCheckPasswordSRP>
computeNewPasswordHash(algo: tl.TypePasswordKdfAlgo, password: string): Promise<Uint8Array> computeNewPasswordHash(algo: tl.TypePasswordKdfAlgo, password: string): Promise<Uint8Array>
getMtprotoMessageId(): Promise<Long>
} }

View file

@ -176,6 +176,7 @@ export { sendCopy } from './methods/messages/send-copy.js'
export type { SendCopyParams } from './methods/messages/send-copy.js' export type { SendCopyParams } from './methods/messages/send-copy.js'
export { sendMediaGroup } from './methods/messages/send-media-group.js' export { sendMediaGroup } from './methods/messages/send-media-group.js'
export { sendMedia } from './methods/messages/send-media.js' export { sendMedia } from './methods/messages/send-media.js'
export { sendPaidReaction } from './methods/messages/send-paid-reaction.js'
export { quoteWithText } from './methods/messages/send-quote.js' export { quoteWithText } from './methods/messages/send-quote.js'
export type { QuoteParamsFrom } from './methods/messages/send-quote.js' export type { QuoteParamsFrom } from './methods/messages/send-quote.js'
export { quoteWithMedia } from './methods/messages/send-quote.js' export { quoteWithMedia } from './methods/messages/send-quote.js'

View file

@ -0,0 +1,89 @@
import { tl } from '@mtcute/tl'
import type { ITelegramClient } from '../../client.types.js'
import type {
InputMessageId,
} from '../../types/index.js'
import {
MessageReactions,
PeersIndex,
normalizeInputMessageId,
} from '../../types/index.js'
import { assertIsUpdatesGroup } from '../../updates/utils.js'
import { resolvePeer } from '../users/resolve-peer.js'
import { MtcuteError } from '../../../types/errors.js'
import { assertTypeIs } from '../../../utils/type-assertions.js'
import { getMarkedPeerId } from '../../../utils/peer-utils.js'
// @available=user
/**
* Send a paid reaction using Telegram Stars.
*
* @returns
* Message to which the reaction was sent, if available.
* The message is normally available for users, but may not be available for bots in PMs.
*/
export async function sendPaidReaction(
client: ITelegramClient,
params: InputMessageId & {
/** Whether to send the reaction anonymously */
anonymous?: boolean
/**
* Number of reactions to send
*
* @default 1
*/
count?: number
/**
* Whether to dispatch the returned edit message event
* to the client's update handler.
*/
shouldDispatch?: true
},
): Promise<MessageReactions> {
const { anonymous, count = 1 } = params
const { chatId, message } = normalizeInputMessageId(params)
const peer = await resolvePeer(client, chatId)
let res
for (let i = 0; i < 3; i++) {
try {
res = await client.call({
_: 'messages.sendPaidReaction',
peer,
msgId: message,
count,
private: anonymous,
randomId: await client.getMtprotoMessageId(),
})
break
} catch (e) {
if (tl.RpcError.is(e, 'RANDOM_ID_EXPIRED')) {
// just retry, mtproto message id may have been stale
continue
}
throw e
}
}
if (!res) {
throw new MtcuteError('Could not send paid reaction')
}
assertIsUpdatesGroup('messages.sendReaction', res)
// normally the group only contains updateMessageReactions
const peers = PeersIndex.from(res)
const upd = res.updates[0]
assertTypeIs('messages.sendPaidReaction (@ .updates[0])', upd, 'updateMessageReactions')
return new MessageReactions(upd.msgId, getMarkedPeerId(upd.peer), upd.reactions, peers)
}

View file

@ -3,7 +3,7 @@ import type { tl } from '@mtcute/tl'
import { makeInspectable } from '../../utils/index.js' import { makeInspectable } from '../../utils/index.js'
import { memoizeGetters } from '../../utils/memoize.js' import { memoizeGetters } from '../../utils/memoize.js'
import type { PeersIndex } from '../peers/index.js' import type { PeersIndex } from '../peers/index.js'
import { PeerReaction } from '../reactions/peer-reaction.js' import { PaidPeerReaction, PeerReaction } from '../reactions/peer-reaction.js'
import { ReactionCount } from '../reactions/reaction-count.js' import { ReactionCount } from '../reactions/reaction-count.js'
/** /**
@ -40,6 +40,11 @@ export class MessageReactions {
get recentReactions(): PeerReaction[] { get recentReactions(): PeerReaction[] {
return this.raw.recentReactions?.map(reaction => new PeerReaction(reaction, this._peers)) ?? [] return this.raw.recentReactions?.map(reaction => new PeerReaction(reaction, this._peers)) ?? []
} }
/** Leaderboard of paid reactions to the message */
get paidReactions(): PaidPeerReaction[] {
return this.raw.topReactors?.map(reaction => new PaidPeerReaction(reaction, this._peers)) ?? []
}
} }
memoizeGetters(MessageReactions, ['reactions', 'recentReactions']) memoizeGetters(MessageReactions, ['reactions', 'recentReactions'])

View file

@ -354,6 +354,14 @@ export interface ChatActionTopicDeleted {
topic: ForumTopic topic: ForumTopic
} }
/** Profile signatures were toggled in a channel ("Super Channel" mode) */
export interface ChatActionSignatureProfilesToggled {
type: 'signature_profiles_toggled'
/** Whether signatures are enabled as of this action */
new: boolean
}
/** Chat event action (`null` if unsupported) */ /** Chat event action (`null` if unsupported) */
export type ChatAction = export type ChatAction =
| ChatActionUserJoined | ChatActionUserJoined
@ -392,6 +400,7 @@ export type ChatAction =
| ChatActionTopicCreated | ChatActionTopicCreated
| ChatActionTopicEdited | ChatActionTopicEdited
| ChatActionTopicDeleted | ChatActionTopicDeleted
| ChatActionSignatureProfilesToggled
| null | null
/** @internal */ /** @internal */
@ -613,6 +622,11 @@ export function _actionFromTl(e: tl.TypeChannelAdminLogEventAction, peers: Peers
} }
// case 'channelAdminLogEventActionPinTopic' // case 'channelAdminLogEventActionPinTopic'
// ^ looks like it is not used, and pinned topics are not at all presented in the event log // ^ looks like it is not used, and pinned topics are not at all presented in the event log
case 'channelAdminLogEventActionToggleSignatureProfiles':
return {
type: 'signature_profiles_toggled',
new: e.newValue,
}
default: default:
return null return null
} }

View file

@ -63,6 +63,7 @@ export function normalizeChatEventFilters(input: InputChatEventFilters): ChatEve
case 'def_perms_changed': case 'def_perms_changed':
case 'forum_toggled': case 'forum_toggled':
case 'no_forwards_toggled': case 'no_forwards_toggled':
case 'signature_profiles_toggled':
serverFilter.settings = true serverFilter.settings = true
break break
case 'msg_pinned': case 'msg_pinned':

View file

@ -175,6 +175,22 @@ export class ChatMember {
return null return null
} }
} }
/**
* If this member subscribed to a channel using Telegram Stars,
* this field will contain the date when the subscription will expire
*/
get subscriptionUntilDate(): Date | null {
switch (this.raw._) {
case 'channelParticipant':
case 'channelParticipantSelf':
if (!this.raw.subscriptionUntilDate) return null
return new Date(this.raw.subscriptionUntilDate * 1000)
}
return null
}
} }
memoizeGetters(ChatMember, ['user', 'invitedBy', 'promotedBy', 'restrictedBy', 'restrictions']) memoizeGetters(ChatMember, ['user', 'invitedBy', 'promotedBy', 'restrictedBy', 'restrictions'])

View file

@ -284,6 +284,11 @@ export class Chat {
return (this.peer._ === 'channel' || this.peer._ === 'chat') && this.peer.noforwards! return (this.peer._ === 'channel' || this.peer._ === 'chat') && this.peer.noforwards!
} }
/** Whether this channel has profile signatures (i.e. "Super Channel") */
get hasProfileSignatures(): boolean {
return this.peer._ === 'channel' && this.peer.signatureProfiles!
}
/** /**
* Title, for supergroups, channels and groups * Title, for supergroups, channels and groups
*/ */
@ -487,6 +492,16 @@ export class Chat {
return new User(this.peer) return new User(this.peer)
} }
/**
* If a subscription to this channel was bought using Telegram Stars,
* this field will contain the date when the subscription will expire.
*/
get subscriptionUntilDate(): Date | null {
if (this.peer._ !== 'channel' || !this.peer.subscriptionUntilDate) return null
return new Date(this.peer.subscriptionUntilDate * 1000)
}
/** @internal */ /** @internal */
static _parseFromMessage(message: tl.RawMessage | tl.RawMessageService, peers: PeersIndex): Chat { static _parseFromMessage(message: tl.RawMessage | tl.RawMessageService, peers: PeersIndex): Chat {
return Chat._parseFromPeer(message.peerId, peers) return Chat._parseFromPeer(message.peerId, peers)

View file

@ -62,6 +62,11 @@ export class FullChat extends Chat {
return this.fullPeer?._ === 'userFull' && this.fullPeer.voiceMessagesForbidden! return this.fullPeer?._ === 'userFull' && this.fullPeer.voiceMessagesForbidden!
} }
/** Whether paid reactions are enabled for this channel */
get hasPaidReactions(): boolean {
return this.fullPeer?._ === 'channelFull' && this.fullPeer.paidReactionsAvailable!
}
/** /**
* Full information about this chat's photo, if any. * Full information about this chat's photo, if any.
* *

View file

@ -6,6 +6,7 @@ import { makeInspectable } from '../../utils/index.js'
import { memoizeGetters } from '../../utils/memoize.js' import { memoizeGetters } from '../../utils/memoize.js'
import type { PeersIndex } from '../peers/peers-index.js' import type { PeersIndex } from '../peers/peers-index.js'
import { User } from '../peers/user.js' import { User } from '../peers/user.js'
import { type Peer, parsePeer } from '../peers/peer.js'
import type { ReactionEmoji } from './types.js' import type { ReactionEmoji } from './types.js'
import { toReactionEmoji } from './types.js' import { toReactionEmoji } from './types.js'
@ -59,3 +60,41 @@ export class PeerReaction {
memoizeGetters(PeerReaction, ['user']) memoizeGetters(PeerReaction, ['user'])
makeInspectable(PeerReaction) makeInspectable(PeerReaction)
/**
* Information about paid reactions of a single user to a message,
* currently only used for a per-post leaderboard in the app.
*/
export class PaidPeerReaction {
constructor(
readonly raw: tl.RawMessageReactor,
readonly _peers: PeersIndex,
) {}
/** Whether this reaction is from the current user */
get my(): boolean {
return this.raw.my!
}
/** Whether this reaction was sent anonymously */
get anonymous(): boolean {
return this.raw.anonymous!
}
/**
* If this reaction was not sent anonymously,
* this field will contain the user who sent it
*/
get peer(): Peer | null {
if (!this.raw.peerId) return null
return parsePeer(this.raw.peerId, this._peers)
}
/** Number of reactions sent by this user */
get count(): number {
return this.raw.count
}
}
memoizeGetters(PaidPeerReaction, ['peer'])
makeInspectable(PaidPeerReaction)

View file

@ -18,6 +18,11 @@ export class ReactionCount {
return toReactionEmoji(this.raw.reaction) return toReactionEmoji(this.raw.reaction)
} }
/** Whether this is a paid reaction */
get isPaid(): boolean {
return this.raw.reaction._ === 'reactionPaid'
}
/** /**
* Number of users who reacted with this emoji * Number of users who reacted with this emoji
*/ */

View file

@ -12,11 +12,19 @@ export type InputReaction = string | tl.Long | tl.TypeReaction
* Emoji describing a reaction. * Emoji describing a reaction.
* *
* Either a `string` with a unicode emoji, or a `tl.Long` for a custom emoji * Either a `string` with a unicode emoji, or a `tl.Long` for a custom emoji
*
* For paid reactions, will always contain `` emoji
*/ */
export type ReactionEmoji = string | tl.Long export type ReactionEmoji = string | tl.Long
export function normalizeInputReaction(reaction?: InputReaction | null): tl.TypeReaction { export function normalizeInputReaction(reaction?: InputReaction | null): tl.TypeReaction {
if (typeof reaction === 'string') { if (typeof reaction === 'string') {
if (reaction === 'paid') {
return {
_: 'reactionPaid',
}
}
return { return {
_: 'reactionEmoji', _: 'reactionEmoji',
emoticon: reaction, emoticon: reaction,
@ -44,6 +52,8 @@ export function toReactionEmoji(reaction: tl.TypeReaction, allowEmpty?: boolean)
return reaction.emoticon return reaction.emoticon
case 'reactionCustomEmoji': case 'reactionCustomEmoji':
return reaction.documentId return reaction.documentId
case 'reactionPaid':
return '⭐'
case 'reactionEmpty': case 'reactionEmpty':
if (!allowEmpty) { if (!allowEmpty) {
throw new MtTypeAssertionError('toReactionEmoji', 'not reactionEmpty', reaction._) throw new MtTypeAssertionError('toReactionEmoji', 'not reactionEmpty', reaction._)

View file

@ -45,6 +45,7 @@ export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> imp
readonly computeNewPasswordHash: ITelegramClient['computeNewPasswordHash'] readonly computeNewPasswordHash: ITelegramClient['computeNewPasswordHash']
readonly startUpdatesLoop: ITelegramClient['startUpdatesLoop'] readonly startUpdatesLoop: ITelegramClient['startUpdatesLoop']
readonly stopUpdatesLoop: ITelegramClient['stopUpdatesLoop'] readonly stopUpdatesLoop: ITelegramClient['stopUpdatesLoop']
readonly getMtprotoMessageId: ITelegramClient['getMtprotoMessageId']
private _abortController = new AbortController() private _abortController = new AbortController()
readonly stopSignal: AbortSignal = this._abortController.signal readonly stopSignal: AbortSignal = this._abortController.signal
@ -79,6 +80,7 @@ export abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> imp
this.computeNewPasswordHash = bind('computeNewPasswordHash') this.computeNewPasswordHash = bind('computeNewPasswordHash')
this.startUpdatesLoop = bind('startUpdatesLoop') this.startUpdatesLoop = bind('startUpdatesLoop')
this.stopUpdatesLoop = bind('stopUpdatesLoop') this.stopUpdatesLoop = bind('stopUpdatesLoop')
this.getMtprotoMessageId = bind('getMtprotoMessageId')
} }
call<T extends tl.RpcMethod>( call<T extends tl.RpcMethod>(

View file

@ -1,5 +1,6 @@
import type { mtp, tl } from '@mtcute/tl' import type { mtp, tl } from '@mtcute/tl'
import type { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime' import type { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
import type Long from 'long'
import { getPlatform } from '../platform.js' import { getPlatform } from '../platform.js'
import type { StorageManager } from '../storage/storage.js' import type { StorageManager } from '../storage/storage.js'
@ -896,4 +897,8 @@ export class NetworkManager {
this.config.offReload(this._onConfigChanged) this.config.offReload(this._onConfigChanged)
this._resetOnNetworkChange?.() this._resetOnNetworkChange?.()
} }
getMtprotoMessageId(): Long {
return this._primaryDc!.main._sessions[0].getMessageId()
}
} }

View file

@ -2,7 +2,7 @@
TL schema and related utils used for mtcute. TL schema and related utils used for mtcute.
Generated from TL layer **185** (last updated on 07.08.2024). Generated from TL layer **186** (last updated on 18.08.2024).
## About ## About

File diff suppressed because one or more lines are too long

View file

@ -1,37 +1,37 @@
{ {
"name": "@mtcute/tl", "name": "@mtcute/tl",
"version": "185.1.0", "version": "186.0.0",
"description": "TL schema used for mtcute", "description": "TL schema used for mtcute",
"author": "alina sireneva <alina@tei.su>", "author": "alina sireneva <alina@tei.su>",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"test": "tsc --noEmit --esModuleInterop tests/types.ts", "test": "tsc --noEmit --esModuleInterop tests/types.ts",
"fetch-mtp": "tsx scripts/fetch-mtp.ts", "fetch-mtp": "tsx scripts/fetch-mtp.ts",
"fetch-api": "tsx scripts/fetch-api.ts", "fetch-api": "tsx scripts/fetch-api.ts",
"fetch-errors": "tsx scripts/fetch-errors.ts", "fetch-errors": "tsx scripts/fetch-errors.ts",
"docs-cli": "tsx scripts/documentation.ts", "docs-cli": "tsx scripts/documentation.ts",
"gen-code": "tsx scripts/gen-code.ts", "gen-code": "tsx scripts/gen-code.ts",
"gen-rsa": "tsx scripts/gen-rsa-keys.ts", "gen-rsa": "tsx scripts/gen-rsa-keys.ts",
"fetch-and-gen": "pnpm run fetch-api && pnpm run gen-code", "fetch-and-gen": "pnpm run fetch-api && pnpm run gen-code",
"build": "pnpm run -w build-package tl" "build": "pnpm run -w build-package tl"
}, },
"dependencies": { "dependencies": {
"long": "5.2.3" "long": "5.2.3"
}, },
"devDependencies": { "devDependencies": {
"@mtcute/core": "workspace:^", "@mtcute/core": "workspace:^",
"@mtcute/node": "workspace:^", "@mtcute/node": "workspace:^",
"@mtcute/tl-utils": "workspace:^", "@mtcute/tl-utils": "workspace:^",
"@types/js-yaml": "^4.0.5", "@types/js-yaml": "^4.0.5",
"cheerio": "1.0.0-rc.12", "cheerio": "1.0.0-rc.12",
"csv-parse": "^5.5.0", "csv-parse": "^5.5.0",
"eager-async-pool": "^1.0.0", "eager-async-pool": "^1.0.0",
"js-yaml": "4.1.0" "js-yaml": "4.1.0"
}, },
"typedoc": { "typedoc": {
"entryPoint": "index.d.ts" "entryPoint": "index.d.ts"
}, },
"jsrOnlyFields": { "jsrOnlyFields": {
"exports": {} "exports": {}
} }
} }