Skip to content

Commit a4ba7e6

Browse files
committed
feat: Implemeht group chart support
1 parent 6221dd0 commit a4ba7e6

8 files changed

Lines changed: 72 additions & 41 deletions

File tree

locales/en.ftl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ error = { error-title }: { $message } { $error-emoticon }
55
error-not-url = i couldn't find url in your message
66
error-request-not-found = looks like i forgot your link, try sending it again
77
error-not-button-owner = looks like this button is not yours (¬_¬")
8+
error-admin-button = only admins can touch this button!!
89
error-too-large = sorry, but this file is too big - telegram doesn't allow me to upload it
910
error-invalid-response = server response is invalid, maybe it's down or encountered an internal error
1011
error-unresponsive = couldn't connect to this server, maybe it's down...
@@ -71,4 +72,4 @@ stats-global = i helped with downloading { $count } times! (˶ᵔ ᵕ ᵔ˶)
7172
info =
7273
running { $name }@{ $version }
7374
sources: { $repository }
74-
report bugs: { $bugs }
75+
report bugs: { $bugs }

locales/ru.ftl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ error = { error-title }: { $message } { $error-emoticon }
55
error-not-url = я не нашёл ссылки в твоём сообщении
66
error-request-not-found = похоже я потерял твою ссылку, можешь отправить её снова?
77
error-not-button-owner = похоже что эта кнопка не твоя (¬_¬")
8+
error-admin-button = только админам можно тыкать эту кнопку!!
89
error-too-large = этот файл слишком большой, к сожалению тг не даёт его загрузить
910
error-invalid-response = сервер некорректно ответил, возможно он столкнулся с внутренней ошибкой или лежит
1011
error-unresponsive = не удалось подключиться к серверу, наверное он лежит...
@@ -57,4 +58,4 @@ stats-global = я помог с загрузкой { $count } раз! (˶ᵔ ᵕ
5758
info =
5859
выполняется { $name }@{ $version }
5960
сурсы: { $repository }
60-
репорт багов: { $bugs }
61+
репорт багов: { $bugs }

src/telegram/bot/download.ts

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import type { BusinessCallbackQueryContext, CallbackQueryContext, InlineCallbackQueryContext } from "@mtcute/dispatcher"
12
import type { InputMediaLike, Peer } from "@mtcute/node"
23

34
import { randomUUID } from "node:crypto"
4-
import { Dispatcher, filters } from "@mtcute/dispatcher"
5+
import { Dispatcher } from "@mtcute/dispatcher"
56
import { BotInline, BotKeyboard } from "@mtcute/node"
67

78
import type { MediaRequest } from "@/core/data/request"
89
import { createRequest, getRequest } from "@/core/data/request"
10+
import type { Settings } from "@/core/data/settings"
911
import { incrementDownloadCount } from "@/core/data/stats"
1012
import {
1113
getOutputSelectionMessage,
@@ -18,8 +20,8 @@ import { evaluatorsFor } from "@/telegram/helpers/text"
1820

1921
export const downloadDp = Dispatcher.child()
2022

21-
downloadDp.onNewMessage(filters.chat("user"), async (msg) => {
22-
const { e, t } = await evaluatorsFor(msg.sender)
23+
downloadDp.onNewMessage(async (msg) => {
24+
const { e, t } = await evaluatorsFor(msg.chat)
2325

2426
if (msg.text === "meow") {
2527
await msg.replyText("meow :з")
@@ -31,7 +33,8 @@ downloadDp.onNewMessage(filters.chat("user"), async (msg) => {
3133
const req = await createRequest(extractedUrl || msg.text, msg.sender.id)
3234

3335
if (!req.success) {
34-
await msg.replyText(t("error", { message: e(req.error) }))
36+
if (msg.chat.type === "user")
37+
await msg.replyText(t("error", { message: e(req.error) }))
3538
return
3639
}
3740

@@ -45,16 +48,16 @@ downloadDp.onNewMessage(filters.chat("user"), async (msg) => {
4548
]),
4649
})
4750

48-
const settings = await getPeerSettings(msg.sender)
51+
const settings = await getPeerSettings(msg.chat)
4952
if (settings.preferredOutput) {
5053
await onOutputSelected(
5154
settings.preferredOutput,
5255
req.result,
5356
args => msg.client.editMessage({ ...args, message: reply }),
5457
{ e, t },
55-
msg.sender,
56-
!!settings.preferredAttribution,
58+
settings,
5759
({ medias }) => msg.replyMediaGroup(medias),
60+
msg.sender,
5861
)
5962
}
6063
})
@@ -102,8 +105,13 @@ downloadDp.onInlineQuery(async (ctx) => {
102105
})
103106

104107
downloadDp.onAnyCallbackQuery(OutputButton.filter(), async (upd) => {
105-
const settings = await getPeerSettings(upd.user)
106-
const { t, e } = await evaluatorsFor(upd.user)
108+
// When passing a filter to onAnyCallbackQuery it applies a modification to the update object, which makes it lose its enum-like properties.
109+
// To access the original update object, we need to cast it to the original type.
110+
const rawUpd = upd as unknown as (CallbackQueryContext | InlineCallbackQueryContext | BusinessCallbackQueryContext)
111+
112+
const peer = rawUpd._name === "callback_query" ? rawUpd.chat : upd.user
113+
const settings = await getPeerSettings(peer)
114+
const { t, e } = await evaluatorsFor(peer)
107115
const { output: outputType, request: requestId } = upd.match
108116

109117
const request = await getRequest(requestId)
@@ -118,9 +126,9 @@ downloadDp.onAnyCallbackQuery(OutputButton.filter(), async (upd) => {
118126
request,
119127
args => upd.editMessage(args),
120128
{ t, e },
129+
settings,
130+
({ medias }) => upd.client.sendMediaGroup(peer.id, medias),
121131
upd.user,
122-
!!settings.preferredAttribution,
123-
({ medias }) => upd.client.sendMediaGroup(upd.user.id, medias),
124132
)
125133
})
126134

@@ -136,9 +144,9 @@ downloadDp.onChosenInlineResult(async (upd) => {
136144
request,
137145
args => upd.editMessage({ ...args, messageId }),
138146
await evaluatorsFor(upd.user),
139-
upd.user,
140-
!!settings.preferredAttribution,
147+
settings,
141148
({ medias }) => upd.client.sendMediaGroup(upd.user.id, medias),
149+
upd.user,
142150
)
143151
}
144152
})
@@ -148,12 +156,12 @@ async function onOutputSelected(
148156
request: MediaRequest | undefined,
149157
editMessage: (edit: { text?: string, media?: InputMediaLike }) => Promise<unknown>,
150158
{ t, e }: Evaluators,
151-
peer: Peer,
152-
leaveSourceLink: boolean,
159+
settings: Settings,
153160
sendGroup: (send: { medias: InputMediaLike[] }) => Promise<unknown>,
161+
sender: Peer,
154162
) {
155163
await editMessage({ text: t("downloading-title") })
156-
const res = await handleMediaDownload(outputType, request, peer)
164+
const res = await handleMediaDownload(outputType, request, settings)
157165
if (!res.success) {
158166
await editMessage({ text: t("error", { message: e(res.error) }) })
159167
return
@@ -168,10 +176,9 @@ async function onOutputSelected(
168176
await sendGroup({ medias: chunk })
169177
}
170178
} else {
171-
await editMessage({ media: res.result[0] })
172-
await editMessage({ text: (leaveSourceLink && request?.url) || "" })
179+
await editMessage({ media: res.result[0], text: (!!settings.preferredAttribution && request?.url) || "" })
173180
}
174181

175-
incrementDownloadCount(peer.id)
182+
incrementDownloadCount(sender.id)
176183
.catch(() => { /* noop */ })
177184
}

src/telegram/bot/info.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ import { evaluatorsFor } from "@/telegram/helpers/text"
66
export const infoDp = Dispatcher.child()
77

88
settingsDp.onNewMessage(filters.command("info"), async (msg) => {
9-
const { t } = await evaluatorsFor(msg.sender)
9+
const { t } = await evaluatorsFor(msg.chat)
1010
await msg.replyText(t("info", { bugs, name, repository, version }))
1111
})

src/telegram/bot/settings.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Peer, TelegramClient, User } from "@mtcute/node"
12
import { Dispatcher, filters, PropagationAction } from "@mtcute/dispatcher"
23
import { BotKeyboard } from "@mtcute/node"
34

@@ -34,6 +35,13 @@ function settingsMessage(e: TextEvaluator, settings: Settings) {
3435
}
3536
}
3637

38+
async function isAdmin(client: Pick<TelegramClient, "getChatMember">, chat: Peer, user: User) {
39+
if (chat.type !== "chat")
40+
return true
41+
const member = await client.getChatMember({ chatId: chat, userId: user })
42+
return member?.status === "admin" || member?.status === "creator"
43+
}
44+
3745
function settingEditMessage(e: TextEvaluator, settings: Settings, setting: keyof Settings) {
3846
const menu = getSettingMenu(settings, setting)
3947
return {
@@ -49,16 +57,21 @@ function settingEditMessage(e: TextEvaluator, settings: Settings, setting: keyof
4957
settingsDp.onNewMessage(
5058
filters.or(filters.command("settings"), filters.deeplink(["settings"])),
5159
async (msg) => {
52-
const { e } = await evaluatorsFor(msg.sender)
53-
const settings = await getPeerSettings(msg.sender)
60+
const { e } = await evaluatorsFor(msg.chat)
61+
const settings = await getPeerSettings(msg.chat)
5462
const { text, ...props } = settingsMessage(e, settings)
5563
await msg.replyText(text, props)
5664
},
5765
)
5866

59-
settingsDp.onAnyCallbackQuery(SettingButton.filter(), async (upd) => {
60-
const { e } = await evaluatorsFor(upd.user)
61-
const settings = await getPeerSettings(upd.user)
67+
settingsDp.onCallbackQuery(SettingButton.filter(), async (upd) => {
68+
const { e, t } = await evaluatorsFor(upd.chat)
69+
if (!await isAdmin(upd.client, upd.chat, upd.user)) {
70+
return await upd.answer({
71+
text: t("error-admin-button"),
72+
})
73+
}
74+
const settings = await getPeerSettings(upd.chat)
6275
if (upd.match.setting === "back") {
6376
await upd.editMessage(settingsMessage(e, settings))
6477
return
@@ -69,36 +82,46 @@ settingsDp.onAnyCallbackQuery(SettingButton.filter(), async (upd) => {
6982
await upd.editMessage(settingEditMessage(e, settings, upd.match.setting))
7083
})
7184

72-
settingsDp.onAnyCallbackQuery(SettingUpdateButton.filter(), async (upd, state) => {
85+
settingsDp.onCallbackQuery(SettingUpdateButton.filter(), async (upd, state) => {
7386
if (!isValidSettingKey(upd.match.setting))
7487
return // Invalid key
88+
if (!await isAdmin(upd.client, upd.chat, upd.user)) {
89+
const { t } = await evaluatorsFor(upd.chat)
90+
return await upd.answer({
91+
text: t("error-admin-button"),
92+
})
93+
}
7594

76-
const settings = await getPeerSettings(upd.user)
95+
const settings = await getPeerSettings(upd.chat)
7796
const valueIndex = +upd.match.value
7897
const value = getSettingValues(upd.match.setting)[valueIndex]
7998
if (value === customValue) {
80-
const { e, t } = await evaluatorsFor(upd.user)
99+
const { e, t } = await evaluatorsFor(upd.chat)
81100
const { text: _, ...props } = settingEditMessage(e, settings, upd.match.setting)
82101
await upd.editMessage({ text: t("setting-custom"), ...props })
83102
await state.enter(settingInputScene, { with: { setting: upd.match.setting } })
84103
return
85104
}
86-
const newSettings = await updateSetting(upd.match.setting, value, upd.user.id)
105+
const newSettings = await updateSetting(upd.match.setting, value, upd.chat.id)
87106

88107
// We're getting evaluator AFTER the possible locale update
89-
const { e } = await evaluatorsFor(upd.user)
108+
const { e } = await evaluatorsFor(upd.chat)
90109
await upd.editMessage(settingEditMessage(e, newSettings ?? settings, upd.match.setting))
91110
})
92111

93112
settingInputScene.onNewMessage(async (upd, state) => {
113+
if (upd.sender.type !== "user" || !await isAdmin(upd.client, upd.chat, upd.sender)) {
114+
return
115+
}
116+
94117
const stateData = await state.get()
95118
if (!stateData) {
96119
await state.exit()
97120
return
98121
}
99122

100-
const { t } = await evaluatorsFor(upd.sender)
101-
await updateSetting(stateData.setting, upd.text, upd.sender.id)
123+
const { t } = await evaluatorsFor(upd.chat)
124+
await updateSetting(stateData.setting, upd.text, upd.chat.id)
102125
await upd.replyText(t("setting-saved"))
103126

104127
await state.exit()

src/telegram/bot/start.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ import { translatorFor } from "@/telegram/helpers/i18n"
33

44
export const startDp = Dispatcher.child()
55
startDp.onNewMessage(filters.start, async (msg) => {
6-
const t = await translatorFor(msg.sender)
6+
const t = await translatorFor(msg.chat)
77
await msg.replyText(t("start"))
88
})

src/telegram/bot/stats.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import { evaluatorsFor } from "@/telegram/helpers/text"
66
export const statsDp = Dispatcher.child()
77

88
statsDp.onNewMessage(filters.command("stats"), async (msg) => {
9-
const { t } = await evaluatorsFor(msg.sender)
9+
const { t } = await evaluatorsFor(msg.chat)
1010
const count = await getDownloadStats()
1111
await msg.replyText(t("stats-global", { count }))
1212
})
1313

1414
statsDp.onNewMessage(filters.command("mystats"), async (msg) => {
15-
const { t } = await evaluatorsFor(msg.sender)
15+
const { t } = await evaluatorsFor(msg.chat)
1616
const id = msg.sender.id
1717
const count = await getDownloadStats(id)
1818
await msg.replyText(t("stats-personal", { count }))

src/telegram/helpers/handler.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { InputMediaLike, Peer } from "@mtcute/node"
1+
import type { InputMediaLike } from "@mtcute/node"
22
import type { GeneralTrack, ImageTrack, VideoTrack } from "mediainfo.js"
33

44
import { CallbackDataBuilder } from "@mtcute/dispatcher"
@@ -8,13 +8,13 @@ import type { ApiServer, CobaltDownloadParams } from "@/core/data/cobalt"
88
import type { DownloadedMediaContent } from "@/core/data/cobalt/tunnel"
99
import type { MediaRequest } from "@/core/data/request"
1010
import { finishRequest, outputOptions } from "@/core/data/request"
11+
import type { Settings } from "@/core/data/settings"
1112
import type { Result } from "@/core/utils/result"
1213
import { error, ok } from "@/core/utils/result"
1314
import type { Text } from "@/core/utils/text"
1415
import { translatable } from "@/core/utils/text"
1516
import { urlWithAuthSchema } from "@/core/utils/url"
1617
import { env } from "@/telegram/helpers/env"
17-
import { getPeerSettings } from "@/telegram/helpers/settings"
1818

1919
export const OutputButton = new CallbackDataBuilder("dl", "output", "request")
2020
export const getOutputSelectionMessage = (requestId: string) => ({
@@ -108,10 +108,9 @@ function getApiEndpoints(override: string | null): Result<ApiServer[], Text> {
108108
)
109109
}
110110

111-
export async function handleMediaDownload(outputType: string, request: MediaRequest | undefined, peer: Peer): Promise<Result<InputMediaLike[], Text>> {
111+
export async function handleMediaDownload(outputType: string, request: MediaRequest | undefined, settings: Settings): Promise<Result<InputMediaLike[], Text>> {
112112
if (!request)
113113
return error(translatable("error-request-not-found"))
114-
const settings = await getPeerSettings(peer)
115114
const endpoints = getApiEndpoints(settings.instanceOverride)
116115
if (!endpoints.success)
117116
return endpoints

0 commit comments

Comments
 (0)