feat(core): support Telegram Business and some other new features
This commit is contained in:
parent
a414ea9425
commit
756b99e12f
21 changed files with 1124 additions and 33 deletions
|
@ -177,10 +177,15 @@ import { cancelPasswordEmail, resendPasswordEmail, verifyPasswordEmail } from '.
|
|||
import { removeCloudPassword } from './methods/password/remove-cloud-password.js'
|
||||
import { applyBoost } from './methods/premium/apply-boost.js'
|
||||
import { canApplyBoost, CanApplyBoostResult } from './methods/premium/can-apply-boost.js'
|
||||
import { createBusinessChatLink } from './methods/premium/create-business-chat-link.js'
|
||||
import { deleteBusinessChatLink, editBusinessChatLink } from './methods/premium/edit-business-chat-link.js'
|
||||
import { getBoostStats } from './methods/premium/get-boost-stats.js'
|
||||
import { getBoosts } from './methods/premium/get-boosts.js'
|
||||
import { getBusinessChatLinks } from './methods/premium/get-business-chat-links.js'
|
||||
import { getMyBoostSlots } from './methods/premium/get-my-boost-slots.js'
|
||||
import { iterBoosters } from './methods/premium/iter-boosters.js'
|
||||
import { setBusinessIntro } from './methods/premium/set-business-intro.js'
|
||||
import { setBusinessWorkHours } from './methods/premium/set-business-work-hours.js'
|
||||
import { addStickerToSet } from './methods/stickers/add-sticker-to-set.js'
|
||||
import { createStickerSet } from './methods/stickers/create-sticker-set.js'
|
||||
import { deleteStickerFromSet } from './methods/stickers/delete-sticker-from-set.js'
|
||||
|
@ -225,6 +230,7 @@ import { iterProfilePhotos } from './methods/users/iter-profile-photos.js'
|
|||
import { resolveChannel, resolvePeer, resolveUser } from './methods/users/resolve-peer.js'
|
||||
import { resolvePeerMany } from './methods/users/resolve-peer-many.js'
|
||||
import { setGlobalTtl } from './methods/users/set-global-ttl.js'
|
||||
import { setMyBirthday } from './methods/users/set-my-birthday.js'
|
||||
import { setMyEmojiStatus } from './methods/users/set-my-emoji-status.js'
|
||||
import { setMyProfilePhoto } from './methods/users/set-my-profile-photo.js'
|
||||
import { setMyUsername } from './methods/users/set-my-username.js'
|
||||
|
@ -245,6 +251,8 @@ import {
|
|||
BotReactionCountUpdate,
|
||||
BotReactionUpdate,
|
||||
BotStoppedUpdate,
|
||||
BusinessChatLink,
|
||||
BusinessWorkHoursDay,
|
||||
CallbackQuery,
|
||||
Chat,
|
||||
ChatEvent,
|
||||
|
@ -271,6 +279,7 @@ import {
|
|||
InputFileLike,
|
||||
InputInlineResult,
|
||||
InputMediaLike,
|
||||
InputMediaSticker,
|
||||
InputMessageId,
|
||||
InputPeerLike,
|
||||
InputPrivacyRule,
|
||||
|
@ -4161,6 +4170,47 @@ export interface TelegramClient extends ITelegramClient {
|
|||
*/
|
||||
canApplyBoost(): Promise<CanApplyBoostResult>
|
||||
|
||||
/**
|
||||
* Create a new business chat link
|
||||
*
|
||||
* **Available**: 👤 users only
|
||||
*
|
||||
* @param text Text to be inserted into the message input
|
||||
*/
|
||||
createBusinessChatLink(
|
||||
text: InputText,
|
||||
params?: {
|
||||
/** Custom title for the link */
|
||||
title?: string
|
||||
},
|
||||
): Promise<BusinessChatLink>
|
||||
|
||||
/**
|
||||
* Edit an existing business chat link
|
||||
*
|
||||
* **Available**: 👤 users only
|
||||
*
|
||||
* @param link The link to edit
|
||||
*/
|
||||
editBusinessChatLink(
|
||||
link: string | BusinessChatLink,
|
||||
params: {
|
||||
/** Text to be inserted in the message input */
|
||||
text: InputText
|
||||
/** Custom title for the link */
|
||||
title?: string
|
||||
},
|
||||
): Promise<BusinessChatLink>
|
||||
|
||||
/**
|
||||
* Delete a business chat link
|
||||
*
|
||||
* **Available**: 👤 users only
|
||||
*
|
||||
* @param link The link to delete
|
||||
*/
|
||||
deleteBusinessChatLink(link: string | BusinessChatLink): Promise<void>
|
||||
|
||||
/**
|
||||
* Get information about boosts in a channel
|
||||
*
|
||||
|
@ -4190,6 +4240,13 @@ export interface TelegramClient extends ITelegramClient {
|
|||
limit?: number
|
||||
},
|
||||
): Promise<ArrayPaginated<Boost, string>>
|
||||
|
||||
/**
|
||||
* Get current user's business chat links
|
||||
* **Available**: 👤 users only
|
||||
*
|
||||
*/
|
||||
getBusinessChatLinks(): Promise<BusinessChatLink[]>
|
||||
/**
|
||||
* Get boost slots information of the current user.
|
||||
*
|
||||
|
@ -4227,6 +4284,57 @@ export interface TelegramClient extends ITelegramClient {
|
|||
chunkSize?: number
|
||||
},
|
||||
): AsyncIterableIterator<Boost>
|
||||
|
||||
/**
|
||||
* Set current user's business introduction.
|
||||
*
|
||||
* **Available**: 👤 users only
|
||||
*
|
||||
* @param intro Introduction parameters, or `null` to remove
|
||||
*/
|
||||
setBusinessIntro(
|
||||
intro: {
|
||||
/**
|
||||
* Title of the introduction
|
||||
*/
|
||||
title?: string
|
||||
|
||||
/**
|
||||
* Description of the introduction
|
||||
*/
|
||||
description?: string
|
||||
|
||||
/**
|
||||
* Sticker to show beneath the introduction
|
||||
*/
|
||||
sticker?: InputMediaSticker | InputFileLike | tl.TypeInputDocument
|
||||
} | null,
|
||||
): Promise<void>
|
||||
|
||||
/**
|
||||
* Set current user's business work hours.
|
||||
* **Available**: 👤 users only
|
||||
*
|
||||
*/
|
||||
setBusinessWorkHours(
|
||||
params:
|
||||
| ({
|
||||
/** Timezone in which the hours are defined */
|
||||
timezone: string
|
||||
} & (
|
||||
| {
|
||||
/**
|
||||
* Business work intervals, per-day (like available in {@link BusinessWorkHours.days})
|
||||
*/
|
||||
hours: ReadonlyArray<BusinessWorkHoursDay>
|
||||
}
|
||||
| {
|
||||
/** Business work intervals, raw intervals */
|
||||
intervals: tl.TypeBusinessWeeklyOpen[]
|
||||
}
|
||||
))
|
||||
| null,
|
||||
): Promise<void>
|
||||
/**
|
||||
* Add a sticker to a sticker set.
|
||||
*
|
||||
|
@ -5062,6 +5170,22 @@ export interface TelegramClient extends ITelegramClient {
|
|||
* @param period New TTL period, in seconds (or 0 to disable)
|
||||
*/
|
||||
setGlobalTtl(period: number): Promise<void>
|
||||
|
||||
/**
|
||||
* Set or remove current user's birthday.
|
||||
* **Available**: 👤 users only
|
||||
*
|
||||
*/
|
||||
setMyBirthday(
|
||||
birthday: {
|
||||
/** Birthday day */
|
||||
day: number
|
||||
/** Birthday month */
|
||||
month: number
|
||||
/** Birthday year (optional) */
|
||||
year?: number
|
||||
} | null,
|
||||
): Promise<void>
|
||||
/**
|
||||
* Set an emoji status for the current user
|
||||
*
|
||||
|
@ -5742,18 +5866,36 @@ TelegramClient.prototype.applyBoost = function (...args) {
|
|||
TelegramClient.prototype.canApplyBoost = function (...args) {
|
||||
return canApplyBoost(this._client, ...args)
|
||||
}
|
||||
TelegramClient.prototype.createBusinessChatLink = function (...args) {
|
||||
return createBusinessChatLink(this._client, ...args)
|
||||
}
|
||||
TelegramClient.prototype.editBusinessChatLink = function (...args) {
|
||||
return editBusinessChatLink(this._client, ...args)
|
||||
}
|
||||
TelegramClient.prototype.deleteBusinessChatLink = function (...args) {
|
||||
return deleteBusinessChatLink(this._client, ...args)
|
||||
}
|
||||
TelegramClient.prototype.getBoostStats = function (...args) {
|
||||
return getBoostStats(this._client, ...args)
|
||||
}
|
||||
TelegramClient.prototype.getBoosts = function (...args) {
|
||||
return getBoosts(this._client, ...args)
|
||||
}
|
||||
TelegramClient.prototype.getBusinessChatLinks = function (...args) {
|
||||
return getBusinessChatLinks(this._client, ...args)
|
||||
}
|
||||
TelegramClient.prototype.getMyBoostSlots = function (...args) {
|
||||
return getMyBoostSlots(this._client, ...args)
|
||||
}
|
||||
TelegramClient.prototype.iterBoosters = function (...args) {
|
||||
return iterBoosters(this._client, ...args)
|
||||
}
|
||||
TelegramClient.prototype.setBusinessIntro = function (...args) {
|
||||
return setBusinessIntro(this._client, ...args)
|
||||
}
|
||||
TelegramClient.prototype.setBusinessWorkHours = function (...args) {
|
||||
return setBusinessWorkHours(this._client, ...args)
|
||||
}
|
||||
TelegramClient.prototype.addStickerToSet = function (...args) {
|
||||
return addStickerToSet(this._client, ...args)
|
||||
}
|
||||
|
@ -5900,6 +6042,9 @@ TelegramClient.prototype.resolveChannel = function (...args) {
|
|||
TelegramClient.prototype.setGlobalTtl = function (...args) {
|
||||
return setGlobalTtl(this._client, ...args)
|
||||
}
|
||||
TelegramClient.prototype.setMyBirthday = function (...args) {
|
||||
return setMyBirthday(this._client, ...args)
|
||||
}
|
||||
TelegramClient.prototype.setMyEmojiStatus = function (...args) {
|
||||
return setMyEmojiStatus(this._client, ...args)
|
||||
}
|
||||
|
|
|
@ -192,10 +192,16 @@ export { removeCloudPassword } from './methods/password/remove-cloud-password.js
|
|||
export { applyBoost } from './methods/premium/apply-boost.js'
|
||||
export type { CanApplyBoostResult } from './methods/premium/can-apply-boost.js'
|
||||
export { canApplyBoost } from './methods/premium/can-apply-boost.js'
|
||||
export { createBusinessChatLink } from './methods/premium/create-business-chat-link.js'
|
||||
export { editBusinessChatLink } from './methods/premium/edit-business-chat-link.js'
|
||||
export { deleteBusinessChatLink } from './methods/premium/edit-business-chat-link.js'
|
||||
export { getBoostStats } from './methods/premium/get-boost-stats.js'
|
||||
export { getBoosts } from './methods/premium/get-boosts.js'
|
||||
export { getBusinessChatLinks } from './methods/premium/get-business-chat-links.js'
|
||||
export { getMyBoostSlots } from './methods/premium/get-my-boost-slots.js'
|
||||
export { iterBoosters } from './methods/premium/iter-boosters.js'
|
||||
export { setBusinessIntro } from './methods/premium/set-business-intro.js'
|
||||
export { setBusinessWorkHours } from './methods/premium/set-business-work-hours.js'
|
||||
export { addStickerToSet } from './methods/stickers/add-sticker-to-set.js'
|
||||
export { createStickerSet } from './methods/stickers/create-sticker-set.js'
|
||||
export { deleteStickerFromSet } from './methods/stickers/delete-sticker-from-set.js'
|
||||
|
@ -245,6 +251,7 @@ export { resolveUser } from './methods/users/resolve-peer.js'
|
|||
export { resolveChannel } from './methods/users/resolve-peer.js'
|
||||
export { resolvePeerMany } from './methods/users/resolve-peer-many.js'
|
||||
export { setGlobalTtl } from './methods/users/set-global-ttl.js'
|
||||
export { setMyBirthday } from './methods/users/set-my-birthday.js'
|
||||
export { setMyEmojiStatus } from './methods/users/set-my-emoji-status.js'
|
||||
export { setMyProfilePhoto } from './methods/users/set-my-profile-photo.js'
|
||||
export { setMyUsername } from './methods/users/set-my-username.js'
|
||||
|
|
|
@ -26,6 +26,8 @@ import {
|
|||
BotReactionCountUpdate,
|
||||
BotReactionUpdate,
|
||||
BotStoppedUpdate,
|
||||
BusinessChatLink,
|
||||
BusinessWorkHoursDay,
|
||||
CallbackQuery,
|
||||
Chat,
|
||||
ChatEvent,
|
||||
|
@ -52,6 +54,7 @@ import {
|
|||
InputFileLike,
|
||||
InputInlineResult,
|
||||
InputMediaLike,
|
||||
InputMediaSticker,
|
||||
InputMessageId,
|
||||
InputPeerLike,
|
||||
InputPrivacyRule,
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import { ITelegramClient } from '../../client.types.js'
|
||||
import { InputText } from '../../types/index.js'
|
||||
import { BusinessChatLink } from '../../types/premium/business-chat-link.js'
|
||||
import { _normalizeInputText } from '../misc/normalize-text.js'
|
||||
|
||||
// @available=user
|
||||
/**
|
||||
* Create a new business chat link
|
||||
*
|
||||
* @param text Text to be inserted into the message input
|
||||
*/
|
||||
export async function createBusinessChatLink(
|
||||
client: ITelegramClient,
|
||||
text: InputText,
|
||||
params?: {
|
||||
/** Custom title for the link */
|
||||
title?: string
|
||||
},
|
||||
): Promise<BusinessChatLink> {
|
||||
const [message, entities] = await _normalizeInputText(client, text)
|
||||
const res = await client.call({
|
||||
_: 'account.createBusinessChatLink',
|
||||
link: {
|
||||
_: 'inputBusinessChatLink',
|
||||
message,
|
||||
entities,
|
||||
title: params?.title,
|
||||
},
|
||||
})
|
||||
|
||||
return new BusinessChatLink(res)
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import { assertTrue } from '../../../utils/type-assertions.js'
|
||||
import { ITelegramClient } from '../../client.types.js'
|
||||
import { InputText } from '../../types/index.js'
|
||||
import { BusinessChatLink } from '../../types/premium/business-chat-link.js'
|
||||
import { _normalizeInputText } from '../misc/normalize-text.js'
|
||||
|
||||
// @available=user
|
||||
/**
|
||||
* Edit an existing business chat link
|
||||
*
|
||||
* @param link The link to edit
|
||||
*/
|
||||
export async function editBusinessChatLink(
|
||||
client: ITelegramClient,
|
||||
link: string | BusinessChatLink,
|
||||
params: {
|
||||
/** Text to be inserted in the message input */
|
||||
text: InputText
|
||||
/** Custom title for the link */
|
||||
title?: string
|
||||
},
|
||||
): Promise<BusinessChatLink> {
|
||||
const [message, entities] = await _normalizeInputText(client, params.text)
|
||||
const res = await client.call({
|
||||
_: 'account.editBusinessChatLink',
|
||||
slug: link instanceof BusinessChatLink ? link.link : link,
|
||||
link: {
|
||||
_: 'inputBusinessChatLink',
|
||||
message,
|
||||
entities,
|
||||
title: params?.title,
|
||||
},
|
||||
})
|
||||
|
||||
return new BusinessChatLink(res)
|
||||
}
|
||||
|
||||
// @available=user
|
||||
/**
|
||||
* Delete a business chat link
|
||||
*
|
||||
* @param link The link to delete
|
||||
*/
|
||||
export async function deleteBusinessChatLink(client: ITelegramClient, link: string | BusinessChatLink): Promise<void> {
|
||||
const res = await client.call({
|
||||
_: 'account.deleteBusinessChatLink',
|
||||
slug: typeof link === 'string' ? link : link.link,
|
||||
})
|
||||
|
||||
assertTrue('account.deleteBusinessChatLink', res)
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { ITelegramClient } from '../../client.types.js'
|
||||
import { BusinessChatLink } from '../../types/premium/business-chat-link.js'
|
||||
|
||||
// @available=user
|
||||
/**
|
||||
* Get current user's business chat links
|
||||
*/
|
||||
export async function getBusinessChatLinks(client: ITelegramClient): Promise<BusinessChatLink[]> {
|
||||
const res = await client.call({ _: 'account.getBusinessChatLinks' })
|
||||
|
||||
return res.links.map((x) => new BusinessChatLink(x))
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
|
||||
import { assertTrue, assertTypeIs } from '../../../utils/type-assertions.js'
|
||||
import { ITelegramClient } from '../../client.types.js'
|
||||
import { InputFileLike, InputMediaSticker } from '../../types/index.js'
|
||||
import { _normalizeFileToDocument } from '../files/normalize-file-to-document.js'
|
||||
import { _normalizeInputMedia } from '../files/normalize-input-media.js'
|
||||
|
||||
const isInputMediaSticker = (media: unknown): media is InputMediaSticker =>
|
||||
typeof media === 'object' && media !== null && 'type' in media && media.type === 'sticker'
|
||||
|
||||
// @available=user
|
||||
/**
|
||||
* Set current user's business introduction.
|
||||
*
|
||||
* @param intro Introduction parameters, or `null` to remove
|
||||
*/
|
||||
export async function setBusinessIntro(
|
||||
client: ITelegramClient,
|
||||
intro: {
|
||||
/**
|
||||
* Title of the introduction
|
||||
*/
|
||||
title?: string
|
||||
|
||||
/**
|
||||
* Description of the introduction
|
||||
*/
|
||||
description?: string
|
||||
|
||||
/**
|
||||
* Sticker to show beneath the introduction
|
||||
*/
|
||||
sticker?: InputMediaSticker | InputFileLike | tl.TypeInputDocument
|
||||
} | null,
|
||||
): Promise<void> {
|
||||
let tlIntro: tl.TypeInputBusinessIntro | undefined = undefined
|
||||
|
||||
if (intro) {
|
||||
let sticker: tl.TypeInputDocument | undefined
|
||||
|
||||
if (intro.sticker) {
|
||||
if (isInputMediaSticker(intro.sticker)) {
|
||||
const media = await _normalizeInputMedia(client, intro.sticker, undefined, true)
|
||||
|
||||
assertTypeIs('_normalizeInputMedia', media, 'inputMediaDocument')
|
||||
sticker = media.id
|
||||
} else {
|
||||
sticker = await _normalizeFileToDocument(client, intro.sticker, {})
|
||||
}
|
||||
}
|
||||
|
||||
tlIntro = {
|
||||
_: 'inputBusinessIntro',
|
||||
title: intro.title ?? '',
|
||||
description: intro.description ?? '',
|
||||
sticker,
|
||||
}
|
||||
}
|
||||
|
||||
const res = await client.call({
|
||||
_: 'account.updateBusinessIntro',
|
||||
intro: tlIntro,
|
||||
})
|
||||
|
||||
assertTrue('account.updateBusinessIntro', res)
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
|
||||
import { assertTrue } from '../../../utils/type-assertions.js'
|
||||
import { ITelegramClient } from '../../client.types.js'
|
||||
import { BusinessWorkHoursDay, businessWorkHoursDaysToRaw } from '../../types/premium/business-work-hours.js'
|
||||
|
||||
// @available=user
|
||||
/**
|
||||
* Set current user's business work hours.
|
||||
*/
|
||||
export async function setBusinessWorkHours(
|
||||
client: ITelegramClient,
|
||||
params:
|
||||
| ({
|
||||
/** Timezone in which the hours are defined */
|
||||
timezone: string
|
||||
} & (
|
||||
| {
|
||||
/**
|
||||
* Business work intervals, per-day (like available in {@link BusinessWorkHours.days})
|
||||
*/
|
||||
hours: ReadonlyArray<BusinessWorkHoursDay>
|
||||
}
|
||||
| {
|
||||
/** Business work intervals, raw intervals */
|
||||
intervals: tl.TypeBusinessWeeklyOpen[]
|
||||
}
|
||||
))
|
||||
| null,
|
||||
): Promise<void> {
|
||||
let businessWorkHours: tl.TypeBusinessWorkHours | undefined = undefined
|
||||
|
||||
if (params) {
|
||||
let weeklyOpen: tl.TypeBusinessWeeklyOpen[]
|
||||
|
||||
if ('hours' in params) {
|
||||
weeklyOpen = businessWorkHoursDaysToRaw(params.hours)
|
||||
} else {
|
||||
weeklyOpen = params.intervals
|
||||
}
|
||||
|
||||
businessWorkHours = {
|
||||
_: 'businessWorkHours',
|
||||
timezoneId: params.timezone,
|
||||
weeklyOpen,
|
||||
}
|
||||
}
|
||||
|
||||
const res = await client.call({
|
||||
_: 'account.updateBusinessWorkHours',
|
||||
businessWorkHours,
|
||||
})
|
||||
|
||||
assertTrue('account.updateBusinessWorkHours', res)
|
||||
}
|
30
packages/core/src/highlevel/methods/users/set-my-birthday.ts
Normal file
30
packages/core/src/highlevel/methods/users/set-my-birthday.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { assertTrue } from '../../../utils/type-assertions.js'
|
||||
import { ITelegramClient } from '../../client.types.js'
|
||||
|
||||
// @available=user
|
||||
/**
|
||||
* Set or remove current user's birthday.
|
||||
*/
|
||||
export async function setMyBirthday(
|
||||
client: ITelegramClient,
|
||||
birthday: {
|
||||
/** Birthday day */
|
||||
day: number
|
||||
/** Birthday month */
|
||||
month: number
|
||||
/** Birthday year (optional) */
|
||||
year?: number
|
||||
} | null,
|
||||
): Promise<void> {
|
||||
const res = await client.call({
|
||||
_: 'account.updateBirthday',
|
||||
birthday: birthday ?
|
||||
{
|
||||
_: 'birthday',
|
||||
...birthday,
|
||||
} :
|
||||
undefined,
|
||||
})
|
||||
|
||||
assertTrue('account.updateBirthday', res)
|
||||
}
|
|
@ -372,7 +372,24 @@ export interface ActionPhotoSuggested {
|
|||
photo: Photo
|
||||
}
|
||||
|
||||
/** A peer was chosen by the user after clicking on a RequestPeer button */
|
||||
/**
|
||||
* A peer was chosen by the user after clicking on a RequestPeer button.
|
||||
* The user-side version of {@link ActionPeerChosen}
|
||||
*/
|
||||
export interface ActionPeerSent {
|
||||
readonly type: 'peer_sent'
|
||||
|
||||
/** ID of the button passed earlier by the bot */
|
||||
buttonId: number
|
||||
|
||||
/** Brief information about the chosen peers */
|
||||
peers: tl.TypeRequestedPeer[]
|
||||
}
|
||||
|
||||
/**
|
||||
* A peer was chosen by the user after clicking on a RequestPeer button
|
||||
* The bot-side version of {@link ActionPeerSent}
|
||||
*/
|
||||
export interface ActionPeerChosen {
|
||||
readonly type: 'peer_chosen'
|
||||
|
||||
|
@ -472,6 +489,7 @@ export type MessageAction =
|
|||
| ActionWebviewDataReceived
|
||||
| ActionPremiumGifted
|
||||
| ActionPhotoSuggested
|
||||
| ActionPeerSent
|
||||
| ActionPeerChosen
|
||||
| ActionWallpaperChanged
|
||||
| ActionGiftCode
|
||||
|
@ -696,6 +714,12 @@ export function _messageActionFromTl(this: Message, act: tl.TypeMessageAction):
|
|||
type: 'photo_suggested',
|
||||
photo: new Photo(act.photo as tl.RawPhoto),
|
||||
}
|
||||
case 'messageActionRequestedPeerSentMe':
|
||||
return {
|
||||
type: 'peer_sent',
|
||||
buttonId: act.buttonId,
|
||||
peers: act.peers,
|
||||
}
|
||||
case 'messageActionRequestedPeer':
|
||||
return {
|
||||
type: 'peer_chosen',
|
||||
|
|
|
@ -79,6 +79,14 @@ export class Message {
|
|||
return this.raw._ === 'message' && this.raw.noforwards!
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the message was sent by an implicit action, for example,
|
||||
* as an away or a greeting business message, or as a scheduled message
|
||||
*/
|
||||
get isFromOffline(): boolean {
|
||||
return this.raw._ === 'message' && this.raw.offline!
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiple media messages with the same grouped ID
|
||||
* indicate an album or media group
|
||||
|
@ -126,6 +134,15 @@ export class Message {
|
|||
return parsePeer(from, this._peers)
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of boosts applied to this {@link chat} by the sender
|
||||
*/
|
||||
get senderBoostCount(): number {
|
||||
if (this.raw._ !== 'message') return 0
|
||||
|
||||
return this.raw.fromBoostsApplied ?? 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversation the message belongs to
|
||||
*/
|
||||
|
|
|
@ -138,6 +138,13 @@ export class StickerSet {
|
|||
return this.brief.official!
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this sticker set was created by the current user
|
||||
*/
|
||||
get isCreator(): boolean {
|
||||
return this.brief.creator!
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of the stickers in this set
|
||||
*/
|
||||
|
|
|
@ -4,8 +4,12 @@ import { MtTypeAssertionError } from '../../../types/errors.js'
|
|||
import { makeInspectable } from '../../utils/inspectable.js'
|
||||
import { memoizeGetters } from '../../utils/memoize.js'
|
||||
import { Photo } from '../media/photo.js'
|
||||
import { StickerSet } from '../misc/sticker-set.js'
|
||||
import { BusinessAccount } from '../premium/business-account.js'
|
||||
import { Chat } from './chat.js'
|
||||
import { ChatInviteLink } from './chat-invite-link.js'
|
||||
import { ChatLocation } from './chat-location.js'
|
||||
import { PeersIndex } from './peers-index.js'
|
||||
|
||||
/**
|
||||
* Complete information about a particular chat.
|
||||
|
@ -20,31 +24,32 @@ export class FullChat extends Chat {
|
|||
|
||||
/** @internal */
|
||||
static _parse(full: tl.messages.RawChatFull | tl.users.TypeUserFull): FullChat {
|
||||
const peers = PeersIndex.from(full)
|
||||
|
||||
if (full._ === 'users.userFull') {
|
||||
const user = full.users.find((it) => it.id === full.fullUser.id)
|
||||
const { fullUser } = full
|
||||
const user = peers.user(full.fullUser.id)
|
||||
|
||||
if (!user || user._ === 'userEmpty') {
|
||||
throw new MtTypeAssertionError('Chat._parseFull', 'user', user?._ ?? 'undefined')
|
||||
}
|
||||
|
||||
return new FullChat(user, full.fullUser)
|
||||
const ret = new FullChat(user, fullUser)
|
||||
|
||||
if (fullUser.personalChannelId) {
|
||||
ret._linkedChat = new Chat(peers.chat(fullUser.personalChannelId))
|
||||
}
|
||||
|
||||
const fullChat = full.fullChat
|
||||
let chat: tl.TypeChat | undefined = undefined
|
||||
let linked: tl.TypeChat | undefined = undefined
|
||||
|
||||
for (const c of full.chats) {
|
||||
if (fullChat.id === c.id) {
|
||||
chat = c
|
||||
}
|
||||
if (fullChat._ === 'channelFull' && fullChat.linkedChatId === c.id) {
|
||||
linked = c
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
const ret = new FullChat(chat!, fullChat)
|
||||
ret._linkedChat = linked ? new Chat(linked) : undefined
|
||||
const { fullChat } = full
|
||||
|
||||
const ret = new FullChat(peers.chat(fullChat.id), fullChat)
|
||||
|
||||
if (fullChat._ === 'channelFull' && fullChat.linkedChatId) {
|
||||
ret._linkedChat = new Chat(peers.chat(fullChat.linkedChatId))
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
@ -132,15 +137,15 @@ export class FullChat extends Chat {
|
|||
}
|
||||
|
||||
/**
|
||||
* Chat's permanent invite link, for groups, supergroups and channels.
|
||||
* Chat's primary invite link, for groups, supergroups and channels.
|
||||
*/
|
||||
get inviteLink(): string | null {
|
||||
get inviteLink(): ChatInviteLink | null {
|
||||
if (this.fullPeer && this.fullPeer._ !== 'userFull') {
|
||||
switch (this.fullPeer.exportedInvite?._) {
|
||||
case 'chatInvitePublicJoinRequests':
|
||||
return null
|
||||
case 'chatInviteExported':
|
||||
return this.fullPeer.exportedInvite.link
|
||||
return new ChatInviteLink(this.fullPeer.exportedInvite)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,10 +153,21 @@ export class FullChat extends Chat {
|
|||
}
|
||||
|
||||
/**
|
||||
* For supergroups, name of the group sticker set.
|
||||
* For supergroups, information about the group sticker set.
|
||||
*/
|
||||
get stickerSetName(): string | null {
|
||||
return this.fullPeer && this.fullPeer._ === 'channelFull' ? this.fullPeer.stickerset?.shortName ?? null : null
|
||||
get stickerSet(): StickerSet | null {
|
||||
if (this.fullPeer?._ !== 'channelFull' || !this.fullPeer.stickerset) return null
|
||||
|
||||
return new StickerSet(this.fullPeer.stickerset)
|
||||
}
|
||||
|
||||
/**
|
||||
* For supergroups, information about the group emoji set.
|
||||
*/
|
||||
get emojiSet(): StickerSet | null {
|
||||
if (this.fullPeer?._ !== 'channelFull' || !this.fullPeer.emojiset) return null
|
||||
|
||||
return new StickerSet(this.fullPeer.emojiset)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -161,19 +177,40 @@ export class FullChat extends Chat {
|
|||
return this.fullPeer && this.fullPeer._ === 'channelFull' ? this.fullPeer.canSetStickers ?? null : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of boosts applied by the current user to this chat.
|
||||
*/
|
||||
get boostsApplied(): number {
|
||||
if (!this.fullPeer || this.fullPeer._ !== 'channelFull') return 0
|
||||
|
||||
return this.fullPeer?.boostsApplied ?? 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of boosts required for the user to be unrestricted in this chat.
|
||||
*/
|
||||
get boostsForUnrestrict(): number {
|
||||
if (!this.fullPeer || this.fullPeer._ !== 'channelFull') return 0
|
||||
|
||||
return this.fullPeer?.boostsUnrestrict ?? 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat members count, for groups, supergroups and channels only.
|
||||
*/
|
||||
get membersCount(): number | null {
|
||||
if (this.fullPeer && this.fullPeer._ !== 'userFull') {
|
||||
if (this.fullPeer._ === 'chatFull' && this.fullPeer.participants._ === 'chatParticipants') {
|
||||
return this.fullPeer.participants.participants.length
|
||||
} else if (this.fullPeer._ === 'channelFull') {
|
||||
return this.fullPeer.participantsCount ?? null
|
||||
}
|
||||
switch (this.fullPeer._) {
|
||||
case 'userFull':
|
||||
return null
|
||||
case 'chatFull':
|
||||
if (this.fullPeer.participants._ !== 'chatParticipants') {
|
||||
return null
|
||||
}
|
||||
|
||||
return null
|
||||
return this.fullPeer.participants.participants.length
|
||||
case 'channelFull':
|
||||
return this.fullPeer.participantsCount ?? null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -189,8 +226,10 @@ export class FullChat extends Chat {
|
|||
|
||||
private _linkedChat?: Chat
|
||||
/**
|
||||
* The linked discussion group (in case of channels)
|
||||
* or the linked channel (in case of supergroups).
|
||||
* Information about a linked chat:
|
||||
* - for channels: the discussion group
|
||||
* - for supergroups: the linked channel
|
||||
* - for users: the personal channel
|
||||
*/
|
||||
get linkedChat(): Chat | null {
|
||||
return this._linkedChat ?? null
|
||||
|
@ -202,7 +241,25 @@ export class FullChat extends Chat {
|
|||
get ttlPeriod(): number | null {
|
||||
return this.fullPeer?.ttlPeriod ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is a business account, information about the business.
|
||||
*/
|
||||
get business(): BusinessAccount | null {
|
||||
if (!this.fullPeer || this.fullPeer._ !== 'userFull') return null
|
||||
|
||||
return new BusinessAccount(this.fullPeer)
|
||||
}
|
||||
}
|
||||
|
||||
memoizeGetters(FullChat, ['fullPhoto', 'personalPhoto', 'realPhoto', 'publicPhoto', 'location'])
|
||||
memoizeGetters(FullChat, [
|
||||
'fullPhoto',
|
||||
'personalPhoto',
|
||||
'realPhoto',
|
||||
'publicPhoto',
|
||||
'location',
|
||||
'stickerSet',
|
||||
'emojiSet',
|
||||
'business',
|
||||
])
|
||||
makeInspectable(FullChat)
|
||||
|
|
|
@ -108,6 +108,14 @@ export class User {
|
|||
return this.raw.bot!
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this user is a bot that can be connected to a
|
||||
* Telegram Business account to receive its messages
|
||||
*/
|
||||
get isBusinessBot(): boolean {
|
||||
return this.raw.botBusiness!
|
||||
}
|
||||
|
||||
/** Whether this user is a bot that has access to all messages */
|
||||
get isBotWithHistory(): boolean {
|
||||
return this.raw.botChatHistory!
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
|
||||
import { makeInspectable } from '../../utils/inspectable.js'
|
||||
import { memoizeGetters } from '../../utils/memoize.js'
|
||||
import { BusinessIntro } from './business-intro.js'
|
||||
import { BusinessLocation } from './business-location.js'
|
||||
import { BusinessWorkHours } from './business-work-hours.js'
|
||||
|
||||
/** Information about a business account */
|
||||
export class BusinessAccount {
|
||||
constructor(readonly info: tl.RawUserFull) {}
|
||||
|
||||
/** Introduction of the business account */
|
||||
get intro(): BusinessIntro | null {
|
||||
if (!this.info.businessIntro) return null
|
||||
|
||||
return new BusinessIntro(this.info.businessIntro)
|
||||
}
|
||||
|
||||
/** Work hours of the business */
|
||||
get workHours(): BusinessWorkHours | null {
|
||||
if (!this.info.businessWorkHours) return null
|
||||
|
||||
return new BusinessWorkHours(this.info.businessWorkHours)
|
||||
}
|
||||
|
||||
/** Location of the business */
|
||||
get location(): BusinessLocation | null {
|
||||
if (!this.info.businessLocation) return null
|
||||
|
||||
return new BusinessLocation(this.info.businessLocation)
|
||||
}
|
||||
|
||||
/** Information about a greeting message */
|
||||
get greetingMessage(): tl.TypeBusinessGreetingMessage | null {
|
||||
return this.info.businessGreetingMessage ?? null
|
||||
}
|
||||
|
||||
/** Information about an "away" message */
|
||||
get awayMessage(): tl.TypeBusinessAwayMessage | null {
|
||||
return this.info.businessAwayMessage ?? null
|
||||
}
|
||||
}
|
||||
|
||||
memoizeGetters(BusinessAccount, ['intro', 'workHours', 'location'])
|
||||
makeInspectable(BusinessAccount)
|
|
@ -0,0 +1,40 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
|
||||
import { makeInspectable } from '../../utils/inspectable.js'
|
||||
import { memoizeGetters } from '../../utils/memoize.js'
|
||||
import { MessageEntity } from '../messages/message-entity.js'
|
||||
|
||||
/**
|
||||
* A business chat link, i.e. a link to start a chat with a pre-filled message.
|
||||
*/
|
||||
export class BusinessChatLink {
|
||||
constructor(readonly raw: tl.RawBusinessChatLink) {}
|
||||
|
||||
/** The link itself */
|
||||
get link(): string {
|
||||
return this.raw.link
|
||||
}
|
||||
|
||||
/** Text to be inserted into the message input */
|
||||
get text(): string {
|
||||
return this.raw.message
|
||||
}
|
||||
|
||||
/** Entities for the text */
|
||||
get entities(): MessageEntity[] {
|
||||
return this.raw.entities?.map((x) => new MessageEntity(x)) ?? []
|
||||
}
|
||||
|
||||
/** Custom title for the link */
|
||||
get title(): string | null {
|
||||
return this.raw.title ?? null
|
||||
}
|
||||
|
||||
/** Number of clicks on the link */
|
||||
get clicks(): number {
|
||||
return this.raw.views
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(BusinessChatLink)
|
||||
memoizeGetters(BusinessChatLink, ['entities'])
|
43
packages/core/src/highlevel/types/premium/business-intro.ts
Normal file
43
packages/core/src/highlevel/types/premium/business-intro.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
|
||||
import { makeInspectable } from '../../utils/inspectable.js'
|
||||
import { memoizeGetters } from '../../utils/memoize.js'
|
||||
import { parseDocument } from '../media/document-utils.js'
|
||||
import { Sticker } from '../media/sticker.js'
|
||||
|
||||
/**
|
||||
* Information about a "business intro" – text that is displayed
|
||||
* when a user opens a chat with a business account for the first time.
|
||||
*/
|
||||
export class BusinessIntro {
|
||||
constructor(readonly raw: tl.RawBusinessIntro) {}
|
||||
|
||||
/**
|
||||
* Title of the intro.
|
||||
*/
|
||||
get title(): string {
|
||||
return this.raw.title
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of the intro.
|
||||
*/
|
||||
get description(): string {
|
||||
return this.raw.description
|
||||
}
|
||||
|
||||
/**
|
||||
* Sticker of the intro.
|
||||
*/
|
||||
get sticker(): Sticker | null {
|
||||
if (!this.raw.sticker || this.raw.sticker._ === 'documentEmpty') return null
|
||||
|
||||
const doc = parseDocument(this.raw.sticker)
|
||||
if (doc.type !== 'sticker') return null
|
||||
|
||||
return doc
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(BusinessIntro)
|
||||
memoizeGetters(BusinessIntro, ['sticker'])
|
|
@ -0,0 +1,24 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
|
||||
import { makeInspectable } from '../../utils/inspectable.js'
|
||||
import { memoizeGetters } from '../../utils/memoize.js'
|
||||
import { Location } from '../media/location.js'
|
||||
|
||||
/** Location of a business */
|
||||
export class BusinessLocation {
|
||||
constructor(readonly raw: tl.RawBusinessLocation) {}
|
||||
|
||||
/** Address of the business */
|
||||
get address(): string {
|
||||
return this.raw.address
|
||||
}
|
||||
|
||||
get location(): Location | null {
|
||||
if (!this.raw.geoPoint || this.raw.geoPoint._ === 'geoPointEmpty') return null
|
||||
|
||||
return new Location(this.raw.geoPoint)
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(BusinessLocation)
|
||||
memoizeGetters(BusinessLocation, ['location'])
|
|
@ -0,0 +1,228 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { createStub } from '@mtcute/test'
|
||||
|
||||
import { BusinessWorkHours, businessWorkHoursDaysToRaw } from './business-work-hours.js'
|
||||
|
||||
describe('BusinessWorkHours', () => {
|
||||
describe('days', () => {
|
||||
const mkHours = (intervals: [number, number][]) =>
|
||||
createStub('businessWorkHours', {
|
||||
weeklyOpen: intervals.map(([start, end]) =>
|
||||
createStub('businessWeeklyOpen', {
|
||||
startMinute: start,
|
||||
endMinute: end,
|
||||
}),
|
||||
),
|
||||
})
|
||||
|
||||
it('should handle a single interval on Monday', () => {
|
||||
const it = new BusinessWorkHours(mkHours([[0, 60]]))
|
||||
|
||||
expect(it.days).toEqual([
|
||||
{ day: 0, is24h: false, intervals: [{ startHour: 0, startMinute: 0, endHour: 1, endMinute: 0 }] },
|
||||
{ day: 1, is24h: false, intervals: [] },
|
||||
{ day: 2, is24h: false, intervals: [] },
|
||||
{ day: 3, is24h: false, intervals: [] },
|
||||
{ day: 4, is24h: false, intervals: [] },
|
||||
{ day: 5, is24h: false, intervals: [] },
|
||||
{ day: 6, is24h: false, intervals: [] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle a single interval on Tuesday', () => {
|
||||
const it = new BusinessWorkHours(mkHours([[1440, 1500]]))
|
||||
|
||||
expect(it.days).toEqual([
|
||||
{ day: 0, is24h: false, intervals: [] },
|
||||
{ day: 1, is24h: false, intervals: [{ startHour: 0, startMinute: 0, endHour: 1, endMinute: 0 }] },
|
||||
{ day: 2, is24h: false, intervals: [] },
|
||||
{ day: 3, is24h: false, intervals: [] },
|
||||
{ day: 4, is24h: false, intervals: [] },
|
||||
{ day: 5, is24h: false, intervals: [] },
|
||||
{ day: 6, is24h: false, intervals: [] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle multiple intervals within a day', () => {
|
||||
const it = new BusinessWorkHours(
|
||||
mkHours([
|
||||
[0, 60],
|
||||
[120, 180],
|
||||
]),
|
||||
)
|
||||
|
||||
expect(it.days).toEqual([
|
||||
{
|
||||
day: 0,
|
||||
is24h: false,
|
||||
intervals: [
|
||||
{ startHour: 0, startMinute: 0, endHour: 1, endMinute: 0 },
|
||||
{ startHour: 2, startMinute: 0, endHour: 3, endMinute: 0 },
|
||||
],
|
||||
},
|
||||
{ day: 1, is24h: false, intervals: [] },
|
||||
{ day: 2, is24h: false, intervals: [] },
|
||||
{ day: 3, is24h: false, intervals: [] },
|
||||
{ day: 4, is24h: false, intervals: [] },
|
||||
{ day: 5, is24h: false, intervals: [] },
|
||||
{ day: 6, is24h: false, intervals: [] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle multiple intervals across different days', () => {
|
||||
const it = new BusinessWorkHours(
|
||||
mkHours([
|
||||
[0, 60],
|
||||
[25 * 60 + 30, 26 * 60 + 30],
|
||||
]),
|
||||
)
|
||||
|
||||
expect(it.days).toEqual([
|
||||
{ day: 0, is24h: false, intervals: [{ startHour: 0, startMinute: 0, endHour: 1, endMinute: 0 }] },
|
||||
{ day: 1, is24h: false, intervals: [{ startHour: 1, startMinute: 30, endHour: 2, endMinute: 30 }] },
|
||||
{ day: 2, is24h: false, intervals: [] },
|
||||
{ day: 3, is24h: false, intervals: [] },
|
||||
{ day: 4, is24h: false, intervals: [] },
|
||||
{ day: 5, is24h: false, intervals: [] },
|
||||
{ day: 6, is24h: false, intervals: [] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle a single interval spanning multiple days', () => {
|
||||
const it = new BusinessWorkHours(mkHours([[0, 1500]]))
|
||||
|
||||
expect(it.days).toEqual([
|
||||
{ day: 0, is24h: true, intervals: [{ startHour: 0, startMinute: 0, endHour: 24, endMinute: 0 }] },
|
||||
{ day: 1, is24h: false, intervals: [{ startHour: 0, startMinute: 0, endHour: 1, endMinute: 0 }] },
|
||||
{ day: 2, is24h: false, intervals: [] },
|
||||
{ day: 3, is24h: false, intervals: [] },
|
||||
{ day: 4, is24h: false, intervals: [] },
|
||||
{ day: 5, is24h: false, intervals: [] },
|
||||
{ day: 6, is24h: false, intervals: [] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle a single 7-day interval', () => {
|
||||
const it = new BusinessWorkHours(mkHours([[0, 7 * 24 * 60]]))
|
||||
|
||||
expect(it.days).toEqual([
|
||||
{ day: 0, is24h: true, intervals: [{ startHour: 0, startMinute: 0, endHour: 24, endMinute: 0 }] },
|
||||
{ day: 1, is24h: true, intervals: [{ startHour: 0, startMinute: 0, endHour: 24, endMinute: 0 }] },
|
||||
{ day: 2, is24h: true, intervals: [{ startHour: 0, startMinute: 0, endHour: 24, endMinute: 0 }] },
|
||||
{ day: 3, is24h: true, intervals: [{ startHour: 0, startMinute: 0, endHour: 24, endMinute: 0 }] },
|
||||
{ day: 4, is24h: true, intervals: [{ startHour: 0, startMinute: 0, endHour: 24, endMinute: 0 }] },
|
||||
{ day: 5, is24h: true, intervals: [{ startHour: 0, startMinute: 0, endHour: 24, endMinute: 0 }] },
|
||||
{ day: 6, is24h: true, intervals: [{ startHour: 0, startMinute: 0, endHour: 24, endMinute: 0 }] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle multiple intervals each spanning multiple days', () => {
|
||||
const it = new BusinessWorkHours(
|
||||
mkHours([
|
||||
[0, 2 * 24 * 60],
|
||||
[4 * 24 * 60, 6 * 24 * 60],
|
||||
]),
|
||||
)
|
||||
|
||||
expect(it.days).toEqual([
|
||||
{ day: 0, is24h: true, intervals: [{ startHour: 0, startMinute: 0, endHour: 24, endMinute: 0 }] },
|
||||
{ day: 1, is24h: true, intervals: [{ startHour: 0, startMinute: 0, endHour: 24, endMinute: 0 }] },
|
||||
{ day: 2, is24h: false, intervals: [] },
|
||||
{ day: 3, is24h: false, intervals: [] },
|
||||
{ day: 4, is24h: true, intervals: [{ startHour: 0, startMinute: 0, endHour: 24, endMinute: 0 }] },
|
||||
{ day: 5, is24h: true, intervals: [{ startHour: 0, startMinute: 0, endHour: 24, endMinute: 0 }] },
|
||||
{ day: 6, is24h: false, intervals: [] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle overlapping intervals', () => {
|
||||
const it = new BusinessWorkHours(
|
||||
mkHours([
|
||||
[0, 60],
|
||||
[30, 90],
|
||||
]),
|
||||
)
|
||||
|
||||
expect(it.days).toEqual([
|
||||
{ day: 0, is24h: false, intervals: [{ startHour: 0, startMinute: 0, endHour: 1, endMinute: 30 }] },
|
||||
{ day: 1, is24h: false, intervals: [] },
|
||||
{ day: 2, is24h: false, intervals: [] },
|
||||
{ day: 3, is24h: false, intervals: [] },
|
||||
{ day: 4, is24h: false, intervals: [] },
|
||||
{ day: 5, is24h: false, intervals: [] },
|
||||
{ day: 6, is24h: false, intervals: [] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle adjascent intervals', () => {
|
||||
const it = new BusinessWorkHours(
|
||||
mkHours([
|
||||
[0, 60],
|
||||
[60, 90],
|
||||
]),
|
||||
)
|
||||
|
||||
expect(it.days).toEqual([
|
||||
{ day: 0, is24h: false, intervals: [{ startHour: 0, startMinute: 0, endHour: 1, endMinute: 30 }] },
|
||||
{ day: 1, is24h: false, intervals: [] },
|
||||
{ day: 2, is24h: false, intervals: [] },
|
||||
{ day: 3, is24h: false, intervals: [] },
|
||||
{ day: 4, is24h: false, intervals: [] },
|
||||
{ day: 5, is24h: false, intervals: [] },
|
||||
{ day: 6, is24h: false, intervals: [] },
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle magic 8th day', () => {
|
||||
const it = new BusinessWorkHours(
|
||||
mkHours([
|
||||
// Mon 12:00 - 14:00
|
||||
[12 * 60, 14 * 60],
|
||||
// Sun 0:00 - Mon (next week) 2:00
|
||||
[6 * 24 * 60 + 20 * 60, 7 * 24 * 60 + 2 * 60],
|
||||
// Mon (next week) 3:00 - Mon (next week) 4:00
|
||||
[7 * 24 * 60 + 3 * 60, 7 * 24 * 60 + 4 * 60],
|
||||
]),
|
||||
)
|
||||
|
||||
expect(it.days).toEqual([
|
||||
{
|
||||
day: 0,
|
||||
is24h: false,
|
||||
intervals: [
|
||||
{ startHour: 0, startMinute: 0, endHour: 2, endMinute: 0 },
|
||||
{ startHour: 3, startMinute: 0, endHour: 4, endMinute: 0 },
|
||||
{ startHour: 12, startMinute: 0, endHour: 14, endMinute: 0 },
|
||||
],
|
||||
},
|
||||
{ day: 1, is24h: false, intervals: [] },
|
||||
{ day: 2, is24h: false, intervals: [] },
|
||||
{ day: 3, is24h: false, intervals: [] },
|
||||
{ day: 4, is24h: false, intervals: [] },
|
||||
{ day: 5, is24h: false, intervals: [] },
|
||||
{ day: 6, is24h: false, intervals: [{ startHour: 20, startMinute: 0, endHour: 24, endMinute: 0 }] },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('businessWorkHoursDaysToRaw', () => {
|
||||
it('should handle 24-hour days', () => {
|
||||
expect(businessWorkHoursDaysToRaw([{ day: 0, is24h: true, intervals: [] }])).toEqual([
|
||||
{ _: 'businessWeeklyOpen', startMinute: 0, endMinute: 1440 },
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle intervals', () => {
|
||||
expect(
|
||||
businessWorkHoursDaysToRaw([
|
||||
{ day: 0, is24h: false, intervals: [{ startHour: 0, startMinute: 0, endHour: 1, endMinute: 0 }] },
|
||||
{ day: 3, is24h: false, intervals: [{ startHour: 12, startMinute: 0, endHour: 14, endMinute: 0 }] },
|
||||
]),
|
||||
).toEqual([
|
||||
{ _: 'businessWeeklyOpen', startMinute: 0, endMinute: 60 },
|
||||
{ _: 'businessWeeklyOpen', startMinute: 3 * 24 * 60 + 12 * 60, endMinute: 3 * 24 * 60 + 14 * 60 },
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
191
packages/core/src/highlevel/types/premium/business-work-hours.ts
Normal file
191
packages/core/src/highlevel/types/premium/business-work-hours.ts
Normal file
|
@ -0,0 +1,191 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
|
||||
import { MtArgumentError } from '../../../types/errors.js'
|
||||
import { makeInspectable } from '../../utils/inspectable.js'
|
||||
import { memoizeGetters } from '../../utils/memoize.js'
|
||||
|
||||
export interface BusinessWorkHoursInterval {
|
||||
/** Start hour of the interval (0-23) */
|
||||
readonly startHour: number
|
||||
/** Start minute of the interval (0-59) */
|
||||
readonly startMinute: number
|
||||
|
||||
/** End hour of the interval (0-23) */
|
||||
readonly endHour: number
|
||||
/** End minute of the interval (0-59) */
|
||||
readonly endMinute: number
|
||||
}
|
||||
|
||||
export interface BusinessWorkHoursDay {
|
||||
/** Day of the week, 0-6, where 0 is Monday and 6 is Sunday */
|
||||
readonly day: number
|
||||
|
||||
/** Whether this day is open 24 hours */
|
||||
readonly is24h: boolean
|
||||
|
||||
/** Open intervals for this day */
|
||||
readonly intervals: BusinessWorkHoursInterval[]
|
||||
}
|
||||
|
||||
const DAYS_IN_WEEK = 7
|
||||
const MINUTES_IN_DAY = 24 * 60
|
||||
|
||||
export function businessWorkHoursDaysToRaw(day: ReadonlyArray<BusinessWorkHoursDay>): tl.TypeBusinessWeeklyOpen[] {
|
||||
const res: tl.TypeBusinessWeeklyOpen[] = []
|
||||
|
||||
for (const d of day) {
|
||||
const dayStart = d.day * MINUTES_IN_DAY
|
||||
|
||||
if (d.is24h) {
|
||||
res.push({
|
||||
_: 'businessWeeklyOpen',
|
||||
startMinute: dayStart,
|
||||
endMinute: dayStart + MINUTES_IN_DAY,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
for (const interval of d.intervals) {
|
||||
const start = dayStart + interval.startHour * 60 + interval.startMinute
|
||||
const end = dayStart + interval.endHour * 60 + interval.endMinute
|
||||
|
||||
if (start >= end) {
|
||||
throw new MtArgumentError('startMinute >= endMinute')
|
||||
}
|
||||
|
||||
res.push({
|
||||
_: 'businessWeeklyOpen',
|
||||
startMinute: start,
|
||||
endMinute: end,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about business work hours.
|
||||
*/
|
||||
export class BusinessWorkHours {
|
||||
constructor(readonly raw: tl.RawBusinessWorkHours) {}
|
||||
|
||||
/** Whether the business is open right now */
|
||||
get isOpenNow(): boolean {
|
||||
return this.raw.openNow!
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifier of the time zone in which the {@link hours} are defined,
|
||||
* in the IANA format.
|
||||
*/
|
||||
get timezoneId(): string {
|
||||
return this.raw.timezoneId
|
||||
}
|
||||
|
||||
/** Raw "open" intervals */
|
||||
get intervals(): tl.TypeBusinessWeeklyOpen[] {
|
||||
return this.raw.weeklyOpen
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsed business hours intervals per week day.
|
||||
*
|
||||
* @returns Array of 7 elements, each representing a day of the week (starting from Monday = 0)
|
||||
*/
|
||||
get days(): ReadonlyArray<BusinessWorkHoursDay> {
|
||||
const days: BusinessWorkHoursDay[] = Array.from({ length: DAYS_IN_WEEK }, (_, i) => ({
|
||||
day: i,
|
||||
is24h: false,
|
||||
intervals: [],
|
||||
}))
|
||||
|
||||
// sort intervals by start time
|
||||
const sorted = [...this.raw.weeklyOpen].sort((a, b) => a.startMinute - b.startMinute)
|
||||
|
||||
// merge overlapping/consecutive intervals
|
||||
for (let i = 1; i < sorted.length; i++) {
|
||||
const prev = sorted[i - 1]
|
||||
const cur = sorted[i]
|
||||
|
||||
if (prev.endMinute >= cur.startMinute) {
|
||||
prev.endMinute = cur.endMinute
|
||||
sorted.splice(i, 1)
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
const mondayPrepend: BusinessWorkHoursInterval[] = []
|
||||
|
||||
// process intervals
|
||||
for (const interval of sorted) {
|
||||
if (interval.startMinute > interval.endMinute) {
|
||||
throw new MtArgumentError('startMinute is greater than endMinute')
|
||||
}
|
||||
|
||||
const startDay = Math.floor(interval.startMinute / MINUTES_IN_DAY)
|
||||
const endDay = Math.floor(interval.endMinute / MINUTES_IN_DAY)
|
||||
|
||||
if (endDay > DAYS_IN_WEEK + 1) {
|
||||
throw new MtArgumentError('interval spans more than a week')
|
||||
}
|
||||
|
||||
for (
|
||||
let day = startDay, dayStart = startDay * MINUTES_IN_DAY;
|
||||
day <= endDay;
|
||||
day++, dayStart += MINUTES_IN_DAY
|
||||
) {
|
||||
const startWithin = Math.max(interval.startMinute, dayStart) - dayStart
|
||||
const endWithin = Math.min(interval.endMinute, dayStart + MINUTES_IN_DAY) - dayStart
|
||||
|
||||
const startHour = Math.floor(startWithin / 60)
|
||||
const startMinute = startWithin % 60
|
||||
const endHour = Math.floor(endWithin / 60)
|
||||
const endMinute = endWithin % 60
|
||||
|
||||
if (startHour === 0 && startMinute === 0 && endHour === 0 && endMinute === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
const obj: BusinessWorkHoursInterval = {
|
||||
startHour,
|
||||
startMinute,
|
||||
endHour,
|
||||
endMinute,
|
||||
}
|
||||
|
||||
if (day === DAYS_IN_WEEK) {
|
||||
// prepend to Monday
|
||||
mondayPrepend.push(obj)
|
||||
} else {
|
||||
days[day].intervals.push(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mondayPrepend.length > 0) {
|
||||
// we do this like this to keep everything sorted
|
||||
days[0].intervals.unshift(...mondayPrepend)
|
||||
}
|
||||
|
||||
// set up 24h days
|
||||
for (const day of days) {
|
||||
if (day.intervals.length !== 1) continue
|
||||
const interval = day.intervals[0]
|
||||
|
||||
if (
|
||||
interval.startHour === 0 &&
|
||||
interval.startMinute === 0 &&
|
||||
((interval.endHour === 24 && interval.endMinute === 0) ||
|
||||
(interval.endHour === 23 && interval.endMinute === 59))
|
||||
) {
|
||||
(day as tl.Mutable<BusinessWorkHoursDay>).is24h = true
|
||||
}
|
||||
}
|
||||
|
||||
return days
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(BusinessWorkHours, undefined, ['intervals'])
|
||||
memoizeGetters(BusinessWorkHours, ['days'])
|
|
@ -1,3 +1,7 @@
|
|||
export * from './boost.js'
|
||||
export * from './boost-slot.js'
|
||||
export * from './boost-stats.js'
|
||||
export * from './business-account.js'
|
||||
export * from './business-chat-link.js'
|
||||
export * from './business-intro.js'
|
||||
export * from './business-work-hours.js'
|
||||
|
|
Loading…
Reference in a new issue