@hasna/connectors 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (351) hide show
  1. package/bin/index.js +1561 -1277
  2. package/bin/mcp.js +534 -213
  3. package/bin/serve.js +1218 -258
  4. package/connectors/adobe/package.json +1 -1
  5. package/connectors/aitableai/package.json +1 -1
  6. package/connectors/alchemy/package.json +1 -1
  7. package/connectors/alerty/package.json +1 -1
  8. package/connectors/alienvault/package.json +1 -1
  9. package/connectors/alphamoon/package.json +1 -1
  10. package/connectors/alttextai/package.json +1 -1
  11. package/connectors/amadeus/package.json +1 -1
  12. package/connectors/amadeus/src/api/client.ts +6 -6
  13. package/connectors/amadeus/src/cli/index.ts +6 -6
  14. package/connectors/amadeus/src/utils/output.ts +5 -5
  15. package/connectors/amazon/package.json +1 -1
  16. package/connectors/amilia/package.json +1 -1
  17. package/connectors/amqpsender/package.json +1 -1
  18. package/connectors/announcekit/package.json +1 -1
  19. package/connectors/anthropic/.env.example +1 -1
  20. package/connectors/anyscale/package.json +1 -1
  21. package/connectors/apiary/package.json +1 -1
  22. package/connectors/apiflash/package.json +1 -1
  23. package/connectors/apitemplateio/package.json +1 -1
  24. package/connectors/apptivegrid/package.json +1 -1
  25. package/connectors/assemblyai/package.json +1 -1
  26. package/connectors/autom/package.json +1 -1
  27. package/connectors/automizy/package.json +1 -1
  28. package/connectors/autopilot/package.json +1 -1
  29. package/connectors/awscognito/package.json +1 -1
  30. package/connectors/bamboohr/package.json +1 -1
  31. package/connectors/bannerbear/package.json +1 -1
  32. package/connectors/baseten/package.json +1 -1
  33. package/connectors/box/package.json +1 -1
  34. package/connectors/brevo/package.json +1 -1
  35. package/connectors/calendly/package.json +1 -1
  36. package/connectors/cerebras/package.json +1 -1
  37. package/connectors/chargebee/package.json +1 -1
  38. package/connectors/clearbit/package.json +1 -1
  39. package/connectors/cloudflare-workers/package.json +1 -1
  40. package/connectors/coda/package.json +1 -1
  41. package/connectors/coingecko/package.json +1 -1
  42. package/connectors/convertkit/package.json +1 -1
  43. package/connectors/customerio/package.json +1 -1
  44. package/connectors/databricks/package.json +1 -1
  45. package/connectors/datadog/package.json +1 -1
  46. package/connectors/datasoap/README.md +2 -2
  47. package/connectors/datasoap/package.json +1 -1
  48. package/connectors/datumbox/README.md +2 -2
  49. package/connectors/datumbox/package.json +1 -1
  50. package/connectors/dayschedule/README.md +2 -2
  51. package/connectors/dayschedule/package.json +1 -1
  52. package/connectors/deepgram/package.json +1 -1
  53. package/connectors/deepinfra/README.md +2 -2
  54. package/connectors/deepinfra/package.json +1 -1
  55. package/connectors/deepl/README.md +2 -2
  56. package/connectors/deepl/package.json +1 -1
  57. package/connectors/deepseek/package.json +1 -1
  58. package/connectors/demio/README.md +2 -2
  59. package/connectors/demio/package.json +1 -1
  60. package/connectors/descript/README.md +2 -2
  61. package/connectors/descript/package.json +1 -1
  62. package/connectors/detrack/README.md +2 -2
  63. package/connectors/detrack/package.json +1 -1
  64. package/connectors/devcycle/README.md +2 -2
  65. package/connectors/devcycle/package.json +1 -1
  66. package/connectors/dhl/README.md +2 -2
  67. package/connectors/dhl/package.json +1 -1
  68. package/connectors/docusign/package.json +1 -1
  69. package/connectors/dropbox/package.json +1 -1
  70. package/connectors/fal/package.json +1 -1
  71. package/connectors/fathom/package.json +1 -1
  72. package/connectors/fireworks/package.json +1 -1
  73. package/connectors/gitlab/package.json +1 -1
  74. package/connectors/googlechat/README.md +1 -2
  75. package/connectors/googledrive/src/api/client.ts +5 -5
  76. package/connectors/googledrive/src/api/files.ts +11 -11
  77. package/connectors/googledrive/src/api/folders.ts +2 -2
  78. package/connectors/googledrive/src/cli/index.ts +4 -4
  79. package/connectors/googledrive/src/utils/output.ts +3 -3
  80. package/connectors/googleforms/README.md +1 -2
  81. package/connectors/googleperspective/README.md +1 -2
  82. package/connectors/googlephotos/src/cli/index.ts +1 -1
  83. package/connectors/grafana/package.json +1 -1
  84. package/connectors/growbots/README.md +2 -2
  85. package/connectors/growbots/package.json +1 -1
  86. package/connectors/gumroad/package.json +1 -1
  87. package/connectors/gupshup/README.md +2 -2
  88. package/connectors/gupshup/package.json +1 -1
  89. package/connectors/guru/README.md +2 -2
  90. package/connectors/guru/package.json +1 -1
  91. package/connectors/hackernews/README.md +2 -2
  92. package/connectors/hackernews/package.json +1 -1
  93. package/connectors/halopsa/README.md +2 -2
  94. package/connectors/halopsa/package.json +1 -1
  95. package/connectors/handwrytten/README.md +2 -2
  96. package/connectors/handwrytten/package.json +1 -1
  97. package/connectors/hansei/README.md +2 -2
  98. package/connectors/hansei/package.json +1 -1
  99. package/connectors/harrypotterapi/README.md +2 -2
  100. package/connectors/harrypotterapi/package.json +1 -1
  101. package/connectors/harvest/README.md +2 -2
  102. package/connectors/harvest/package.json +1 -1
  103. package/connectors/headlesstesting/README.md +2 -2
  104. package/connectors/headlesstesting/package.json +1 -1
  105. package/connectors/homeassistant/package.json +1 -1
  106. package/connectors/klaviyo/package.json +1 -1
  107. package/connectors/lemonsqueezy/package.json +1 -1
  108. package/connectors/lighthouse/src/api/index.ts +1 -1
  109. package/connectors/linkedin/package.json +1 -1
  110. package/connectors/linkish/README.md +2 -2
  111. package/connectors/linkish/package.json +1 -1
  112. package/connectors/liveagent/README.md +2 -2
  113. package/connectors/liveagent/package.json +1 -1
  114. package/connectors/localazy/README.md +2 -2
  115. package/connectors/localazy/package.json +1 -1
  116. package/connectors/loginradius/README.md +2 -2
  117. package/connectors/loginradius/package.json +1 -1
  118. package/connectors/lokalise/README.md +2 -2
  119. package/connectors/lokalise/package.json +1 -1
  120. package/connectors/lonescale/README.md +2 -2
  121. package/connectors/lonescale/package.json +1 -1
  122. package/connectors/lucidchart/README.md +2 -2
  123. package/connectors/lucidchart/package.json +1 -1
  124. package/connectors/luma/package.json +1 -1
  125. package/connectors/magento2/README.md +2 -2
  126. package/connectors/magento2/package.json +1 -1
  127. package/connectors/mailcheck/README.md +2 -2
  128. package/connectors/mailcheck/package.json +1 -1
  129. package/connectors/mailchimp/package.json +1 -1
  130. package/connectors/mailerlite/README.md +2 -2
  131. package/connectors/mailerlite/package.json +1 -1
  132. package/connectors/mailersend/README.md +2 -2
  133. package/connectors/mailersend/package.json +1 -1
  134. package/connectors/mailify/README.md +2 -2
  135. package/connectors/mailify/package.json +1 -1
  136. package/connectors/mailjet/README.md +2 -2
  137. package/connectors/mailjet/package.json +1 -1
  138. package/connectors/mailmodo/README.md +2 -2
  139. package/connectors/mailmodo/package.json +1 -1
  140. package/connectors/malcore/README.md +2 -2
  141. package/connectors/malcore/package.json +1 -1
  142. package/connectors/mandrill/README.md +2 -2
  143. package/connectors/mandrill/package.json +1 -1
  144. package/connectors/marcomrobot/README.md +2 -2
  145. package/connectors/marcomrobot/package.json +1 -1
  146. package/connectors/marketingmasterio/README.md +2 -2
  147. package/connectors/marketingmasterio/package.json +1 -1
  148. package/connectors/marketplacer/README.md +2 -2
  149. package/connectors/marketplacer/package.json +1 -1
  150. package/connectors/marketstack/README.md +2 -2
  151. package/connectors/marketstack/package.json +1 -1
  152. package/connectors/matrix/README.md +1 -1
  153. package/connectors/matrix/package.json +1 -1
  154. package/connectors/mattermost/README.md +1 -1
  155. package/connectors/mattermost/package.json +1 -1
  156. package/connectors/mautic/README.md +1 -1
  157. package/connectors/mautic/package.json +1 -1
  158. package/connectors/mav/README.md +1 -1
  159. package/connectors/mav/package.json +1 -1
  160. package/connectors/maverick/README.md +1 -1
  161. package/connectors/maverick/package.json +1 -1
  162. package/connectors/mem/README.md +1 -1
  163. package/connectors/mem/package.json +1 -1
  164. package/connectors/messagebird/package.json +1 -1
  165. package/connectors/metabase/README.md +1 -1
  166. package/connectors/metabase/package.json +1 -1
  167. package/connectors/miro/package.json +1 -1
  168. package/connectors/mocean/package.json +1 -1
  169. package/connectors/modal/package.json +1 -1
  170. package/connectors/monday/package.json +1 -1
  171. package/connectors/monicacrm/package.json +1 -1
  172. package/connectors/moonclerk/package.json +1 -1
  173. package/connectors/moonmail/package.json +1 -1
  174. package/connectors/morph/package.json +1 -1
  175. package/connectors/mqtt/package.json +1 -1
  176. package/connectors/msg91/package.json +1 -1
  177. package/connectors/mubert/package.json +1 -1
  178. package/connectors/murf/package.json +1 -1
  179. package/connectors/mux/package.json +1 -1
  180. package/connectors/netlify/package.json +1 -1
  181. package/connectors/okta/package.json +1 -1
  182. package/connectors/paddle/package.json +1 -1
  183. package/connectors/perplexity/package.json +1 -1
  184. package/connectors/pinterest/package.json +1 -1
  185. package/connectors/pipedrive/package.json +1 -1
  186. package/connectors/plaid/package.json +1 -1
  187. package/connectors/quickchart/README.md +2 -2
  188. package/connectors/quintadb/README.md +2 -2
  189. package/connectors/rabbitmq/README.md +2 -2
  190. package/connectors/radar/README.md +2 -2
  191. package/connectors/raindrop/README.md +2 -2
  192. package/connectors/rapid7insightvm/README.md +2 -2
  193. package/connectors/raventools/README.md +2 -2
  194. package/connectors/rawgvideogamesdatabase/README.md +2 -2
  195. package/connectors/rdstationcrm/README.md +2 -2
  196. package/connectors/readai/README.md +2 -2
  197. package/connectors/roboflow/package.json +1 -1
  198. package/connectors/runway/package.json +1 -1
  199. package/connectors/sendgrid/package.json +1 -1
  200. package/connectors/shuffler/package.json +1 -1
  201. package/connectors/sierrainteractive/package.json +1 -1
  202. package/connectors/sifter/package.json +1 -1
  203. package/connectors/signaturit/package.json +1 -1
  204. package/connectors/signl4/package.json +1 -1
  205. package/connectors/simpleem/package.json +1 -1
  206. package/connectors/simplehash/package.json +1 -1
  207. package/connectors/simplekpi/package.json +1 -1
  208. package/connectors/simplelocalize/package.json +1 -1
  209. package/connectors/simplero/package.json +1 -1
  210. package/connectors/smartrecruiters/package.json +1 -1
  211. package/connectors/smtp2go/package.json +1 -1
  212. package/connectors/snowflake/package.json +1 -1
  213. package/connectors/sparkpost/package.json +1 -1
  214. package/connectors/split-io/package.json +1 -1
  215. package/connectors/split-io/src/api/client.test.ts +20 -0
  216. package/connectors/split-io/src/cli/index.ts +9 -9
  217. package/connectors/sprinklr/package.json +1 -1
  218. package/connectors/sprout-social/package.json +1 -1
  219. package/connectors/square/package.json +1 -1
  220. package/connectors/sse-trigger/package.json +1 -1
  221. package/connectors/stability-api-platform/AGENTS.md +1 -1
  222. package/connectors/standout/package.json +1 -1
  223. package/connectors/statuspage/package.json +1 -1
  224. package/connectors/stoplight/package.json +1 -1
  225. package/connectors/stoplight-api-platform/package.json +1 -1
  226. package/connectors/strand-ai/CLAUDE.md +1 -1
  227. package/connectors/stripe-financial-connections/package.json +1 -1
  228. package/connectors/stripe-terminal/package.json +1 -1
  229. package/connectors/stytch/package.json +1 -1
  230. package/connectors/sumo-logic/package.json +1 -1
  231. package/connectors/supabase/package.json +1 -1
  232. package/connectors/supabase-api-platform/package.json +1 -1
  233. package/connectors/sysdig/package.json +1 -1
  234. package/connectors/tableau/package.json +1 -1
  235. package/connectors/takecareos/README.md +2 -2
  236. package/connectors/takecareos/package.json +1 -1
  237. package/connectors/telegram/src/api/bot.ts +20 -0
  238. package/connectors/telegram/src/api/client.ts +32 -0
  239. package/connectors/telegram/src/api/files.test.ts +103 -0
  240. package/connectors/telegram/src/api/index.ts +1 -0
  241. package/connectors/telegram/src/api/messages.test.ts +8 -2
  242. package/connectors/telegram/src/cli/files.test.ts +40 -0
  243. package/connectors/telegram/src/cli/files.ts +38 -0
  244. package/connectors/telegram/src/cli/index.ts +27 -30
  245. package/connectors/telegram/src/cli/updates.test.ts +176 -0
  246. package/connectors/telegram/src/cli/updates.ts +95 -0
  247. package/connectors/telegram/src/index.ts +1 -0
  248. package/connectors/telegram/src/types/index.ts +13 -0
  249. package/connectors/terraform-cloud/package.json +1 -1
  250. package/connectors/textit/package.json +1 -1
  251. package/connectors/thousandeyes/package.json +1 -1
  252. package/connectors/ticket-tailor/package.json +1 -1
  253. package/connectors/ticketsource/package.json +1 -1
  254. package/connectors/tiktokads/README.md +1 -1
  255. package/connectors/time-saved/package.json +1 -1
  256. package/connectors/tipalti/package.json +1 -1
  257. package/connectors/tomtom/AGENTS.md +1 -1
  258. package/connectors/totp/package.json +1 -1
  259. package/connectors/traverse/package.json +1 -1
  260. package/connectors/travo-ai/package.json +1 -1
  261. package/connectors/trigger-dev/package.json +1 -1
  262. package/connectors/trigger-dev-api-platform/package.json +1 -1
  263. package/connectors/turbot-pipes/package.json +1 -1
  264. package/connectors/turso/package.json +1 -1
  265. package/connectors/turso-api-platform/package.json +1 -1
  266. package/connectors/tyk-api-platform/package.json +1 -1
  267. package/connectors/umami/package.json +1 -1
  268. package/connectors/unifold/package.json +1 -1
  269. package/connectors/unione/package.json +1 -1
  270. package/connectors/uniprot/README.md +1 -1
  271. package/connectors/uniswap-api/package.json +1 -1
  272. package/connectors/ups/package.json +1 -1
  273. package/connectors/usereframe/package.json +1 -1
  274. package/connectors/vanta/package.json +1 -1
  275. package/connectors/vela/package.json +1 -1
  276. package/connectors/velum-labs/package.json +1 -1
  277. package/connectors/vercel/package.json +1 -1
  278. package/connectors/vercel-blob/package.json +1 -1
  279. package/connectors/vercel-blob-platform/package.json +1 -1
  280. package/connectors/vercel-edge-config/package.json +1 -1
  281. package/connectors/vercel-edge-config-platform/package.json +1 -1
  282. package/connectors/vertex-ai/package.json +1 -1
  283. package/connectors/vidyard/package.json +1 -1
  284. package/connectors/vonage/package.json +1 -1
  285. package/connectors/voygr/package.json +1 -1
  286. package/connectors/wait/package.json +1 -1
  287. package/connectors/walmart-marketplace/package.json +1 -1
  288. package/connectors/wato/package.json +1 -1
  289. package/connectors/wave-accounting/package.json +1 -1
  290. package/connectors/wayco/package.json +1 -1
  291. package/connectors/webpagetest/package.json +1 -1
  292. package/connectors/weezevent/package.json +1 -1
  293. package/connectors/wikipedia/src/api/client.ts +1 -1
  294. package/connectors/wildcard/package.json +1 -1
  295. package/connectors/windmill/package.json +1 -1
  296. package/connectors/wise/package.json +1 -1
  297. package/connectors/withai/package.json +1 -1
  298. package/connectors/wiz/CLAUDE.md +1 -1
  299. package/connectors/wiz/package.json +1 -1
  300. package/connectors/wordfence/package.json +1 -1
  301. package/connectors/wordfence-api-platform/package.json +1 -1
  302. package/connectors/workos/package.json +1 -1
  303. package/connectors/write-binary-file/package.json +1 -1
  304. package/connectors/xai/.env.example +1 -1
  305. package/connectors/xai-api-platform/src/api/client.test.ts +1 -1
  306. package/connectors/xai-api-platform/src/cli/index.ts +1 -1
  307. package/connectors/xai-api-platform/src/utils/config.ts +1 -1
  308. package/connectors/xai-grok/.env.example +1 -1
  309. package/connectors/xai-grok/src/api/client.test.ts +3 -3
  310. package/connectors/xai-grok/src/cli/index.ts +1 -1
  311. package/connectors/xai-grok/src/utils/config.ts +1 -1
  312. package/connectors/ynab/package.json +1 -1
  313. package/connectors/you-com/README.md +2 -2
  314. package/connectors/you-com/package.json +1 -1
  315. package/connectors/yousearch/README.md +1 -1
  316. package/connectors/yousign/package.json +1 -1
  317. package/connectors/zendesk/.env.example +4 -4
  318. package/connectors/zendesk/CLAUDE.md +2 -4
  319. package/connectors/zendesk/Makefile +21 -6
  320. package/connectors/zendesk/README.md +6 -10
  321. package/connectors/zendesk/SCAFFOLD.md +8 -7
  322. package/connectors/zendesk/nginx-connector.conf +8 -8
  323. package/connectors/zendesk/nginx.conf +8 -8
  324. package/connectors/zendesk/src/cli/index.ts +19 -8
  325. package/connectors/zendesk/src/server/index.ts +1 -1
  326. package/connectors/zendesk/src/utils/config.test.ts +70 -0
  327. package/connectors/zendesk/src/utils/config.ts +12 -2
  328. package/connectors/zendesk/src/utils/remote-url.test.ts +72 -0
  329. package/connectors/zerosettle/package.json +1 -1
  330. package/connectors/zoho-bigin/CLAUDE.md +1 -1
  331. package/connectors/zoho-campaigns/package.json +1 -1
  332. package/connectors/zoho-sign/package.json +1 -1
  333. package/connectors/zoho-subscriptions/CLAUDE.md +1 -1
  334. package/connectors/zohoprojects/CLAUDE.md +1 -1
  335. package/connectors/zolvo/package.json +1 -1
  336. package/connectors/zoom-api/package.json +1 -1
  337. package/connectors/zymbly/README.md +2 -2
  338. package/connectors/zymbly/package.json +1 -1
  339. package/dashboard/dist/assets/index-8uoGZF7B.js +280 -0
  340. package/dashboard/dist/index.html +1 -1
  341. package/dist/db/locks.d.ts +1 -1
  342. package/dist/index.js +27 -17
  343. package/dist/lib/connector-resolver.d.ts +1 -1
  344. package/dist/lib/llm.d.ts +1 -1
  345. package/dist/lib/open-browser.d.ts +18 -0
  346. package/dist/lib/open-browser.test.d.ts +1 -0
  347. package/package.json +16 -6
  348. package/connectors/tiktok-events-api/.test-home/.bun/install/cache/@t@/b0fec00ef4c1a326.pile +0 -0
  349. package/dashboard/dist/assets/index-XPlN-9Xy.js +0 -280
  350. package/dist/cli/commands/sync.d.ts +0 -2
  351. package/dist/db/pg-migrations.d.ts +0 -6
@@ -49,6 +49,6 @@
49
49
  },
50
50
  "repository": {
51
51
  "type": "git",
52
- "url": "git+https://github.com/hasna/open-connectors.git"
52
+ "url": "git+https://github.com/hasna/apps.git"
53
53
  }
54
54
  }
@@ -49,6 +49,6 @@
49
49
  },
50
50
  "repository": {
51
51
  "type": "git",
52
- "url": "git+https://github.com/hasna/open-connectors.git"
52
+ "url": "git+https://github.com/hasna/apps.git"
53
53
  }
54
54
  }
@@ -47,6 +47,6 @@
47
47
  },
48
48
  "repository": {
49
49
  "type": "git",
50
- "url": "git+https://github.com/hasna/open-connectors.git"
50
+ "url": "git+https://github.com/hasna/apps.git"
51
51
  }
52
52
  }
@@ -50,6 +50,6 @@
50
50
  },
51
51
  "repository": {
52
52
  "type": "git",
53
- "url": "git+https://github.com/hasna/open-connectors.git"
53
+ "url": "git+https://github.com/hasna/apps.git"
54
54
  }
55
55
  }
@@ -49,6 +49,6 @@
49
49
  },
50
50
  "repository": {
51
51
  "type": "git",
52
- "url": "git+https://github.com/hasna/open-connectors.git"
52
+ "url": "git+https://github.com/hasna/apps.git"
53
53
  }
54
54
  }
@@ -47,6 +47,6 @@
47
47
  },
48
48
  "repository": {
49
49
  "type": "git",
50
- "url": "git+https://github.com/hasna/open-connectors.git"
50
+ "url": "git+https://github.com/hasna/apps.git"
51
51
  }
52
52
  }
@@ -1,6 +1,6 @@
1
1
  # @hasna/connect-takecareos
2
2
 
3
- TakeCareOS connector for the [open-connectors](https://github.com/hasna/open-connectors) registry.
3
+ TakeCareOS connector for the [connectors](https://github.com/hasna/apps) registry.
4
4
 
5
5
  TakeCareOS is a home-care agency operating system. This connector wraps its public
6
6
  REST API to manage clients (care recipients), caregivers, shifts, incident reports,
@@ -18,7 +18,7 @@ The connector authenticates with a Bearer API key. Provide it via environment
18
18
  variable, a CLI flag, or a stored profile:
19
19
 
20
20
  ```bash
21
- export TAKECAREOS_API_KEY=your-api-key
21
+ export TAKECAREOS_API_KEY=<your-api-key>
22
22
  # optional, for a dedicated/regional host
23
23
  export TAKECAREOS_BASE_URL=https://api.takecareos.com/v1
24
24
  ```
@@ -50,6 +50,6 @@
50
50
  },
51
51
  "repository": {
52
52
  "type": "git",
53
- "url": "git+https://github.com/hasna/open-connectors.git"
53
+ "url": "git+https://github.com/hasna/apps.git"
54
54
  }
55
55
  }
@@ -59,6 +59,11 @@ export interface GetFileOptions {
59
59
  fileId: string;
60
60
  }
61
61
 
62
+ export interface DownloadFileResult {
63
+ file: TelegramFile;
64
+ data: Uint8Array;
65
+ }
66
+
62
67
  export interface BotName {
63
68
  name: string;
64
69
  }
@@ -220,4 +225,19 @@ export class BotApi {
220
225
  },
221
226
  });
222
227
  }
228
+
229
+ /**
230
+ * Resolve and download a Telegram file
231
+ */
232
+ async downloadFile(options: GetFileOptions): Promise<DownloadFileResult> {
233
+ const file = await this.getFile(options);
234
+ if (!file.file_path) {
235
+ throw new Error('Telegram did not return a downloadable file path');
236
+ }
237
+
238
+ return {
239
+ file,
240
+ data: await this.client.downloadFile(file.file_path),
241
+ };
242
+ }
223
243
  }
@@ -118,6 +118,38 @@ export class TelegramClient {
118
118
  return this.request<T>(method, { body: formData });
119
119
  }
120
120
 
121
+ /**
122
+ * Download a file returned by the Telegram Bot API
123
+ */
124
+ async downloadFile(filePath: string): Promise<Uint8Array> {
125
+ const normalizedPath = filePath.replace(/^\/+/, '');
126
+ if (!normalizedPath) {
127
+ throw new Error('Telegram file path is required');
128
+ }
129
+
130
+ const encodedPath = normalizedPath
131
+ .split('/')
132
+ .map(segment => encodeURIComponent(segment))
133
+ .join('/');
134
+ const url = `${TELEGRAM_API_BASE}/file/bot${this.botToken}/${encodedPath}`;
135
+
136
+ let response: Response;
137
+ try {
138
+ response = await fetch(url);
139
+ } catch {
140
+ throw new Error('Failed to download Telegram file');
141
+ }
142
+
143
+ if (!response.ok) {
144
+ throw new TelegramApiError(
145
+ `Telegram file download failed with HTTP ${response.status}`,
146
+ response.status
147
+ );
148
+ }
149
+
150
+ return new Uint8Array(await response.arrayBuffer());
151
+ }
152
+
121
153
  /**
122
154
  * Get a preview of the bot token (for display/debugging)
123
155
  */
@@ -0,0 +1,103 @@
1
+ import { afterEach, describe, expect, mock, test } from 'bun:test';
2
+ import { Telegram } from './index';
3
+
4
+ const originalFetch = globalThis.fetch;
5
+
6
+ afterEach(() => {
7
+ globalThis.fetch = originalFetch;
8
+ });
9
+
10
+ describe('Telegram files API', () => {
11
+ test('resolves a file ID and downloads its bytes from the authenticated file URL', async () => {
12
+ const botToken = '123456:test-token';
13
+ const fetchMock = mock(async (
14
+ input: string | URL | Request,
15
+ _init?: RequestInit
16
+ ) => {
17
+ const url = String(input);
18
+ if (url.endsWith('/getFile')) {
19
+ return Response.json({
20
+ ok: true,
21
+ result: {
22
+ file_id: 'incoming-file-id',
23
+ file_unique_id: 'unique-file-id',
24
+ file_size: 4,
25
+ file_path: 'documents/report 1.pdf',
26
+ },
27
+ });
28
+ }
29
+
30
+ return new Response(new Uint8Array([1, 2, 3, 4]));
31
+ });
32
+ globalThis.fetch = fetchMock as unknown as typeof fetch;
33
+
34
+ const telegram = new Telegram({ botToken });
35
+ const downloaded = await telegram.bot.downloadFile({
36
+ fileId: 'incoming-file-id',
37
+ });
38
+
39
+ expect(fetchMock).toHaveBeenCalledTimes(2);
40
+ expect(String(fetchMock.mock.calls[0]?.[0])).toBe(
41
+ `https://api.telegram.org/bot${botToken}/getFile`
42
+ );
43
+ expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toEqual({
44
+ file_id: 'incoming-file-id',
45
+ });
46
+ expect(String(fetchMock.mock.calls[1]?.[0])).toBe(
47
+ `https://api.telegram.org/file/bot${botToken}/documents/report%201.pdf`
48
+ );
49
+ expect(downloaded.file.file_path).toBe('documents/report 1.pdf');
50
+ expect(downloaded.data).toEqual(new Uint8Array([1, 2, 3, 4]));
51
+ });
52
+
53
+ test('fails explicitly when Telegram does not return a file path', async () => {
54
+ const fetchMock = mock(async () =>
55
+ Response.json({
56
+ ok: true,
57
+ result: {
58
+ file_id: 'incoming-file-id',
59
+ file_unique_id: 'unique-file-id',
60
+ },
61
+ })
62
+ );
63
+ globalThis.fetch = fetchMock as unknown as typeof fetch;
64
+
65
+ const telegram = new Telegram({ botToken: '123456:test-token' });
66
+
67
+ expect(
68
+ telegram.bot.downloadFile({ fileId: 'incoming-file-id' })
69
+ ).rejects.toThrow('Telegram did not return a downloadable file path');
70
+ expect(fetchMock).toHaveBeenCalledTimes(1);
71
+ });
72
+
73
+ test('does not expose the bot token in download errors', async () => {
74
+ const botToken = '123456:must-not-leak';
75
+ const fetchMock = mock(async (
76
+ input: string | URL | Request,
77
+ _init?: RequestInit
78
+ ) => {
79
+ if (String(input).endsWith('/getFile')) {
80
+ return Response.json({
81
+ ok: true,
82
+ result: {
83
+ file_id: 'incoming-file-id',
84
+ file_unique_id: 'unique-file-id',
85
+ file_path: 'photos/file.jpg',
86
+ },
87
+ });
88
+ }
89
+ return new Response('not found', { status: 404 });
90
+ });
91
+ globalThis.fetch = fetchMock as unknown as typeof fetch;
92
+
93
+ const telegram = new Telegram({ botToken });
94
+
95
+ try {
96
+ await telegram.bot.downloadFile({ fileId: 'incoming-file-id' });
97
+ throw new Error('Expected download to fail');
98
+ } catch (err) {
99
+ expect(String(err)).toContain('Telegram file download failed');
100
+ expect(String(err)).not.toContain(botToken);
101
+ }
102
+ });
103
+ });
@@ -64,3 +64,4 @@ export { ChatsApi } from './chats';
64
64
  export { UpdatesApi } from './updates';
65
65
  export { InlineApi } from './inline';
66
66
  export { BotApi } from './bot';
67
+ export type { DownloadFileResult } from './bot';
@@ -9,7 +9,10 @@ afterEach(() => {
9
9
 
10
10
  describe("Telegram messages API", () => {
11
11
  test("sends raw exclamation marks unless MarkdownV2 parse mode is requested", async () => {
12
- const fetchMock = mock(async () =>
12
+ const fetchMock = mock(async (
13
+ _input: string | URL | Request,
14
+ _init?: RequestInit
15
+ ) =>
13
16
  Response.json({
14
17
  ok: true,
15
18
  result: {
@@ -37,7 +40,10 @@ describe("Telegram messages API", () => {
37
40
  });
38
41
 
39
42
  test("preserves raw text when HTML parse mode is explicit", async () => {
40
- const fetchMock = mock(async () =>
43
+ const fetchMock = mock(async (
44
+ _input: string | URL | Request,
45
+ _init?: RequestInit
46
+ ) =>
41
47
  Response.json({
42
48
  ok: true,
43
49
  result: {
@@ -0,0 +1,40 @@
1
+ import { afterEach, describe, expect, test } from 'bun:test';
2
+ import { mkdtempSync, readFileSync, rmSync } from 'fs';
3
+ import { tmpdir } from 'os';
4
+ import { join } from 'path';
5
+ import { resolveDownloadPath, writeDownloadedFile } from './files';
6
+
7
+ let temporaryDirectory: string | undefined;
8
+
9
+ afterEach(() => {
10
+ if (temporaryDirectory) {
11
+ rmSync(temporaryDirectory, { recursive: true, force: true });
12
+ temporaryDirectory = undefined;
13
+ }
14
+ });
15
+
16
+ describe('Telegram CLI file downloads', () => {
17
+ test('uses Telegram file name when the output is a directory', () => {
18
+ temporaryDirectory = mkdtempSync(join(tmpdir(), 'connect-telegram-'));
19
+
20
+ expect(resolveDownloadPath('photos/file_42.jpg', temporaryDirectory)).toBe(
21
+ join(temporaryDirectory, 'file_42.jpg')
22
+ );
23
+ });
24
+
25
+ test('creates parent directories without overwriting an existing file', () => {
26
+ temporaryDirectory = mkdtempSync(join(tmpdir(), 'connect-telegram-'));
27
+ const destination = join(temporaryDirectory, 'nested', 'file.bin');
28
+
29
+ writeDownloadedFile(destination, new Uint8Array([1, 2, 3]));
30
+ expect(new Uint8Array(readFileSync(destination))).toEqual(
31
+ new Uint8Array([1, 2, 3])
32
+ );
33
+ expect(() =>
34
+ writeDownloadedFile(destination, new Uint8Array([4, 5, 6]))
35
+ ).toThrow('Refusing to overwrite existing file');
36
+ expect(new Uint8Array(readFileSync(destination))).toEqual(
37
+ new Uint8Array([1, 2, 3])
38
+ );
39
+ });
40
+ });
@@ -0,0 +1,38 @@
1
+ import { existsSync, mkdirSync, statSync, writeFileSync } from 'fs';
2
+ import { basename, dirname, join, resolve, sep } from 'path';
3
+
4
+ export function resolveDownloadPath(
5
+ telegramFilePath: string,
6
+ outputPath?: string
7
+ ): string {
8
+ const defaultName = basename(telegramFilePath);
9
+ if (!defaultName || defaultName === '.' || defaultName === sep) {
10
+ throw new Error('Telegram did not return a usable file name');
11
+ }
12
+
13
+ if (!outputPath) {
14
+ return resolve(defaultName);
15
+ }
16
+
17
+ const requestedPath = resolve(outputPath);
18
+ if (
19
+ (existsSync(requestedPath) && statSync(requestedPath).isDirectory()) ||
20
+ (!existsSync(requestedPath) && outputPath.endsWith(sep))
21
+ ) {
22
+ return join(requestedPath, defaultName);
23
+ }
24
+
25
+ return requestedPath;
26
+ }
27
+
28
+ export function writeDownloadedFile(
29
+ destinationPath: string,
30
+ data: Uint8Array
31
+ ): void {
32
+ if (existsSync(destinationPath)) {
33
+ throw new Error(`Refusing to overwrite existing file: ${destinationPath}`);
34
+ }
35
+
36
+ mkdirSync(dirname(destinationPath), { recursive: true });
37
+ writeFileSync(destinationPath, data, { flag: 'wx' });
38
+ }
@@ -4,6 +4,8 @@ import chalk from 'chalk';
4
4
  import { readFileSync, existsSync } from 'fs';
5
5
  import { basename } from 'path';
6
6
  import { Telegram } from '../api';
7
+ import { resolveDownloadPath, writeDownloadedFile } from './files';
8
+ import { formatUpdate } from './updates';
7
9
  import {
8
10
  getBotToken,
9
11
  setBotToken,
@@ -455,36 +457,7 @@ program
455
457
  return;
456
458
  }
457
459
 
458
- // Format updates for display
459
- const formatted = updates.map(u => {
460
- const result: Record<string, unknown> = {
461
- update_id: u.update_id,
462
- };
463
-
464
- if (u.message) {
465
- result.type = 'message';
466
- result.from = u.message.from?.username || u.message.from?.first_name || 'unknown';
467
- result.chat_id = u.message.chat.id;
468
- result.text = u.message.text || '[media]';
469
- result.date = new Date(u.message.date * 1000).toISOString();
470
- } else if (u.callback_query) {
471
- result.type = 'callback_query';
472
- result.from = u.callback_query.from.username || u.callback_query.from.first_name;
473
- result.data = u.callback_query.data;
474
- } else if (u.inline_query) {
475
- result.type = 'inline_query';
476
- result.from = u.inline_query.from.username || u.inline_query.from.first_name;
477
- result.query = u.inline_query.query;
478
- } else if (u.edited_message) {
479
- result.type = 'edited_message';
480
- result.chat_id = u.edited_message.chat.id;
481
- } else if (u.channel_post) {
482
- result.type = 'channel_post';
483
- result.chat_id = u.channel_post.chat.id;
484
- }
485
-
486
- return result;
487
- });
460
+ const formatted = updates.map(formatUpdate);
488
461
 
489
462
  print(formatted, getFormat(program));
490
463
  info(`Showing ${updates.length} update(s). Last update_id: ${updates[updates.length - 1].update_id}`);
@@ -494,6 +467,30 @@ program
494
467
  }
495
468
  });
496
469
 
470
+ program
471
+ .command('get-file <fileId>')
472
+ .description('Download a Telegram file by file ID')
473
+ .option('-o, --output <path>', 'Output file or directory (defaults to the Telegram file name)')
474
+ .action(async (fileId: string, opts: { output?: string }) => {
475
+ try {
476
+ const client = getClient();
477
+ const downloaded = await client.bot.downloadFile({ fileId });
478
+ const destination = resolveDownloadPath(downloaded.file.file_path!, opts.output);
479
+ writeDownloadedFile(destination, downloaded.data);
480
+
481
+ print({
482
+ file_id: downloaded.file.file_id,
483
+ file_unique_id: downloaded.file.file_unique_id,
484
+ file_path: downloaded.file.file_path,
485
+ output: destination,
486
+ bytes: downloaded.data.byteLength,
487
+ }, getFormat(program));
488
+ } catch (err) {
489
+ error(String(err));
490
+ process.exit(1);
491
+ }
492
+ });
493
+
497
494
  // ============================================
498
495
  // Chat Commands
499
496
  // ============================================
@@ -0,0 +1,176 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import type { TelegramMessage, TelegramUpdate } from '../types';
3
+ import { formatUpdate } from './updates';
4
+
5
+ function updateWith(message: Partial<TelegramMessage>): TelegramUpdate {
6
+ return {
7
+ update_id: 428687256,
8
+ message: {
9
+ message_id: 42,
10
+ date: 1,
11
+ chat: { id: 123, type: 'private' },
12
+ from: {
13
+ id: 456,
14
+ is_bot: false,
15
+ first_name: 'Andrei',
16
+ username: 'andrei',
17
+ },
18
+ ...message,
19
+ },
20
+ };
21
+ }
22
+
23
+ describe('Telegram update formatting', () => {
24
+ test('exposes the largest incoming photo file ID and caption', () => {
25
+ const formatted = formatUpdate(updateWith({
26
+ caption: 'Please inspect this screenshot',
27
+ photo: [
28
+ {
29
+ file_id: 'small-photo-id',
30
+ file_unique_id: 'small-unique-id',
31
+ width: 90,
32
+ height: 90,
33
+ file_size: 100,
34
+ },
35
+ {
36
+ file_id: 'large-photo-id',
37
+ file_unique_id: 'large-unique-id',
38
+ width: 1280,
39
+ height: 720,
40
+ file_size: 5000,
41
+ },
42
+ ],
43
+ }));
44
+
45
+ expect(formatted).toMatchObject({
46
+ update_id: 428687256,
47
+ type: 'message',
48
+ message_id: 42,
49
+ caption: 'Please inspect this screenshot',
50
+ media: {
51
+ type: 'photo',
52
+ file_id: 'large-photo-id',
53
+ file_unique_id: 'large-unique-id',
54
+ width: 1280,
55
+ height: 720,
56
+ file_size: 5000,
57
+ },
58
+ });
59
+ expect(formatted).not.toHaveProperty('text', '[media]');
60
+ });
61
+
62
+ test('exposes actionable metadata for every typed downloadable media kind', () => {
63
+ const cases: Array<{
64
+ mediaType: string;
65
+ message: Partial<TelegramMessage>;
66
+ fileId: string;
67
+ expected: Record<string, unknown>;
68
+ }> = [
69
+ {
70
+ mediaType: 'animation',
71
+ fileId: 'animation-id',
72
+ message: {
73
+ animation: {
74
+ file_id: 'animation-id',
75
+ file_unique_id: 'animation-unique-id',
76
+ width: 640,
77
+ height: 360,
78
+ duration: 4,
79
+ file_name: 'instruction.gif',
80
+ mime_type: 'image/gif',
81
+ file_size: 2500,
82
+ },
83
+ },
84
+ expected: {
85
+ width: 640,
86
+ height: 360,
87
+ duration: 4,
88
+ file_name: 'instruction.gif',
89
+ mime_type: 'image/gif',
90
+ },
91
+ },
92
+ {
93
+ mediaType: 'document',
94
+ fileId: 'document-id',
95
+ message: {
96
+ document: {
97
+ file_id: 'document-id',
98
+ file_unique_id: 'document-unique-id',
99
+ file_name: 'instructions.pdf',
100
+ mime_type: 'application/pdf',
101
+ file_size: 1000,
102
+ },
103
+ },
104
+ expected: {
105
+ file_name: 'instructions.pdf',
106
+ mime_type: 'application/pdf',
107
+ },
108
+ },
109
+ {
110
+ mediaType: 'audio',
111
+ fileId: 'audio-id',
112
+ message: {
113
+ audio: {
114
+ file_id: 'audio-id',
115
+ file_unique_id: 'audio-unique-id',
116
+ duration: 12,
117
+ file_name: 'note.mp3',
118
+ mime_type: 'audio/mpeg',
119
+ },
120
+ },
121
+ expected: { duration: 12, file_name: 'note.mp3', mime_type: 'audio/mpeg' },
122
+ },
123
+ {
124
+ mediaType: 'video',
125
+ fileId: 'video-id',
126
+ message: {
127
+ video: {
128
+ file_id: 'video-id',
129
+ file_unique_id: 'video-unique-id',
130
+ width: 1920,
131
+ height: 1080,
132
+ duration: 8,
133
+ file_name: 'screen.mp4',
134
+ mime_type: 'video/mp4',
135
+ },
136
+ },
137
+ expected: { width: 1920, height: 1080, duration: 8, file_name: 'screen.mp4' },
138
+ },
139
+ {
140
+ mediaType: 'voice',
141
+ fileId: 'voice-id',
142
+ message: {
143
+ voice: {
144
+ file_id: 'voice-id',
145
+ file_unique_id: 'voice-unique-id',
146
+ duration: 5,
147
+ mime_type: 'audio/ogg',
148
+ },
149
+ },
150
+ expected: { duration: 5, mime_type: 'audio/ogg' },
151
+ },
152
+ {
153
+ mediaType: 'video_note',
154
+ fileId: 'video-note-id',
155
+ message: {
156
+ video_note: {
157
+ file_id: 'video-note-id',
158
+ file_unique_id: 'video-note-unique-id',
159
+ length: 384,
160
+ duration: 6,
161
+ },
162
+ },
163
+ expected: { length: 384, duration: 6 },
164
+ },
165
+ ];
166
+
167
+ for (const testCase of cases) {
168
+ const formatted = formatUpdate(updateWith(testCase.message));
169
+ expect(formatted.media).toMatchObject({
170
+ type: testCase.mediaType,
171
+ file_id: testCase.fileId,
172
+ ...testCase.expected,
173
+ });
174
+ }
175
+ });
176
+ });