Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Changelog

# **4.12.0 - (2025-12-29)**

## **Updates**

- Bump version `@telegram.ts/formatters@2.1.0` ([v2.1.0](https://github.com/telegramsjs/formatters))
- Major release
- Bump version `typescript@5.9.3`
- Bump version `@telegram.ts/types@1.24.0` (`MessageEntity` add `languageCode` union type)

## **Bug Fixes**

- **MediaData:** parsing inside array fields cover and thumbnail ([3a3bb6a](https://github.com/telegramsjs/Telegramsjs/commit/3a3bb6ab470191f5afbde0c0d3212de896fec3c2))

## **Refactor**

- **Array:** use Collection instead of Array ([8c36298](https://github.com/telegramsjs/Telegramsjs/commit/8c36298080442b93cd89941a0e591f2d377f601f))
- **MessageEntities:** add `languageCode` union type ((457959c)[https://github.com/telegramsjs/Telegramsjs/commit/457959c6ce531ca4318fb4cf0213897e8bcf2e61])
- **Symbol.iterator:** refactored to a unified style ([846b90d](https://github.com/telegramsjs/Telegramsjs/commit/846b90df8de01bbc2ad4b1ac2c4bc3b6f8d1f459))

## **Typings**

- **TransactionPartner:** fix find module `Chat` ([db7d3af](https://github.com/telegramsjs/Telegramsjs/commit/db7d3af6e01d9cc0857cc699b40b697a8a9d7ed7))
- **Markup:** fix unused `@param` warnings in signatures ([2905ef4](https://github.com/telegramsjs/Telegramsjs/commit/2905ef446d223f1ddd4c5039c9dc488a35333065))

---

# **4.11.0 - (2025-12-15)**

## **Updates**
Expand All @@ -9,13 +35,13 @@
- Bump version `typescript@5.9.3`
- Major release (feature parity with [`@discordjs/collection`](https://github.com/discordjs/discord.js/tree/main/packages/collection))

## Features
## **Features**

- **TelegramClient:** resolve login timing issue with async ready handling ([39726ee](https://github.com/telegramsjs/Telegramsjs/commit/39726ee189b1575e0200763c0245179dd3ec5d62))
- **BaseManager:** allow fetch via resolved chat object ([fe95849](https://github.com/telegramsjs/Telegramsjs/commit/fe95849120b89c39d37050c1db4e747552a5bbb4))
- **BaseManager:** fetches multiple users or chats at once ([19180de](https://github.com/telegramsjs/Telegramsjs/commit/19180de54a9564417f9be5448b819bdcd69f5ce9))

## Typings
## **Typings**

- **ChatManager:** correct name parameters ([a874d79](https://github.com/telegramsjs/Telegramsjs/commit/a874d7942194c44229856a8fc4111245f0113c93))
- **BaseManager:** add UserResolvable and ChatResolvable typings ([49a57ad](https://github.com/telegramsjs/Telegramsjs/commit/49a57ad6cdb550616ef60de39e49b03b5008c69b))
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "telegramsjs",
"description": "A powerful library for interacting with the Telegram Bot API",
"version": "4.11.0",
"version": "4.12.0",
"main": "./dist/src/index.js",
"types": "./typings/index.d.ts",
"scripts": {
Expand Down Expand Up @@ -53,7 +53,7 @@
},
"homepage": "https://docs-telegramsjs.vercel.app/",
"devDependencies": {
"@telegram.ts/types": "^1.23.5",
"@telegram.ts/types": "^1.24.0",
"@types/node": "^18.19.61",
"@types/node-fetch": "^2.6.11",
"@types/safe-compare": "^1.1.2",
Expand Down
2 changes: 1 addition & 1 deletion src/client/interfaces/Message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export declare namespace MessageEntity {
export interface PreMessageEntity extends AbstractMessageEntity {
type: "pre";
/** For “pre” only, the programming language of the entity text */
language?: string;
language?: LanguageCode;
}
export interface TextLinkMessageEntity extends AbstractMessageEntity {
type: "text_link";
Expand Down
4 changes: 1 addition & 3 deletions src/managers/BaseManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,7 @@ class BaseManager<T extends Base, ApiObject extends { id: number }> {
* @returns An iterator object that can be used to iterate over the key-value pairs of the Collection.
*/
*[Symbol.iterator](): IterableIterator<[string, T]> {
for (const item of this.cache) {
yield item;
}
yield* this.cache;
}
}

Expand Down
69 changes: 50 additions & 19 deletions src/rest/MediaData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,24 +216,25 @@ class MediaData {
if (await fileExists(value)) {
await this.attachFormMedia(form, value, { id });
return;
} else if (id === "thumbnail" && value.startsWith("http")) {
}

if (id === "thumbnail" && value.startsWith("http")) {
const attachmentId = randomBytes(16).toString("hex");
const response = await fetch(value, { agent });
value = Buffer.from(await response.arrayBuffer());

await this.attachFormMedia(form, value, { id: attachmentId });
const buffer = Buffer.from(await response.arrayBuffer());
await this.attachFormMedia(form, buffer, { id: attachmentId });
form.addPart({
headers: { "content-disposition": `form-data; name="${id}"` },
body: `attach://${attachmentId}`,
});
return;
} else {
form.addPart({
headers: { "content-disposition": `form-data; name="${id}"` },
body: String(value),
});
return;
}

form.addPart({
headers: { "content-disposition": `form-data; name="${id}"` },
body: String(value),
});
return;
}

if (typeof value === "object" && value !== null && "source" in value) {
Expand Down Expand Up @@ -279,27 +280,57 @@ class MediaData {
if (Array.isArray(value)) {
const attachments = await Promise.all(
value.map(async (item) => {
const media = item.media?.source ? item.media.source : item;
if (!this.isMediaType(media.media)) {
if (!(await fileExists(media.media))) {
return media;
}
const media = item.media?.source ?? item;

if (
!this.isMediaType(media.media) &&
!(await fileExists(media.media))
) {
return media;
}
const attachmentId = randomBytes(16).toString("hex");

const attachmentId = randomBytes(16).toString("hex");
await this.attachFormMedia(form, media.media, {
id: attachmentId,
...(typeof media.media === "object" &&
"filename" in (media.media || {}) && {
"filename" in media.media && {
filename: media.media.filename,
}),
});

let coverId = null;
if ("cover" in item && !item.cover?.startsWith?.("http")) {
coverId = randomBytes(16).toString("hex");
await this.attachFormMedia(form, item.cover, { id: coverId });
}

let thumbnailId = null;
if ("thumbnail" in item) {
thumbnailId = randomBytes(16).toString("hex");

if (item.thumbnail?.startsWith?.("http")) {
const response = await fetch(item.thumbnail, { agent });
const buffer = Buffer.from(await response.arrayBuffer());
await this.attachFormMedia(form, buffer, { id: thumbnailId });
} else {
await this.attachFormMedia(form, item.thumbnail, {
id: thumbnailId,
});
}
}

const result = {
...(item.media.source ? item : media),
media: `attach://${attachmentId}`,
...(coverId && { cover: `attach://${coverId}` }),
...(thumbnailId && { thumbnail: `attach://${thumbnailId}` }),
};

if (item.media.source) {
return { type: item.type, media: `attach://${attachmentId}` };
result.type = item.type;
}

return { ...media, media: `attach://${attachmentId}` };
return result;
}),
);

Expand Down
2 changes: 1 addition & 1 deletion src/structures/ChatJoinRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class ChatJoinRequest extends Base {
constructor(client, data) {
super(client);

/** Identifier of a private chat with the user who sent the join request. The bot can use this identifier for 5 minutes to send messages until the join request is processed, assuming no other administrator contacted the user. */
/** Identifier of a private chat with the user who sent the join request. The bot can use this identifier for 5 minutes to send messages until the join request is processed, assuming no other administrato */
this.userChatId = String(data.user_chat_id);

/**
Expand Down
4 changes: 1 addition & 3 deletions src/structures/boost/UserChatBoosts.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ class UserChatBoosts {
* @returns {IterableIterator<ChatBoost>}
*/
*[Symbol.iterator]() {
for (const [_, boost] of this.boosts) {
yield boost;
}
yield* this.boosts.values();
}
}

Expand Down
4 changes: 1 addition & 3 deletions src/structures/business/BusinessMessagesDeleted.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ class BusinessMessagesDeleted extends Base {
* @returns {IterableIterator<string>}
*/
*[Symbol.iterator]() {
for (const id of this.ids) {
yield id;
}
yield* this.ids;
}
}

Expand Down
4 changes: 1 addition & 3 deletions src/structures/chat/VideoChatParticipantsInvited.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ class VideoChatParticipantsInvited extends Base {
* @returns {IterableIterator<import("../misc/User").User>}
*/
*[Symbol.iterator]() {
for (const [_, user] of this.users) {
yield user;
}
yield* this.users.values();
}
}

Expand Down
44 changes: 33 additions & 11 deletions src/structures/checklist/Checklist.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@
const { Base } = require("../Base");
const { ChecklistTask } = require("./ChecklistTask");
const { MessageEntities } = require("../message/MessageEntities");
const { Collection } = require("@telegram.ts/collection");

/**
* Type guard for completed tasks
* @param {ChecklistTask} task
* @returns {task is ChecklistTask & { completionUnixTime: number; completionTimestamp: number; isCompleted: true; completedByUser: import("../misc/User").User }}
*/
function isCompletedTask(task) {
return task.isCompleted === true;
}

/**
* Type guard for pending tasks
* @param {ChecklistTask} task
* @returns {task is ChecklistTask & { completionUnixTime: undefined; completionTimestamp: undefined; isCompleted: false; completedByUser: undefined }}
*/
function isPendingTask(task) {
return task.isCompleted === false;
}

class Checklist extends Base {
/**
Expand All @@ -23,8 +42,13 @@ class Checklist extends Base {
);
}

/** List of tasks in the checklist */
this.tasks = data.tasks.map((task) => new ChecklistTask(client, task));
/**
* Collection of tasks in the checklist
* @type {import("@telegram.ts/collection").ReadonlyCollection<number, ChecklistTask>}
*/
this.tasks = new Collection(
data.tasks.map((task, index) => [index, new ChecklistTask(client, task)]),
);

if ("others_can_add_tasks" in data) {
/** True, if users other than the creator of the list can add tasks to the list */
Expand All @@ -39,44 +63,42 @@ class Checklist extends Base {

/**
* Get completed tasks
* @type {ChecklistTask[]}
* @type {import("@telegram.ts/collection").ReadonlyCollection<number, ChecklistTask & { completionUnixTime: number; completionTimestamp: number; isCompleted: true; completedByUser: import("../misc/User").User }>}
*/
get completedTasks() {
return this.tasks.filter((task) => task.isCompleted);
return this.tasks.filter(isCompletedTask);
}

/**
* Get pending tasks
* @type {ChecklistTask[]}
* @type {import("@telegram.ts/collection").ReadonlyCollection<number, ChecklistTask & { completionUnixTime: undefined; completionTimestamp: undefined; isCompleted: false; completedByUser: undefined }>}
*/
get pendingTasks() {
return this.tasks.filter((task) => !task.isCompleted);
return this.tasks.filter(isPendingTask);
}

/**
* Get total number of tasks
* @type {number}
*/
get totalTasks() {
return this.tasks.length;
return this.tasks.size;
}

/**
* Get number of completed tasks
* @type {number}
*/
get completedTasksCount() {
return this.completedTasks.length;
return this.completedTasks.size;
}

/**
* Makes the class iterable, returning each check task list
* @returns {IterableIterator<ChecklistTask>}
*/
*[Symbol.iterator]() {
for (const task of this.tasks) {
yield task;
}
yield* this.tasks.values();
}
}

Expand Down
4 changes: 1 addition & 3 deletions src/structures/checklist/ChecklistTasksAdded.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,7 @@ class ChecklistTasksAdded extends Base {
* @returns {IterableIterator<ChecklistTask>}
*/
*[Symbol.iterator]() {
for (const task of this.tasks) {
yield task;
}
yield* this.tasks;
}
}

Expand Down
4 changes: 1 addition & 3 deletions src/structures/gift/Gifts.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ class Gifts {
* @returns {IterableIterator<Gift>}
*/
*[Symbol.iterator]() {
for (const [_, gift] of this.gifts) {
yield gift;
}
yield* this.gifts.values();
}
}

Expand Down
4 changes: 1 addition & 3 deletions src/structures/giveaway/GiveawayWinners.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,7 @@ class GiveawayWinners extends Base {
* @returns {IterableIterator<import("../misc/User").User>}
*/
*[Symbol.iterator]() {
for (const [_, winner] of this.winners) {
yield winner;
}
yield* this.winners.values();
}
}

Expand Down
14 changes: 9 additions & 5 deletions src/structures/invoice/TransactionPartner.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { Gift } = require("../gift/Gift");
const { PaidMedia } = require("../media/paid/PaidMedia");
const { AffiliateInfo } = require("./AffiliateInfo");
const { RevenueWithdrawalState } = require("./RevenueWithdrawalState");
const { Collection } = require("@telegram.ts/collection");

class TransactionPartner extends Base {
/**
Expand Down Expand Up @@ -48,10 +49,13 @@ class TransactionPartner extends Base {
if ("paid_media" in data) {
/**
* Information about the paid media bought by the user. Can be available only for “invoice_payment” transactions.
* @type {PaidMedia[] | undefined}
* @type {import("@telegram.ts/collection").ReadonlyCollection<number, PaidMedia> | undefined}
*/
this.paidMedia = data.paid_media.map(
(media) => new PaidMedia(this.client, media),
this.paidMedia = new Collection(
data.paid_media.map((media, index) => [
index,
new PaidMedia(this.client, media),
]),
);
}

Expand Down Expand Up @@ -136,14 +140,14 @@ class TransactionPartner extends Base {
}

/**
* @returns {this is this & { withdrawal?: undefined; user: import("../misc/User").User; paidMedia?: PaidMedia[]; paidMediaPayload?: string; gift?: Gift; subscriptionPeriod?: number; affiliate?: AffiliateInfo; sponsorUser?: undefined; commissionRate?: undefined; requestCount?: undefined; chat?: undefined; premiumSubscriptionDuration?: number; }}
* @returns {this is this & { withdrawal?: undefined; user: import("../misc/User").User; paidMedia?: import("@telegram.ts/collection").ReadonlyCollection<number, PaidMedia>; paidMediaPayload?: string; gift?: Gift; subscriptionPeriod?: number; affiliate?: AffiliateInfo; sponsorUser?: undefined; commissionRate?: undefined; requestCount?: undefined; chat?: undefined; premiumSubscriptionDuration?: number; }}
*/
isUser() {
return Boolean("user" in this && this.user);
}

/**
* @returns {this is this & { withdrawal?: undefined; user?: undefined; paidMedia?: PaidMedia[]; paidMediaPayload?: string; gift?: Gift; subscriptionPeriod?: number; affiliate?: AffiliateInfo; sponsorUser?: undefined; commissionRate?: undefined; requestCount?: undefined; chat: import("../misc/Chat").Chat; premiumSubscriptionDuration?: undefined; }}
* @returns {this is this & { withdrawal?: undefined; user?: undefined; paidMedia?: undefined; paidMediaPayload?: string; gift?: Gift; subscriptionPeriod?: number; affiliate?: AffiliateInfo; sponsorUser?: undefined; commissionRate?: undefined; requestCount?: undefined; chat: import("../chat/Chat").Chat; premiumSubscriptionDuration?: undefined; }}
*/
isChat() {
return Boolean("chat" in this && this.chat);
Expand Down
Loading
Loading