From a4ba7e645f79d0ffc699bc196f0a27e35d1a1122 Mon Sep 17 00:00:00 2001 From: Damir Modyarov Date: Fri, 9 Jan 2026 17:08:07 +0300 Subject: [PATCH 1/6] feat: Implemeht group chart support --- locales/en.ftl | 3 ++- locales/ru.ftl | 3 ++- src/telegram/bot/download.ts | 45 ++++++++++++++++++------------- src/telegram/bot/info.ts | 2 +- src/telegram/bot/settings.ts | 47 ++++++++++++++++++++++++--------- src/telegram/bot/start.ts | 2 +- src/telegram/bot/stats.ts | 4 +-- src/telegram/helpers/handler.ts | 7 +++-- 8 files changed, 72 insertions(+), 41 deletions(-) diff --git a/locales/en.ftl b/locales/en.ftl index da52fbe..e9390c7 100644 --- a/locales/en.ftl +++ b/locales/en.ftl @@ -5,6 +5,7 @@ error = { error-title }: { $message } { $error-emoticon } error-not-url = i couldn't find url in your message error-request-not-found = looks like i forgot your link, try sending it again error-not-button-owner = looks like this button is not yours (¬_¬") +error-admin-button = only admins can touch this button!! error-too-large = sorry, but this file is too big - telegram doesn't allow me to upload it error-invalid-response = server response is invalid, maybe it's down or encountered an internal error error-unresponsive = couldn't connect to this server, maybe it's down... @@ -71,4 +72,4 @@ stats-global = i helped with downloading { $count } times! (˶ᵔ ᵕ ᵔ˶) info = running { $name }@{ $version } sources: { $repository } - report bugs: { $bugs } \ No newline at end of file + report bugs: { $bugs } diff --git a/locales/ru.ftl b/locales/ru.ftl index 04d865b..7f56ed6 100644 --- a/locales/ru.ftl +++ b/locales/ru.ftl @@ -5,6 +5,7 @@ error = { error-title }: { $message } { $error-emoticon } error-not-url = я не нашёл ссылки в твоём сообщении error-request-not-found = похоже я потерял твою ссылку, можешь отправить её снова? error-not-button-owner = похоже что эта кнопка не твоя (¬_¬") +error-admin-button = только админам можно тыкать эту кнопку!! error-too-large = этот файл слишком большой, к сожалению тг не даёт его загрузить error-invalid-response = сервер некорректно ответил, возможно он столкнулся с внутренней ошибкой или лежит error-unresponsive = не удалось подключиться к серверу, наверное он лежит... @@ -57,4 +58,4 @@ stats-global = я помог с загрузкой { $count } раз! (˶ᵔ ᵕ info = выполняется { $name }@{ $version } сурсы: { $repository } - репорт багов: { $bugs } \ No newline at end of file + репорт багов: { $bugs } diff --git a/src/telegram/bot/download.ts b/src/telegram/bot/download.ts index 25f5a78..4c55611 100644 --- a/src/telegram/bot/download.ts +++ b/src/telegram/bot/download.ts @@ -1,11 +1,13 @@ +import type { BusinessCallbackQueryContext, CallbackQueryContext, InlineCallbackQueryContext } from "@mtcute/dispatcher" import type { InputMediaLike, Peer } from "@mtcute/node" import { randomUUID } from "node:crypto" -import { Dispatcher, filters } from "@mtcute/dispatcher" +import { Dispatcher } from "@mtcute/dispatcher" import { BotInline, BotKeyboard } from "@mtcute/node" import type { MediaRequest } from "@/core/data/request" import { createRequest, getRequest } from "@/core/data/request" +import type { Settings } from "@/core/data/settings" import { incrementDownloadCount } from "@/core/data/stats" import { getOutputSelectionMessage, @@ -18,8 +20,8 @@ import { evaluatorsFor } from "@/telegram/helpers/text" export const downloadDp = Dispatcher.child() -downloadDp.onNewMessage(filters.chat("user"), async (msg) => { - const { e, t } = await evaluatorsFor(msg.sender) +downloadDp.onNewMessage(async (msg) => { + const { e, t } = await evaluatorsFor(msg.chat) if (msg.text === "meow") { await msg.replyText("meow :з") @@ -31,7 +33,8 @@ downloadDp.onNewMessage(filters.chat("user"), async (msg) => { const req = await createRequest(extractedUrl || msg.text, msg.sender.id) if (!req.success) { - await msg.replyText(t("error", { message: e(req.error) })) + if (msg.chat.type === "user") + await msg.replyText(t("error", { message: e(req.error) })) return } @@ -45,16 +48,16 @@ downloadDp.onNewMessage(filters.chat("user"), async (msg) => { ]), }) - const settings = await getPeerSettings(msg.sender) + const settings = await getPeerSettings(msg.chat) if (settings.preferredOutput) { await onOutputSelected( settings.preferredOutput, req.result, args => msg.client.editMessage({ ...args, message: reply }), { e, t }, - msg.sender, - !!settings.preferredAttribution, + settings, ({ medias }) => msg.replyMediaGroup(medias), + msg.sender, ) } }) @@ -102,8 +105,13 @@ downloadDp.onInlineQuery(async (ctx) => { }) downloadDp.onAnyCallbackQuery(OutputButton.filter(), async (upd) => { - const settings = await getPeerSettings(upd.user) - const { t, e } = await evaluatorsFor(upd.user) + // When passing a filter to onAnyCallbackQuery it applies a modification to the update object, which makes it lose its enum-like properties. + // To access the original update object, we need to cast it to the original type. + const rawUpd = upd as unknown as (CallbackQueryContext | InlineCallbackQueryContext | BusinessCallbackQueryContext) + + const peer = rawUpd._name === "callback_query" ? rawUpd.chat : upd.user + const settings = await getPeerSettings(peer) + const { t, e } = await evaluatorsFor(peer) const { output: outputType, request: requestId } = upd.match const request = await getRequest(requestId) @@ -118,9 +126,9 @@ downloadDp.onAnyCallbackQuery(OutputButton.filter(), async (upd) => { request, args => upd.editMessage(args), { t, e }, + settings, + ({ medias }) => upd.client.sendMediaGroup(peer.id, medias), upd.user, - !!settings.preferredAttribution, - ({ medias }) => upd.client.sendMediaGroup(upd.user.id, medias), ) }) @@ -136,9 +144,9 @@ downloadDp.onChosenInlineResult(async (upd) => { request, args => upd.editMessage({ ...args, messageId }), await evaluatorsFor(upd.user), - upd.user, - !!settings.preferredAttribution, + settings, ({ medias }) => upd.client.sendMediaGroup(upd.user.id, medias), + upd.user, ) } }) @@ -148,12 +156,12 @@ async function onOutputSelected( request: MediaRequest | undefined, editMessage: (edit: { text?: string, media?: InputMediaLike }) => Promise, { t, e }: Evaluators, - peer: Peer, - leaveSourceLink: boolean, + settings: Settings, sendGroup: (send: { medias: InputMediaLike[] }) => Promise, + sender: Peer, ) { await editMessage({ text: t("downloading-title") }) - const res = await handleMediaDownload(outputType, request, peer) + const res = await handleMediaDownload(outputType, request, settings) if (!res.success) { await editMessage({ text: t("error", { message: e(res.error) }) }) return @@ -168,10 +176,9 @@ async function onOutputSelected( await sendGroup({ medias: chunk }) } } else { - await editMessage({ media: res.result[0] }) - await editMessage({ text: (leaveSourceLink && request?.url) || "" }) + await editMessage({ media: res.result[0], text: (!!settings.preferredAttribution && request?.url) || "" }) } - incrementDownloadCount(peer.id) + incrementDownloadCount(sender.id) .catch(() => { /* noop */ }) } diff --git a/src/telegram/bot/info.ts b/src/telegram/bot/info.ts index df5930c..8302083 100644 --- a/src/telegram/bot/info.ts +++ b/src/telegram/bot/info.ts @@ -6,6 +6,6 @@ import { evaluatorsFor } from "@/telegram/helpers/text" export const infoDp = Dispatcher.child() settingsDp.onNewMessage(filters.command("info"), async (msg) => { - const { t } = await evaluatorsFor(msg.sender) + const { t } = await evaluatorsFor(msg.chat) await msg.replyText(t("info", { bugs, name, repository, version })) }) diff --git a/src/telegram/bot/settings.ts b/src/telegram/bot/settings.ts index 6f436d2..602158f 100644 --- a/src/telegram/bot/settings.ts +++ b/src/telegram/bot/settings.ts @@ -1,3 +1,4 @@ +import type { Peer, TelegramClient, User } from "@mtcute/node" import { Dispatcher, filters, PropagationAction } from "@mtcute/dispatcher" import { BotKeyboard } from "@mtcute/node" @@ -34,6 +35,13 @@ function settingsMessage(e: TextEvaluator, settings: Settings) { } } +async function isAdmin(client: Pick, chat: Peer, user: User) { + if (chat.type !== "chat") + return true + const member = await client.getChatMember({ chatId: chat, userId: user }) + return member?.status === "admin" || member?.status === "creator" +} + function settingEditMessage(e: TextEvaluator, settings: Settings, setting: keyof Settings) { const menu = getSettingMenu(settings, setting) return { @@ -49,16 +57,21 @@ function settingEditMessage(e: TextEvaluator, settings: Settings, setting: keyof settingsDp.onNewMessage( filters.or(filters.command("settings"), filters.deeplink(["settings"])), async (msg) => { - const { e } = await evaluatorsFor(msg.sender) - const settings = await getPeerSettings(msg.sender) + const { e } = await evaluatorsFor(msg.chat) + const settings = await getPeerSettings(msg.chat) const { text, ...props } = settingsMessage(e, settings) await msg.replyText(text, props) }, ) -settingsDp.onAnyCallbackQuery(SettingButton.filter(), async (upd) => { - const { e } = await evaluatorsFor(upd.user) - const settings = await getPeerSettings(upd.user) +settingsDp.onCallbackQuery(SettingButton.filter(), async (upd) => { + const { e, t } = await evaluatorsFor(upd.chat) + if (!await isAdmin(upd.client, upd.chat, upd.user)) { + return await upd.answer({ + text: t("error-admin-button"), + }) + } + const settings = await getPeerSettings(upd.chat) if (upd.match.setting === "back") { await upd.editMessage(settingsMessage(e, settings)) return @@ -69,36 +82,46 @@ settingsDp.onAnyCallbackQuery(SettingButton.filter(), async (upd) => { await upd.editMessage(settingEditMessage(e, settings, upd.match.setting)) }) -settingsDp.onAnyCallbackQuery(SettingUpdateButton.filter(), async (upd, state) => { +settingsDp.onCallbackQuery(SettingUpdateButton.filter(), async (upd, state) => { if (!isValidSettingKey(upd.match.setting)) return // Invalid key + if (!await isAdmin(upd.client, upd.chat, upd.user)) { + const { t } = await evaluatorsFor(upd.chat) + return await upd.answer({ + text: t("error-admin-button"), + }) + } - const settings = await getPeerSettings(upd.user) + const settings = await getPeerSettings(upd.chat) const valueIndex = +upd.match.value const value = getSettingValues(upd.match.setting)[valueIndex] if (value === customValue) { - const { e, t } = await evaluatorsFor(upd.user) + const { e, t } = await evaluatorsFor(upd.chat) const { text: _, ...props } = settingEditMessage(e, settings, upd.match.setting) await upd.editMessage({ text: t("setting-custom"), ...props }) await state.enter(settingInputScene, { with: { setting: upd.match.setting } }) return } - const newSettings = await updateSetting(upd.match.setting, value, upd.user.id) + const newSettings = await updateSetting(upd.match.setting, value, upd.chat.id) // We're getting evaluator AFTER the possible locale update - const { e } = await evaluatorsFor(upd.user) + const { e } = await evaluatorsFor(upd.chat) await upd.editMessage(settingEditMessage(e, newSettings ?? settings, upd.match.setting)) }) settingInputScene.onNewMessage(async (upd, state) => { + if (upd.sender.type !== "user" || !await isAdmin(upd.client, upd.chat, upd.sender)) { + return + } + const stateData = await state.get() if (!stateData) { await state.exit() return } - const { t } = await evaluatorsFor(upd.sender) - await updateSetting(stateData.setting, upd.text, upd.sender.id) + const { t } = await evaluatorsFor(upd.chat) + await updateSetting(stateData.setting, upd.text, upd.chat.id) await upd.replyText(t("setting-saved")) await state.exit() diff --git a/src/telegram/bot/start.ts b/src/telegram/bot/start.ts index 7f5e387..4538a5e 100644 --- a/src/telegram/bot/start.ts +++ b/src/telegram/bot/start.ts @@ -3,6 +3,6 @@ import { translatorFor } from "@/telegram/helpers/i18n" export const startDp = Dispatcher.child() startDp.onNewMessage(filters.start, async (msg) => { - const t = await translatorFor(msg.sender) + const t = await translatorFor(msg.chat) await msg.replyText(t("start")) }) diff --git a/src/telegram/bot/stats.ts b/src/telegram/bot/stats.ts index 211b5dc..015fe59 100644 --- a/src/telegram/bot/stats.ts +++ b/src/telegram/bot/stats.ts @@ -6,13 +6,13 @@ import { evaluatorsFor } from "@/telegram/helpers/text" export const statsDp = Dispatcher.child() statsDp.onNewMessage(filters.command("stats"), async (msg) => { - const { t } = await evaluatorsFor(msg.sender) + const { t } = await evaluatorsFor(msg.chat) const count = await getDownloadStats() await msg.replyText(t("stats-global", { count })) }) statsDp.onNewMessage(filters.command("mystats"), async (msg) => { - const { t } = await evaluatorsFor(msg.sender) + const { t } = await evaluatorsFor(msg.chat) const id = msg.sender.id const count = await getDownloadStats(id) await msg.replyText(t("stats-personal", { count })) diff --git a/src/telegram/helpers/handler.ts b/src/telegram/helpers/handler.ts index eaf94b9..4d3cadf 100644 --- a/src/telegram/helpers/handler.ts +++ b/src/telegram/helpers/handler.ts @@ -1,4 +1,4 @@ -import type { InputMediaLike, Peer } from "@mtcute/node" +import type { InputMediaLike } from "@mtcute/node" import type { GeneralTrack, ImageTrack, VideoTrack } from "mediainfo.js" import { CallbackDataBuilder } from "@mtcute/dispatcher" @@ -8,13 +8,13 @@ import type { ApiServer, CobaltDownloadParams } from "@/core/data/cobalt" import type { DownloadedMediaContent } from "@/core/data/cobalt/tunnel" import type { MediaRequest } from "@/core/data/request" import { finishRequest, outputOptions } from "@/core/data/request" +import type { Settings } from "@/core/data/settings" import type { Result } from "@/core/utils/result" import { error, ok } from "@/core/utils/result" import type { Text } from "@/core/utils/text" import { translatable } from "@/core/utils/text" import { urlWithAuthSchema } from "@/core/utils/url" import { env } from "@/telegram/helpers/env" -import { getPeerSettings } from "@/telegram/helpers/settings" export const OutputButton = new CallbackDataBuilder("dl", "output", "request") export const getOutputSelectionMessage = (requestId: string) => ({ @@ -108,10 +108,9 @@ function getApiEndpoints(override: string | null): Result { ) } -export async function handleMediaDownload(outputType: string, request: MediaRequest | undefined, peer: Peer): Promise> { +export async function handleMediaDownload(outputType: string, request: MediaRequest | undefined, settings: Settings): Promise> { if (!request) return error(translatable("error-request-not-found")) - const settings = await getPeerSettings(peer) const endpoints = getApiEndpoints(settings.instanceOverride) if (!endpoints.success) return endpoints From 373ab3db1aa76d6a72af7a015068dd52f6e97f43 Mon Sep 17 00:00:00 2001 From: Damir Modyarov Date: Fri, 9 Jan 2026 18:02:04 +0300 Subject: [PATCH 2/6] fix: Update picker note to match actual behavior --- locales/en.ftl | 2 +- locales/ru.ftl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.ftl b/locales/en.ftl index e9390c7..2553d79 100644 --- a/locales/en.ftl +++ b/locales/en.ftl @@ -13,7 +13,7 @@ error-invalid-url = looks like i dont recognise the link you sent... maybe the s error-media-unavailable = i found your media, but couldn't download it. maybe its private, age restricted or region locked. error-unknown = oops, an internal error happened. i reported it to my developer, so they'll fix it! -note-picker = your link contained multiple media files, so i sent them to you via pms +note-picker = your link contained multiple media files, so i sent them to you seperately or via pms download-title = download from provided url type-select-title = select download type (。 · ᎑ ·。) diff --git a/locales/ru.ftl b/locales/ru.ftl index 7f56ed6..d17e5a4 100644 --- a/locales/ru.ftl +++ b/locales/ru.ftl @@ -13,7 +13,7 @@ error-invalid-url = кажется у меня не получается рас error-media-unavailable = я нашёл нужный файл, но не смог его скачать. возможно на нём ограничения по региону, возрасту или приватности. error-unknown = ой, произошла внутреняя ошибка. я сообщил об этом моим разработчикам, чтобы они исправили! -note-picker = по твоей ссылке я нашёл несколько медиа файлов, поэтому я отправил тебе их в лс +note-picker = по твоей ссылке я нашёл несколько медиа файлов, поэтому я отправил тебе отдельно или их в лс download-title = скачать по ссылке type-select-title = выбери тип загрузки (。 · ᎑ ·。) From a3f93448cf435d2503b9ffa23fc4fc4e4af3874a Mon Sep 17 00:00:00 2001 From: Damir Modyarov Date: Fri, 9 Jan 2026 18:16:06 +0300 Subject: [PATCH 3/6] feat: Implement multi-link download --- src/telegram/bot/download.ts | 61 +++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/src/telegram/bot/download.ts b/src/telegram/bot/download.ts index 4c55611..73a3d5a 100644 --- a/src/telegram/bot/download.ts +++ b/src/telegram/bot/download.ts @@ -28,37 +28,40 @@ downloadDp.onNewMessage(async (msg) => { return } - const urlEntity = msg.entities.find(e => e.is("text_link") || e.is("url")) - const extractedUrl = urlEntity && (urlEntity.is("text_link") ? urlEntity.params.url : urlEntity.text) - const req = await createRequest(extractedUrl || msg.text, msg.sender.id) + const urlEntities = msg.entities.filter(e => e.is("text_link") || e.is("url")) + const extractedUrls = urlEntities.map(e => (e.is("text_link") ? e.params.url : e.text)) + const urls = extractedUrls.length ? extractedUrls : [msg.text] + for (const url of urls) { + const req = await createRequest(url, msg.sender.id) + + if (!req.success) { + if (msg.chat.type === "user") + await msg.replyText(t("error", { message: e(req.error) })) + return + } - if (!req.success) { - if (msg.chat.type === "user") - await msg.replyText(t("error", { message: e(req.error) })) - return - } + const selectMsg = getOutputSelectionMessage(req.result.id) + const reply = await msg.replyText(e(selectMsg.caption), { + replyMarkup: BotKeyboard.inline([ + selectMsg.options.map(o => BotKeyboard.callback( + e(o.name), + o.key, + )), + ]), + }) - const selectMsg = getOutputSelectionMessage(req.result.id) - const reply = await msg.replyText(e(selectMsg.caption), { - replyMarkup: BotKeyboard.inline([ - selectMsg.options.map(o => BotKeyboard.callback( - e(o.name), - o.key, - )), - ]), - }) - - const settings = await getPeerSettings(msg.chat) - if (settings.preferredOutput) { - await onOutputSelected( - settings.preferredOutput, - req.result, - args => msg.client.editMessage({ ...args, message: reply }), - { e, t }, - settings, - ({ medias }) => msg.replyMediaGroup(medias), - msg.sender, - ) + const settings = await getPeerSettings(msg.chat) + if (settings.preferredOutput) { + await onOutputSelected( + settings.preferredOutput, + req.result, + args => msg.client.editMessage({ ...args, message: reply }), + { e, t }, + settings, + ({ medias }) => msg.replyMediaGroup(medias), + msg.sender, + ) + } } }) From e0cf9fbacd31c9d06fd2a9e3183edd33c8c78f52 Mon Sep 17 00:00:00 2001 From: Damir Modyarov Date: Fri, 9 Jan 2026 18:32:11 +0300 Subject: [PATCH 4/6] feat: Make error messages automatically deleted after a timeout in group chats --- src/telegram/bot/download.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/telegram/bot/download.ts b/src/telegram/bot/download.ts index 73a3d5a..a41eee7 100644 --- a/src/telegram/bot/download.ts +++ b/src/telegram/bot/download.ts @@ -20,6 +20,8 @@ import { evaluatorsFor } from "@/telegram/helpers/text" export const downloadDp = Dispatcher.child() +const errorDeleteDelay = 30 * 1000 + downloadDp.onNewMessage(async (msg) => { const { e, t } = await evaluatorsFor(msg.chat) @@ -52,7 +54,7 @@ downloadDp.onNewMessage(async (msg) => { const settings = await getPeerSettings(msg.chat) if (settings.preferredOutput) { - await onOutputSelected( + const res = await onOutputSelected( settings.preferredOutput, req.result, args => msg.client.editMessage({ ...args, message: reply }), @@ -61,6 +63,8 @@ downloadDp.onNewMessage(async (msg) => { ({ medias }) => msg.replyMediaGroup(medias), msg.sender, ) + if (!res && msg.chat.type !== "user") + setTimeout(() => msg.client.deleteMessages([reply]), errorDeleteDelay) } } }) @@ -124,7 +128,7 @@ downloadDp.onAnyCallbackQuery(OutputButton.filter(), async (upd) => { }) } - await onOutputSelected( + const res = await onOutputSelected( outputType, request, args => upd.editMessage(args), @@ -133,6 +137,8 @@ downloadDp.onAnyCallbackQuery(OutputButton.filter(), async (upd) => { ({ medias }) => upd.client.sendMediaGroup(peer.id, medias), upd.user, ) + if (!res && rawUpd._name === "callback_query" && rawUpd.chat.type !== "user") + setTimeout(() => upd.client.deleteMessagesById(rawUpd.chat.id, [rawUpd.messageId]), errorDeleteDelay) }) downloadDp.onChosenInlineResult(async (upd) => { @@ -167,7 +173,7 @@ async function onOutputSelected( const res = await handleMediaDownload(outputType, request, settings) if (!res.success) { await editMessage({ text: t("error", { message: e(res.error) }) }) - return + return false } await editMessage({ text: t("uploading-title") }) @@ -184,4 +190,6 @@ async function onOutputSelected( incrementDownloadCount(sender.id) .catch(() => { /* noop */ }) + + return true } From 962a39058887420c1ff398097f4d30542a475755 Mon Sep 17 00:00:00 2001 From: Damir Modyarov Date: Fri, 9 Jan 2026 18:45:53 +0300 Subject: [PATCH 5/6] feat: Add group join message --- locales/en.ftl | 4 ++++ locales/ru.ftl | 4 ++++ src/telegram/bot/start.ts | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/locales/en.ftl b/locales/en.ftl index 2553d79..4895e26 100644 --- a/locales/en.ftl +++ b/locales/en.ftl @@ -1,4 +1,8 @@ start = hii! just send me a link and i'll download it. (ᵔᵕᵔ)◜ +join = + hii! (ᵔᵕᵔ)◜ + i will download all links i'll find in this chat. + all error messages will be deleted within 30 seconds to not annoy you. error-title = error error = { error-title }: { $message } { $error-emoticon } diff --git a/locales/ru.ftl b/locales/ru.ftl index d17e5a4..91f0f98 100644 --- a/locales/ru.ftl +++ b/locales/ru.ftl @@ -1,4 +1,8 @@ start = привет! просто отправь мне ссылку и я её скачаю. (ᵔᵕᵔ)◜ +join = + привет! (ᵔᵕᵔ)◜ + я буду скачивать все ссылки, которые я найду в этом чате. + все сообщения об ошибках будут удаляться в течении 30 секунд, чтобы не мешать вам. error-title = ошибка error = { error-title }: { $message } { $error-emoticon } diff --git a/src/telegram/bot/start.ts b/src/telegram/bot/start.ts index 4538a5e..9013685 100644 --- a/src/telegram/bot/start.ts +++ b/src/telegram/bot/start.ts @@ -6,3 +6,8 @@ startDp.onNewMessage(filters.start, async (msg) => { const t = await translatorFor(msg.chat) await msg.replyText(t("start")) }) + +startDp.onChatMemberUpdate(filters.and(filters.chatMemberSelf, filters.chatMember("added")), async (upd) => { + const t = await translatorFor(upd.chat) + await upd.client.sendText(upd.chat, t("join")) +}) From f5f6c7922788daa68646d5260a4e2ebe0a907686 Mon Sep 17 00:00:00 2001 From: Damir Modyarov Date: Fri, 9 Jan 2026 18:46:53 +0300 Subject: [PATCH 6/6] fix: Force auto dowload in groups --- src/telegram/bot/download.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/telegram/bot/download.ts b/src/telegram/bot/download.ts index a41eee7..4501867 100644 --- a/src/telegram/bot/download.ts +++ b/src/telegram/bot/download.ts @@ -53,9 +53,9 @@ downloadDp.onNewMessage(async (msg) => { }) const settings = await getPeerSettings(msg.chat) - if (settings.preferredOutput) { + if (settings.preferredOutput || msg.chat.type !== "user") { const res = await onOutputSelected( - settings.preferredOutput, + settings.preferredOutput || "auto", req.result, args => msg.client.editMessage({ ...args, message: reply }), { e, t },