├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── Contracts └── TelegramSenderContract.php ├── Enums ├── FileType.php └── ParseMode.php ├── Exceptions └── CouldNotSendNotification.php ├── Telegram.php ├── TelegramBase.php ├── TelegramChannel.php ├── TelegramContact.php ├── TelegramFile.php ├── TelegramLocation.php ├── TelegramMessage.php ├── TelegramPoll.php ├── TelegramServiceProvider.php ├── TelegramUpdates.php ├── TelegramVenue.php └── Traits └── HasSharedLogic.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `telegram` will be documented in this file 4 | 5 | ## 6.0 - 2025-02-25 6 | 7 | #### What's Changed 8 | 9 | * Add support for Laravel 12. 10 | * Add `TelegramVenue` to support `sendVenue` method. 11 | * Add `sticker` method to the `TelegramFile` to send sticker file. 12 | * Add `sendWhen` method to conditionally send a message. 13 | * Add ParseMode Enum and refactor parsing mode setting logic. 14 | * Add `buttonWithWebApp` method to open web app from a button. 15 | * Add `onError` method to handle exceptions. Based of https://github.com/laravel-notification-channels/telegram/pull/201 by @Hesammousavi. 16 | * Refactor `sendFile` to support raw data sending. 17 | * Refactor `escapedLine` method. 18 | * Refactor `HasSharedLogic` trait. 19 | * Refactor classes to use PHP 8.2 features. 20 | * Revise `keyboard` method parameters to `$requestLocation` and `$requestContact` to be consistent. 21 | * Drop support for Laravel 10. 22 | * Drop support for PHP 8.1. 23 | 24 | ### New Contributors 25 | 26 | * @Hesammousavi made their first contribution in https://github.com/laravel-notification-channels/telegram/pull/201 27 | 28 | **Full Changelog**: https://github.com/laravel-notification-channels/telegram/compare/5.0.0...6.0.0 29 | 30 | ## 5.0 - 2024-03-12 31 | 32 | ### What's Changed 33 | 34 | * Laravel 11 Support. 35 | * Fix Call to a member function toArray() on array by @tantrus332 in https://github.com/laravel-notification-channels/telegram/pull/174 36 | * Add keyboard function to messages by @abbasudo in https://github.com/laravel-notification-channels/telegram/pull/183 37 | * Add ability to change default parsing mode by @abbasudo in https://github.com/laravel-notification-channels/telegram/pull/185 38 | * Addition of 'lineIf' method to messages by @MammutAlex in https://github.com/laravel-notification-channels/telegram/pull/190 39 | 40 | ### New Contributors 41 | 42 | * @tantrus332 made their first contribution in https://github.com/laravel-notification-channels/telegram/pull/174 43 | * @MammutAlex made their first contribution in https://github.com/laravel-notification-channels/telegram/pull/190 44 | 45 | **Full Changelog**: https://github.com/laravel-notification-channels/telegram/compare/4.0.0...5.0.0 46 | 47 | ## 4.0.0 - 2023-02-14 48 | 49 | ### What's Changed 50 | 51 | - Fix Chunk method error by @faissaloux in https://github.com/laravel-notification-channels/telegram/pull/163 52 | - Add escapedLine() method to TelegramMessage by @ziming in https://github.com/laravel-notification-channels/telegram/pull/168 53 | - Laravel 10.x Compatibility by @laravel-shift in https://github.com/laravel-notification-channels/telegram/pull/167 54 | - Drop support for PHP `< 8.1`. 55 | 56 | ### New Contributors 57 | 58 | - @ziming made their first contribution in https://github.com/laravel-notification-channels/telegram/pull/168 59 | - @laravel-shift made their first contribution in https://github.com/laravel-notification-channels/telegram/pull/167 60 | 61 | **Full Changelog**: https://github.com/laravel-notification-channels/telegram/compare/3.0.0...4.0.0 62 | 63 | ## 3.0 - 2022-11-19 64 | 65 | ### What's Changed 66 | 67 | - Send logic moved to drivers by @llabbasmkhll in ### https://github.com/laravel-notification-channels/telegram/pull/146 68 | - Drop support for Laravel < 9. 69 | - Convert tests to Pest and improve coverage in https://github.com/laravel-notification-channels/telegram/pull/151. 70 | - Add TelegramUpdatesTests. 71 | - Add PHPStan for static analysis using GitHub Action. 72 | - Add Changelog updater workflow. 73 | - Add Codecov for code coverage workflow. 74 | - Improve GitHub action workflows. 75 | - Clean and improve code. 76 | - Remove the scrutinizer. 77 | - Add `line()` method (adapted from a PR) by @llabbasmkhll in https://github.com/laravel-notification-channels/telegram/pull/149. 78 | - Upgrade required and dev packages. 79 | - Normalize composer JSON file. 80 | - Add type hints and return types were supported. 81 | - Rename `TelegramSender` contract to `TelegramSenderContract` 82 | - Harden error handling for JSON encode and decode methods. 83 | - Improve doc blocks. 84 | - Add `toArray()` method to TelegramUpdates to retrieve payload. 85 | - Restructure the tests directory. 86 | - Add data to notification failed event in https://github.com/laravel-notification-channels/telegram/pull/156 87 | - Add More Tests in https://github.com/laravel-notification-channels/telegram/pull/157 88 | 89 | ### New Contributors 90 | 91 | - @llabbasmkhll made their first contribution in https://github.com/laravel-notification-channels/telegram/pull/146 92 | 93 | **Full Changelog**: https://github.com/laravel-notification-channels/telegram/compare/2.1.0...3.0.0 94 | 95 | ## 2.0.0 - 2022-02-11 96 | 97 | - Add Laravel 9 Support. 98 | - Add Laravel conditional trait to payload builder to use `when` on methods chain. PR [#139](https://github.com/laravel-notification-channels/telegram/pull/139). 99 | - Drop support for older version of PHP `< 7.4` and Laravel `< 8`. 100 | 101 | ## 1.0.0 - 2021-12-11 102 | 103 | - Register Telegram instance to container. 104 | - Add `TelegramUpdates` to retrieve bot updates. PR [#133](https://github.com/laravel-notification-channels/telegram/pull/133). 105 | - Refactor TelegramChannel. PR [#136](https://github.com/laravel-notification-channels/telegram/pull/136). 106 | - Add Retrieving Chat ID docs and improve docs. 107 | - Add missing type declaration and minor improvements to various methods. 108 | - Add Contact Support. PR [#138](https://github.com/laravel-notification-channels/telegram/pull/138). 109 | 110 | ## 0.9.0 - 2021-11-24 111 | 112 | - Add Poll Support. PR [#130](https://github.com/laravel-notification-channels/telegram/pull/130). 113 | - Remove StyleCI in favor of GitHub Actions Workflow for Code Styling. PR [#131](https://github.com/laravel-notification-channels/telegram/pull/131). 114 | 115 | ## 0.8.0 - 2021-11-14 116 | 117 | - Add message chunking feature (`chunk($limit)`) in cases where the message is too long. Closes [#127](https://github.com/laravel-notification-channels/telegram/issues/127). 118 | 119 | ## 0.7.0 - 2021-10-28 120 | 121 | - Dropped PHP 7.1 support. PR [#118](https://github.com/laravel-notification-channels/telegram/pull/118). 122 | - Dispatch event `NotificationFailed` on exception. PR [#119](https://github.com/laravel-notification-channels/telegram/pull/119). 123 | - Test against PHP 8.1. PR [#120](https://github.com/laravel-notification-channels/telegram/pull/120). 124 | - Add support to use `TelegramChannel::class` in on-demand notification route. PR [#122](https://github.com/laravel-notification-channels/telegram/pull/122). 125 | - Refactor channel registration with the channel manager. PR [#122](https://github.com/laravel-notification-channels/telegram/pull/122). 126 | 127 | ## 0.6.0 - 2021-10-04 128 | 129 | - Add GitHub Actions workflows for tests and coverage. PR [#103](https://github.com/laravel-notification-channels/telegram/pull/103). 130 | - Add alternate method to resolve Telegram notification channel. PR [#110](https://github.com/laravel-notification-channels/telegram/pull/110). 131 | - Add `buttonWithCallback()` method. PR [#114](https://github.com/laravel-notification-channels/telegram/pull/114). 132 | - Revise file upload logic. 133 | - Add more info on proxy setting. 134 | - Remove dead badges. 135 | 136 | ## 0.5.1 - 2020-12-07 137 | 138 | - PHP 8 Support. 139 | 140 | ## 0.5.0 - 2020-09-08 141 | 142 | - Add previous `ClientException` when constructing `CouldNotSendNotification` exception. PR [#86](https://github.com/laravel-notification-channels/telegram/pull/86). 143 | - Add Laravel 8 Support. PR [#88](https://github.com/laravel-notification-channels/telegram/pull/88). 144 | - Add Bot token per notification support. Closed [#84](https://github.com/laravel-notification-channels/telegram/issues/84). 145 | - Add view file support for notification content. Closed [#82](https://github.com/laravel-notification-channels/telegram/issues/82). 146 | - Add support to set HTTP Client. 147 | 148 | ## 0.4.1 - 2020-07-07 149 | 150 | - Add Guzzle 7 Support. PR [#80](https://github.com/laravel-notification-channels/telegram/pull/80). 151 | 152 | ## 0.4.0 - 2020-06-02 153 | 154 | - Add support to set custom api `base_uri` for web bridge. 155 | - Revise README with instructions for Proxy or Bridge support. 156 | - Revise on-demand notification instructions - Fixes [#72](https://github.com/laravel-notification-channels/telegram/issues/72). 157 | - Fix typo in test. 158 | - Remove redundant test. 159 | - Remove exception when chat id isn't provided - PR [#75](https://github.com/laravel-notification-channels/telegram/pull/75). 160 | 161 | ## 0.3.0 - 2020-03-26 162 | 163 | - Add ability to set param in `disableNotification` method. 164 | 165 | ## 0.2.0 - 2020-02-19 166 | 167 | - Laravel 7 Support. 168 | - Support response handling from Telegram. 169 | 170 | ## 0.1.1 - 2019-11-07 171 | 172 | - Support PHP 7.1 and up. 173 | 174 | ## 0.1.0 - 2019-10-11 175 | 176 | - New Helper Methods to work with file attachments. 177 | - Code cleanup. 178 | - Documentation updated with more examples and previews. 179 | - Micro optimization and improvements. 180 | - Typehint and return type declaration. 181 | - Fixed tests. 182 | 183 | ## 0.0.6 - 2019-09-28 184 | 185 | - Laravel 6 Support. 186 | - Add Photo, Document, Audio, Location and other file notification type support. 187 | - Token getter and setter. 188 | 189 | ## 0.0.5 - 2018-09-08 190 | 191 | - Laravel 5.7 Support. 192 | - Add ability to change button columns. 193 | 194 | ## 0.0.4 - 2018-02-08 195 | 196 | - Laravel 5.6 Support. 197 | 198 | ## 0.0.3 - 2017-09-01 199 | 200 | - Laravel 5.5 Support with Auto-Discovery. 201 | 202 | ## 0.0.2 - 2017-03-24 203 | 204 | - Laravel 5.4 Support. 205 | 206 | ## 0.0.1 - 2016-08-14 207 | 208 | - Initial Release. 209 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | `github at lukonet.net`. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | Please read and understand the contribution guide before creating an issue or pull request. 6 | 7 | ## Etiquette 8 | 9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code 10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be 11 | extremely unfair for them to suffer abuse or anger for their hard work. 12 | 13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the 14 | world that developers are civilized and selfless people. 15 | 16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient 17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. 18 | 19 | ## Viability 20 | 21 | When requesting or submitting new features, first consider whether it might be useful to others. Open 22 | source projects are used by many developers, who may have entirely different needs to your own. Think about 23 | whether or not your feature is likely to be used by other users of the project. 24 | 25 | ## Procedure 26 | 27 | Before filing an issue: 28 | 29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. 30 | - Check to make sure your feature suggestion isn't already present within the project. 31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress. 32 | - Check the pull requests tab to ensure that the feature isn't already in progress. 33 | 34 | Before submitting a pull request: 35 | 36 | - Check the codebase to ensure that your feature doesn't already exist. 37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix. 38 | 39 | ## Requirements 40 | 41 | If the project maintainer has any additional requirements, you will find them listed here. 42 | 43 | - **Code Styling** - The repository uses GitHub Action to automatically apply the standard code styling. 44 | 45 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 46 | 47 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 48 | 49 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 50 | 51 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 52 | 53 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 54 | 55 | **Happy coding**! 56 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) Irfaq Syed 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telegram Notifications Channel for Laravel 2 | 3 | [![Join PHP Chat][ico-phpchat]][link-phpchat] 4 | [![Chat on Telegram][ico-telegram]][link-telegram] 5 | [![Latest Version on Packagist][ico-version]][link-packagist] 6 | [![Software License][ico-license]](LICENSE.md) 7 | [![Total Downloads][ico-downloads]][link-packagist] 8 | 9 | This package makes it easy to send Telegram notification using [Telegram Bot API](https://core.telegram.org/bots) with Laravel. 10 | 11 | ## Contents 12 | 13 | - [Installation](#installation) 14 | - [Setting up your Telegram Bot](#setting-up-your-telegram-bot) 15 | - [Retrieving Chat ID](#retrieving-chat-id) 16 | - [Using in Lumen](#using-in-lumen) 17 | - [Proxy or Bridge Support](#proxy-or-bridge-support) 18 | - [Usage](#usage) 19 | - [Text Notification](#text-notification) 20 | - [Send with Keyboard](#send-with-keyboard) 21 | - [Send a Poll](#send-a-poll) 22 | - [Attach a Contact](#attach-a-contact) 23 | - [Attach an Audio](#attach-an-audio) 24 | - [Attach a Photo](#attach-a-photo) 25 | - [Attach a Document](#attach-a-document) 26 | - [Attach a Location](#attach-a-location) 27 | - [Attach a Venue](#attach-a-venue) 28 | - [Attach a Video](#attach-a-video) 29 | - [Attach a GIF File](#attach-a-gif-file) 30 | - [Attach a Sticker](#attach-a-sticker) 31 | - [Routing a Message](#routing-a-message) 32 | - [Handling Response](#handling-response) 33 | - [Exception Handling](#exception-handling) 34 | - [Using NotificationFailed Event](#using-notificationfailed-event) 35 | - [Using onError Callback](#using-onerror-callback) 36 | - [On-Demand Notifications](#on-demand-notifications) 37 | - [Sending to Multiple Recipients](#sending-to-multiple-recipients) 38 | - [Available Methods](#available-methods) 39 | - [Common Methods](#common-methods) 40 | - [Telegram Message Methods](#telegram-message-methods) 41 | - [Telegram Location Methods](#telegram-location-methods) 42 | - [Telegram Venue Methods](#telegram-venue-methods) 43 | - [Telegram File Methods](#telegram-file-methods) 44 | - [Telegram Contact Methods](#telegram-contact-methods) 45 | - [Telegram Poll Methods](#telegram-poll-methods) 46 | - [Alternatives](#alternatives) 47 | - [Changelog](#changelog) 48 | - [Testing](#testing) 49 | - [Security](#security) 50 | - [Contributing](#contributing) 51 | - [Credits](#credits) 52 | - [License](#license) 53 | 54 | ## Installation 55 | 56 | You can install the package via composer: 57 | 58 | ```bash 59 | composer require laravel-notification-channels/telegram 60 | ``` 61 | 62 | ## Setting up your Telegram Bot 63 | 64 | Talk to [@BotFather](https://core.telegram.org/bots#6-botfather) and generate a Bot API Token. 65 | 66 | Then, configure your Telegram Bot API Token: 67 | 68 | ```php 69 | # config/services.php 70 | 71 | 'telegram-bot-api' => [ 72 | 'token' => env('TELEGRAM_BOT_TOKEN', 'YOUR BOT TOKEN HERE') 73 | ], 74 | ``` 75 | 76 | ## Retrieving Chat ID 77 | 78 | For us to send notifications to your Telegram Bot user/channel or group, we need to know their Chat ID. 79 | 80 | This can be done by fetching the [updates][link-telegram-docs-update] for your Bot using the `getUpdates` method as per Telegram Bot API [docs][link-telegram-docs-getupdates]. 81 | 82 | An [update][link-telegram-docs-update] is an object containing relevant fields based on the type of update it represents, some examples of an update object are `message`, `callback_query`, and `poll`. For a complete list of fields, see [Telegram Bot API docs][link-telegram-docs-update]. 83 | 84 | To make things easier, the library comes with a handy method that can be used to get the updates from which you can parse the relevant Chat ID. 85 | 86 | Please keep in mind the user has to first interact with your bot for you to be able to obtain their Chat ID which you can then store in your database for future interactions or notifications. 87 | 88 | Here's an example of fetching an update: 89 | 90 | ```php 91 | use NotificationChannels\Telegram\TelegramUpdates; 92 | 93 | // Response is an array of updates. 94 | $updates = TelegramUpdates::create() 95 | 96 | // (Optional). Get's the latest update. 97 | // NOTE: All previous updates will be forgotten using this method. 98 | // ->latest() 99 | 100 | // (Optional). Limit to 2 updates (By default, updates starting with the earliest unconfirmed update are returned). 101 | ->limit(2) 102 | 103 | // (Optional). Add more params to the request. 104 | ->options([ 105 | 'timeout' => 0, 106 | ]) 107 | ->get(); 108 | 109 | if($updates['ok']) { 110 | // Chat ID 111 | $chatId = $updates['result'][0]['message']['chat']['id']; 112 | } 113 | ``` 114 | 115 | > [!NOTE] 116 | > This method will not work if an outgoing webhook is set up. 117 | 118 | For a complete list of available parameters for the `options`, see [Telegram Bot API docs][link-telegram-docs-getupdates]. 119 | 120 | ## Using in Lumen 121 | 122 | If you're using this notification channel in your Lumen project, you will have to add the below code in your `bootstrap/app.php` file. 123 | 124 | ```php 125 | # bootstrap/app.php 126 | 127 | // Make sure to create a "config/services.php" file and add the config from the above step. 128 | $app->configure('services'); 129 | 130 | # Register the notification service providers. 131 | $app->register(Illuminate\Notifications\NotificationServiceProvider::class); 132 | $app->register(NotificationChannels\Telegram\TelegramServiceProvider::class); 133 | ``` 134 | 135 | ## Proxy or Bridge Support 136 | 137 | You may not be able to send notifications if Telegram Bot API is not accessible in your country, 138 | you can either set a proxy by following the instructions [here](http://docs.guzzlephp.org/en/stable/quickstart.html#environment-variables) or 139 | use a web bridge by setting the `base_uri` config above with the bridge uri. 140 | 141 | You can set `HTTPS_PROXY` in your `.env` file. 142 | 143 | ## Usage 144 | 145 | You can now use the channel in your `via()` method inside the Notification class. 146 | 147 | ### Text Notification 148 | 149 | ```php 150 | use NotificationChannels\Telegram\TelegramMessage; 151 | use Illuminate\Notifications\Notification; 152 | 153 | class InvoicePaid extends Notification 154 | { 155 | public function via($notifiable) 156 | { 157 | return ["telegram"]; 158 | } 159 | 160 | public function toTelegram($notifiable) 161 | { 162 | $url = url('/invoice/' . $notifiable->invoice->id); 163 | 164 | return TelegramMessage::create() 165 | // Optional recipient user id. 166 | ->to($notifiable->telegram_user_id) 167 | 168 | // Markdown supported. 169 | ->content("Hello there!") 170 | ->line("Your invoice has been *PAID*") 171 | ->lineIf($notifiable->amount > 0, "Amount paid: {$notifiable->amount}") 172 | ->line("Thank you!") 173 | 174 | // (Optional) Blade template for the content. 175 | // ->view('notification', ['url' => $url]) 176 | 177 | // (Optional) Inline Buttons 178 | ->button('View Invoice', $url) 179 | ->button('Download Invoice', $url); 180 | 181 | // (Optional) Conditional notification. 182 | // Only send if amount is greater than 0. Otherwise, don't send. 183 | // ->sendWhen($notifiable->amount > 0) 184 | 185 | // (Optional) Inline Button with Web App 186 | // ->buttonWithWebApp('Open Web App', $url) 187 | 188 | // (Optional) Inline Button with callback. You can handle callback in your bot instance 189 | // ->buttonWithCallback('Confirm', 'confirm_invoice ' . $this->invoice->id) 190 | } 191 | } 192 | ``` 193 | 194 | Here's a screenshot preview of the above notification on Telegram Messenger: 195 | 196 | ![Laravel Telegram Notification Example](https://user-images.githubusercontent.com/1915268/66616627-39be6180-ebef-11e9-92cc-f2da81da047a.jpg) 197 | 198 | ### Send with Keyboard 199 | 200 | ```php 201 | public function toTelegram($notifiable) 202 | { 203 | return TelegramPoll::create() 204 | ->to($notifiable) 205 | ->content('Choose an option:') 206 | ->keyboard('Button 1') 207 | ->keyboard('Button 2'); 208 | // ->keyboard('Send your number', requestContact: true) 209 | // ->keyboard('Send your location', requestLocation: true); 210 | } 211 | ``` 212 | 213 | Preview: 214 | 215 | ![Laravel Telegram Notification Keyboard](https://github.com/abbasudo/telegram/assets/86796762/9c10c7d0-740b-4270-bc7c-f0600e57ba7b) 216 | 217 | ### Send a Poll 218 | 219 | ```php 220 | public function toTelegram($notifiable) 221 | { 222 | return TelegramPoll::create() 223 | ->question('Which is your favorite Laravel Notification Channel?') 224 | ->choices(['Telegram', 'Facebook', 'Slack']); 225 | } 226 | ``` 227 | 228 | Preview: 229 | 230 | ![Laravel Telegram Poll Example](https://github.com/user-attachments/assets/7324ccc5-9370-414a-9337-10c4e7446f5c) 231 | 232 | ### Attach a Contact 233 | 234 | ```php 235 | public function toTelegram($notifiable) 236 | { 237 | return TelegramContact::create() 238 | ->to($notifiable->telegram_user_id) // Optional 239 | ->firstName('John') 240 | ->lastName('Doe') // Optional 241 | ->phoneNumber('00000000'); 242 | } 243 | ``` 244 | 245 | Preview: 246 | 247 | ![Laravel Telegram Contact Example](https://github.com/user-attachments/assets/24f6e1c9-3ed6-4839-b9da-64ce09d09663) 248 | 249 | ### Attach an Audio 250 | 251 | ```php 252 | public function toTelegram($notifiable) 253 | { 254 | return TelegramFile::create() 255 | ->to($notifiable->telegram_user_id) // Optional 256 | ->content('Audio') // Optional Caption 257 | ->audio('/path/to/audio.mp3'); 258 | } 259 | ``` 260 | 261 | Preview: 262 | 263 | ![Laravel Telegram Audio Notification Example](https://user-images.githubusercontent.com/60013703/143334174-4d796910-185f-46e2-89ad-5ec7a1a438c9.png) 264 | 265 | ### Attach a Photo 266 | 267 | ```php 268 | public function toTelegram($notifiable) 269 | { 270 | return TelegramFile::create() 271 | ->to($notifiable->telegram_user_id) // Optional 272 | ->content('Awesome *bold* text and [inline URL](http://www.example.com/)') 273 | ->file('/storage/archive/6029014.jpg', 'photo'); // local photo 274 | 275 | // OR using a helper method with or without a remote file. 276 | // ->photo('https://file-examples-com.github.io/uploads/2017/10/file_example_JPG_1MB.jpg'); 277 | } 278 | ``` 279 | 280 | Preview: 281 | 282 | ![Laravel Telegram Photo Notification Example](https://user-images.githubusercontent.com/1915268/66616792-daad1c80-ebef-11e9-9bdf-c0bc484cf037.jpg) 283 | 284 | ### Attach a Document 285 | 286 | ```php 287 | public function toTelegram($notifiable) 288 | { 289 | return TelegramFile::create() 290 | ->to($notifiable->telegram_user_id) // Optional 291 | ->content('Did you know we can set a custom filename too?') 292 | ->document('https://file-examples-com.github.io/uploads/2017/10/file-sample_150kB.pdf', 'sample.pdf'); 293 | 294 | // You may also send document content on-fly. 295 | // ->document('Hello Text Document Content', 'hello.txt'); 296 | } 297 | ``` 298 | 299 | Preview: 300 | 301 | ![Laravel Telegram Document Notification Example](https://user-images.githubusercontent.com/1915268/66616850-10520580-ebf0-11e9-9122-4f4d263f3b53.jpg) 302 | 303 | ### Attach a Location 304 | 305 | ```php 306 | public function toTelegram($notifiable) 307 | { 308 | return TelegramLocation::create() 309 | ->latitude('40.6892494') 310 | ->longitude('-74.0466891'); 311 | } 312 | ``` 313 | 314 | Preview: 315 | 316 | ![Laravel Telegram Location Notification Example](https://user-images.githubusercontent.com/1915268/66616918-54450a80-ebf0-11e9-86ea-d5264fe05ba9.jpg) 317 | 318 | ### Attach a Venue 319 | 320 | ```php 321 | public function toTelegram($notifiable) 322 | { 323 | return TelegramVenue::create() 324 | ->latitude('38.8951') 325 | ->longitude('-77.0364') 326 | ->title('Grand Palace') 327 | ->address('Bangkok, Thailand'); 328 | } 329 | ``` 330 | 331 | Preview: 332 | 333 | ![Laravel Telegram Venue Notification Example](https://github.com/user-attachments/assets/96e762a6-c4b5-4d8d-8c2d-9d32adb754d0) 334 | 335 | ### Attach a Video 336 | 337 | ```php 338 | public function toTelegram($notifiable) 339 | { 340 | return TelegramFile::create() 341 | ->content('Sample *video* notification!') 342 | ->video('https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_480_1_5MG.mp4'); 343 | } 344 | ``` 345 | 346 | Preview: 347 | 348 | ![Laravel Telegram Video Notification Example](https://user-images.githubusercontent.com/1915268/66617038-ed742100-ebf0-11e9-865a-bf0245d2cbbb.jpg) 349 | 350 | ### Attach a GIF File 351 | 352 | ```php 353 | public function toTelegram($notifiable) 354 | { 355 | return TelegramFile::create() 356 | ->content('Woot! We can send animated gif notifications too!') 357 | ->animation('https://sample-videos.com/gif/2.gif'); 358 | 359 | // Or local file 360 | // ->animation('/path/to/some/animated.gif'); 361 | } 362 | ``` 363 | 364 | Preview: 365 | 366 | ![Laravel Telegram Gif Notification Example](https://user-images.githubusercontent.com/1915268/66617071-109ed080-ebf1-11e9-989b-b237f2b9502d.jpg) 367 | 368 | ### Attach a Sticker 369 | 370 | ```php 371 | public function toTelegram($notifiable) 372 | { 373 | return TelegramFile::create() 374 | ->sticker(storage_path('telegram/AnimatedSticker.tgs')); 375 | } 376 | ``` 377 | 378 | Preview: 379 | 380 | ![Laravel Telegram Sticker Notification Example](https://github.com/user-attachments/assets/5206aac7-022c-4288-ae26-3a117f117fe0) 381 | 382 | ### Routing a Message 383 | 384 | You can either send the notification by providing with the chat ID of the recipient to the `to($chatId)` method like shown in the previous examples or add a `routeNotificationForTelegram()` method in your notifiable model: 385 | 386 | ```php 387 | /** 388 | * Route notifications for the Telegram channel. 389 | * 390 | * @return int 391 | */ 392 | public function routeNotificationForTelegram() 393 | { 394 | return $this->telegram_user_id; 395 | } 396 | ``` 397 | 398 | ### Handling Response 399 | 400 | You can make use of the [notification events](https://laravel.com/docs/11.x/notifications#notification-events) to handle the response from Telegram. On success, your event listener will receive a [Message](https://core.telegram.org/bots/api#message) object with various fields as appropriate to the notification type. 401 | 402 | For a complete list of response fields, please refer the Telegram Bot API's [Message object](https://core.telegram.org/bots/api#message) docs. 403 | 404 | ### Exception Handling 405 | 406 | In case of failures, the package provides two ways to handle exceptions. 407 | 408 | #### Using NotificationFailed Event 409 | 410 | > You can listen to the `Illuminate\Notifications\Events\NotificationFailed` event, which provides a `$data` array containing `to`, `request`, and `exception` keys. 411 | 412 | Listener example: 413 | ```php 414 | use Illuminate\Notifications\Events\NotificationFailed; 415 | 416 | class HandleNotificationFailure 417 | { 418 | public function handle(NotificationFailed $event) 419 | { 420 | // $event->notification: The notification instance. 421 | // $event->notifiable: The notifiable entity who received the notification. 422 | // $event->channel: The channel name. 423 | // $event->data: The data needed to process this failure. 424 | 425 | if ($event->channel !== 'telegram') { 426 | return; 427 | } 428 | 429 | // Log the error / notify administrator or disable notification channel for the user, etc. 430 | \Log::error('Telegram notification failed', [ 431 | 'chat_id' => $event->data['to'], 432 | 'error' => $event->data['exception']->getMessage(), 433 | 'request' => $event->data['request'] 434 | ]); 435 | } 436 | } 437 | ``` 438 | 439 | #### Using onError Callback 440 | 441 | > You can handle exceptions for individual notifications using the `onError` method in your notification: 442 | 443 | ```php 444 | public function toTelegram($notifiable) 445 | { 446 | return TelegramMessage::create() 447 | ->content('Hello!') 448 | ->onError(function ($data) { 449 | \Log::error('Failed to send Telegram notification', [ 450 | 'chat_id' => $data['to'], 451 | 'error' => $data['exception']->getMessage() 452 | ]); 453 | }); 454 | } 455 | ``` 456 | 457 | In both methods, the `$data` array contains the following keys: 458 | 459 | - `to`: The recipient's chat ID. 460 | - `request`: The payload sent to the Telegram Bot API. 461 | - `exception`: The exception object containing error details. 462 | 463 | ### On-Demand Notifications 464 | 465 | > Sometimes you may need to send a notification to someone who is not stored as a "user" of your application. Using the `Notification::route` method, you may specify ad-hoc notification routing information before sending the notification. For more details, you can check out the [on-demand notifications][link-on-demand-notifications] docs. 466 | 467 | ```php 468 | use Illuminate\Support\Facades\Notification; 469 | 470 | Notification::route('telegram', 'TELEGRAM_CHAT_ID') 471 | ->notify(new InvoicePaid($invoice)); 472 | ``` 473 | 474 | ### Sending to Multiple Recipients 475 | 476 | Using the [notification facade][link-notification-facade] you can send a notification to multiple recipients at once. 477 | 478 | > [!WARNING] 479 | > If you're sending bulk notifications to multiple users, the Telegram Bot API will not allow more than 30 messages per second or so. 480 | > Consider spreading out notifications over large intervals of 8—12 hours for best results. 481 | > 482 | > Also note that your bot will not be able to send more than 20 messages per minute to the same group. 483 | > 484 | > If you go over the limit, you'll start getting `429` errors. For more details, refer Telegram Bots [FAQ](https://core.telegram.org/bots/faq#broadcasting-to-users). 485 | 486 | ```php 487 | use Illuminate\Support\Facades\Notification; 488 | 489 | // Recipients can be an array of chat IDs or collection of notifiable entities. 490 | Notification::send($recipients, new InvoicePaid()); 491 | ``` 492 | 493 | ## Available Methods 494 | 495 | For more information on supported parameters, check out these [docs](https://core.telegram.org/bots/api#sendmessage). 496 | 497 | ### Common Methods 498 | 499 | > These methods are optional and common across all the API methods. 500 | 501 | - `to(int|string $chatId)` - Set recipient's chat ID. 502 | - `token(string $token)` - Override default bot token. 503 | - `parseMode(enum ParseMode $mode)` - Set message parse mode (or `normal()` to unset). Default is `ParseMode::Markdown`. 504 | - `keyboard(string $text, int $columns = 2, bool $requestContact = false, bool $requestLocation = false)` - Add regular keyboard. You can add as many as you want, and they'll be placed 2 in a row by default. 505 | - `button(string $text, string $url, int $columns = 2)` - Add inline CTA button. 506 | - `buttonWithCallback(string $text, string $callbackData, int $columns = 2)` - Add inline button with callback. 507 | - `buttonWithWebApp(string $text, string $url, int $columns = 2)` - Add inline web app button. 508 | - `disableNotification(bool $disableNotification = true)` - Send silently (notification without sound). 509 | - `options(array $options)` - Add/override payload parameters. 510 | - `sendWhen(bool $condition)` - Set condition for sending. If the condition is true, the notification will be sent; otherwise, it will not. 511 | - `onError(Closure $callback)` - Set error handler (receives a data array with `to`, `request`, `exception` keys). 512 | - `getPayloadValue(string $key)` - Get specific payload value. 513 | 514 | ### Telegram Message Methods 515 | 516 | > Telegram message notifications are used to send text messages to the user. Supports [Telegram formatting options](https://core.telegram.org/bots/api#formatting-options) 517 | 518 | - `content(string $content, int $limit = null)` - Set message content with optional length limit. Supports markdown. 519 | - `line(string $content)` - Add new line of content. 520 | - `lineIf(bool $condition, string $content)` - Conditionally add new line. 521 | - `escapedLine(string $content)` - Add escaped content line (for Markdown). 522 | - `view(string $view, array $data = [], array $mergeData = [])` - Use Blade template with Telegram supported HTML or Markdown syntax content if you wish to use a view file instead of the `content()` method. 523 | - `chunk(int $limit = 4096)` - Split long messages (rate limited to 1/second). 524 | 525 | > [!NOTE] 526 | > Chunked messages will be rate limited to one message per second to comply with rate limitation requirements from Telegram. 527 | 528 | ### Telegram Location Methods 529 | 530 | > Telegram location messages are used to share a geographical location with the user. 531 | 532 | - `latitude(float|string $latitude)` - Set location latitude. 533 | - `longitude(float|string $longitude)` - Set location longitude. 534 | 535 | ### Telegram Venue Methods 536 | 537 | > Telegram venue messages are used to share a geographical location information about a venue. 538 | 539 | - `latitude(float|string $latitude)` - Set venue latitude. 540 | - `longitude(float|string $longitude)` - Set venue longitude. 541 | - `title(string $title)` - Set venue name/title. 542 | - `address(string $address)` - Set venue address. 543 | - `foursquareId(string $foursquareId)` - (Optional) Set Foursquare identifier of the venue. 544 | - `foursquareType(string $foursquareType)` - (Optional) Set Foursquare type of the venue, if known. 545 | - `googlePlaceId(string $googlePlaceId)` - (Optional) Set Google Places identifier of the venue. 546 | - `googlePlaceType(string $googlePlaceType)` - (Optional) Set Google Places type of the venue. 547 | 548 | ### Telegram File Methods 549 | 550 | > Telegram file messages are used to share various types of files with the user. 551 | 552 | - `content(string $content)` - Set file caption. Supports markdown. 553 | - `view(string $view, array $data = [], array $mergeData = [])` - Use Blade template for caption. 554 | - `file(string|resource|StreamInterface $file, FileType|string $type, string $filename = null)` - Attach file by path/URL. Types: `photo`, `audio`, `document`, `video`, `animation`, `voice`, `video_note`, `sticker` (Use Enum `Enums\FileType`). Use helper methods below for convenience. Filename is optional, ex: `sample.pdf`. 555 | 556 | #### Helper Methods: 557 | 558 | - `photo(string $file)` - Send photo. 559 | - `audio(string $file)` - Send audio (MP3). 560 | - `document(string $file, string $filename = null)` - Send document or any file as document. 561 | - `video(string $file)` - Send video. 562 | - `animation(string $file)` - Send animated GIF. 563 | - `voice(string $file)` - Send voice note (OGG/OPUS). 564 | - `videoNote(string $file)` - Send video note (≤1min, rounded square video). 565 | - `sticker(string $file)` - Send sticker (static PNG/WEBP, animated .TGS, or video .WEBM stickers). 566 | 567 | ### Telegram Contact Methods 568 | 569 | > Telegram contact messages are used to share contact information with the user. 570 | 571 | - `phoneNumber(string $phone)` - Set contact phone. 572 | - `firstName(string $name)` - Set contact first name. 573 | - `lastName(string $name)` - Set contact last name (optional). 574 | - `vCard(string $vcard)` - Set contact vCard (optional). 575 | 576 | ### Telegram Poll Methods 577 | 578 | > Telegram polls are a type of interactive message that allows users to vote on a question. Polls can be used to gather feedback, make decisions, or even run contests. 579 | 580 | - `question(string $question)` - Set poll question. 581 | - `choices(array $choices)` - Set poll choices. 582 | 583 | ## Alternatives 584 | 585 | For advance usage, please consider using [telegram-bot-sdk](https://github.com/irazasyed/telegram-bot-sdk) instead. 586 | 587 | ## Changelog 588 | 589 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 590 | 591 | ## Testing 592 | 593 | ```bash 594 | $ composer test 595 | ``` 596 | 597 | ## Security 598 | 599 | If you discover any security related issues, please email syed@lukonet.com instead of using the issue tracker. 600 | 601 | ## Contributing 602 | 603 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 604 | 605 | ## Credits 606 | 607 | - [Irfaq Syed][link-author] 608 | - [All Contributors][link-contributors] 609 | 610 | ## License 611 | 612 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 613 | 614 | [ico-phpchat]: https://img.shields.io/badge/Slack-PHP%20Chat-5c6aaa.svg?style=flat-square&logo=slack&labelColor=4A154B 615 | [ico-telegram]: https://img.shields.io/badge/@PHPChatCo-2CA5E0.svg?style=flat-square&logo=telegram&label=Telegram 616 | [ico-version]: https://img.shields.io/packagist/v/laravel-notification-channels/telegram.svg?style=flat-square 617 | [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square 618 | [ico-downloads]: https://img.shields.io/packagist/dt/laravel-notification-channels/telegram.svg?style=flat-square 619 | 620 | [link-phpchat]: https://phpchat.co/?ref=laravel-channel-telegram 621 | [link-telegram]: https://t.me/PHPChatCo 622 | [link-repo]: https://github.com/laravel-notification-channels/telegram 623 | [link-packagist]: https://packagist.org/packages/laravel-notification-channels/telegram 624 | [link-author]: https://github.com/irazasyed 625 | [link-contributors]: ../../contributors 626 | [link-notification-facade]: https://laravel.com/docs/11.x/notifications#using-the-notification-facade 627 | [link-on-demand-notifications]: https://laravel.com/docs/11.x/notifications#on-demand-notifications 628 | [link-telegram-docs-update]: https://core.telegram.org/bots/api#update 629 | [link-telegram-docs-getupdates]: https://core.telegram.org/bots/api#getupdates 630 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel-notification-channels/telegram", 3 | "description": "Telegram Notifications Channel for Laravel", 4 | "license": "MIT", 5 | "keywords": [ 6 | "telegram notification", 7 | "laravel", 8 | "telegram", 9 | "notification", 10 | "telegram notifications channel" 11 | ], 12 | "authors": [ 13 | { 14 | "name": "Irfaq Syed", 15 | "email": "github@lukonet.net", 16 | "homepage": "https://lukonet.com", 17 | "role": "Developer" 18 | } 19 | ], 20 | "homepage": "https://github.com/laravel-notification-channels/telegram", 21 | "require": { 22 | "php": "^8.2", 23 | "ext-json": "*", 24 | "guzzlehttp/guzzle": "^7.8", 25 | "illuminate/contracts": "^11.0 || ^12.0", 26 | "illuminate/notifications": "^11.0 || ^12.0", 27 | "illuminate/support": "^11.0 || ^12.0" 28 | }, 29 | "require-dev": { 30 | "larastan/larastan": "^3.0", 31 | "mockery/mockery": "^1.4.4", 32 | "orchestra/testbench": "^10.0", 33 | "pestphp/pest": "^3.0", 34 | "pestphp/pest-plugin-laravel": "^3.0", 35 | "phpstan/extension-installer": "^1.2", 36 | "phpstan/phpstan-deprecation-rules": "^2.0", 37 | "phpstan/phpstan-phpunit": "^2.0" 38 | }, 39 | "minimum-stability": "dev", 40 | "prefer-stable": true, 41 | "autoload": { 42 | "psr-4": { 43 | "NotificationChannels\\Telegram\\": "src" 44 | } 45 | }, 46 | "autoload-dev": { 47 | "psr-4": { 48 | "NotificationChannels\\Telegram\\Tests\\": "tests" 49 | } 50 | }, 51 | "config": { 52 | "allow-plugins": { 53 | "phpstan/extension-installer": true, 54 | "pestphp/pest-plugin": true 55 | }, 56 | "sort-packages": true 57 | }, 58 | "extra": { 59 | "laravel": { 60 | "providers": [ 61 | "NotificationChannels\\Telegram\\TelegramServiceProvider" 62 | ] 63 | } 64 | }, 65 | "scripts": { 66 | "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", 67 | "analyse": "phpstan analyse", 68 | "test": "pest", 69 | "test-coverage": "pest --coverage" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Contracts/TelegramSenderContract.php: -------------------------------------------------------------------------------- 1 | 'application/octet-stream', 28 | self::Photo => 'image/jpeg', 29 | self::Audio => 'audio/mp3', 30 | self::Video => 'video/mp4', 31 | self::Animation => 'video/mp4', 32 | self::Voice => 'audio/ogg', 33 | self::VideoNote => 'video/mp4', 34 | self::Sticker => 'image/webp', 35 | }; 36 | } 37 | 38 | /** 39 | * Get allowed file extensions for this type. 40 | * 41 | * @return array 42 | */ 43 | public function getAllowedExtensions(): array 44 | { 45 | return match ($this) { 46 | self::Document => [], // Any extension allowed 47 | self::Photo => ['jpg', 'jpeg', 'png', 'webp'], 48 | self::Audio => ['mp3', 'ogg', 'm4a'], 49 | self::Video => ['mp4', 'avi', 'mov', 'mkv'], 50 | self::Animation => ['gif', 'mp4'], 51 | self::Voice => ['ogg', 'mp3'], 52 | self::VideoNote => ['mp4'], 53 | self::Sticker => ['png', 'webp', 'tgs', 'webm'], 54 | }; 55 | } 56 | 57 | /** 58 | * Check if a file extension is allowed for this type. 59 | */ 60 | public function isExtensionAllowed(string $extension): bool 61 | { 62 | $extensions = $this->getAllowedExtensions(); 63 | 64 | // Document allows all extensions 65 | if ($this === self::Document || empty($extensions)) { 66 | return true; 67 | } 68 | 69 | return in_array(strtolower($extension), $extensions, true); 70 | } 71 | 72 | /** 73 | * Get all file types as an array. 74 | * 75 | * @return array 76 | */ 77 | public static function toArray(): array 78 | { 79 | return array_column(self::cases(), 'value', 'name'); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Enums/ParseMode.php: -------------------------------------------------------------------------------- 1 | hasResponse()) { 23 | return new self('Telegram responded with an error but no response body found'); 24 | } 25 | 26 | $statusCode = $exception->getResponse()->getStatusCode(); 27 | 28 | $result = json_decode($exception->getResponse()->getBody()->getContents()); 29 | $description = $result->description ?? 'no description given'; 30 | 31 | return new self("Telegram responded with an error `{$statusCode} - {$description}`", 0, $exception); 32 | } 33 | 34 | /** 35 | * Thrown when there's no bot token provided. 36 | */ 37 | public static function telegramBotTokenNotProvided(string $message): self 38 | { 39 | return new self($message); 40 | } 41 | 42 | /** 43 | * Thrown when we're unable to communicate with Telegram. 44 | */ 45 | public static function couldNotCommunicateWithTelegram(string $message): self 46 | { 47 | return new self("The communication with Telegram failed. `{$message}`"); 48 | } 49 | 50 | /** 51 | * Thrown when the file cannot be opened. 52 | */ 53 | public static function fileAccessFailed(string $file): self 54 | { 55 | return new self("Failed to open file: {$file}"); 56 | } 57 | 58 | /** 59 | * Thrown when the file identifier is invalid (ID or URL). 60 | */ 61 | public static function invalidFileIdentifier(string $file): self 62 | { 63 | return new self("Invalid file identifier: {$file}"); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Telegram.php: -------------------------------------------------------------------------------- 1 | setApiBaseUri($apiBaseUri ?? static::API_BASE_URI); 26 | } 27 | 28 | /** 29 | * Token getter. 30 | */ 31 | public function getToken(): ?string 32 | { 33 | return $this->token; 34 | } 35 | 36 | /** 37 | * Token setter. 38 | * 39 | * @return $this 40 | */ 41 | public function setToken(string $token): self 42 | { 43 | $this->token = $token; 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * API Base URI getter. 50 | */ 51 | public function getApiBaseUri(): string 52 | { 53 | return $this->apiBaseUri; 54 | } 55 | 56 | /** 57 | * API Base URI setter. 58 | * 59 | * @return $this 60 | */ 61 | public function setApiBaseUri(string $apiBaseUri): self 62 | { 63 | $this->apiBaseUri = rtrim($apiBaseUri, '/'); 64 | 65 | return $this; 66 | } 67 | 68 | /** 69 | * Set HTTP Client. 70 | * 71 | * @return $this 72 | */ 73 | public function setHttpClient(HttpClient $http): self 74 | { 75 | $this->http = $http; 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * Send text message. 82 | * 83 | * 84 | * $params = [ 85 | * 'chat_id' => '', 86 | * 'text' => '', 87 | * 'parse_mode' => '', 88 | * 'disable_web_page_preview' => '', 89 | * 'disable_notification' => '', 90 | * 'reply_to_message_id' => '', 91 | * 'reply_markup' => '', 92 | * ]; 93 | * 94 | * 95 | * @see https://core.telegram.org/bots/api#sendmessage 96 | * 97 | * @throws CouldNotSendNotification 98 | */ 99 | public function sendMessage(array $params): ?ResponseInterface 100 | { 101 | return $this->sendRequest('sendMessage', $params); 102 | } 103 | 104 | /** 105 | * Send File as Image or Document. 106 | * 107 | * 108 | * @throws CouldNotSendNotification 109 | */ 110 | public function sendFile(array $params, string $type, bool $multipart = false): ?ResponseInterface 111 | { 112 | return $this->sendRequest('send'.Str::studly($type), $params, $multipart); 113 | } 114 | 115 | /** 116 | * Send a Poll. 117 | * 118 | * 119 | * @throws CouldNotSendNotification 120 | */ 121 | public function sendPoll(array $params): ?ResponseInterface 122 | { 123 | return $this->sendRequest('sendPoll', $params); 124 | } 125 | 126 | /** 127 | * Send a Contact. 128 | * 129 | * 130 | * @throws CouldNotSendNotification 131 | */ 132 | public function sendContact(array $params): ?ResponseInterface 133 | { 134 | return $this->sendRequest('sendContact', $params); 135 | } 136 | 137 | /** 138 | * Get updates. 139 | * 140 | * 141 | * @throws CouldNotSendNotification 142 | */ 143 | public function getUpdates(array $params): ?ResponseInterface 144 | { 145 | return $this->sendRequest('getUpdates', $params); 146 | } 147 | 148 | /** 149 | * Send a Location. 150 | * 151 | * 152 | * @throws CouldNotSendNotification 153 | */ 154 | public function sendLocation(array $params): ?ResponseInterface 155 | { 156 | return $this->sendRequest('sendLocation', $params); 157 | } 158 | 159 | /** 160 | * Send a Venue. 161 | * 162 | * @throws CouldNotSendNotification 163 | */ 164 | public function sendVenue(array $params): ?ResponseInterface 165 | { 166 | return $this->sendRequest('sendVenue', $params); 167 | } 168 | 169 | /** 170 | * Get HttpClient. 171 | */ 172 | protected function httpClient(): HttpClient 173 | { 174 | return $this->http; 175 | } 176 | 177 | /** 178 | * Send an API request and return response. 179 | * 180 | * 181 | * @throws CouldNotSendNotification 182 | */ 183 | protected function sendRequest(string $endpoint, array $params, bool $multipart = false): ?ResponseInterface 184 | { 185 | if (blank($this->token)) { 186 | throw CouldNotSendNotification::telegramBotTokenNotProvided('You must provide your telegram bot token to make any API requests.'); 187 | } 188 | 189 | $apiUri = sprintf('%s/bot%s/%s', $this->apiBaseUri, $this->token, $endpoint); 190 | 191 | try { 192 | return $this->httpClient()->post($apiUri, [ 193 | $multipart ? 'multipart' : 'form_params' => $params, 194 | ]); 195 | } catch (ClientException $exception) { 196 | throw CouldNotSendNotification::telegramRespondedWithAnError($exception); 197 | } catch (Exception $exception) { 198 | throw CouldNotSendNotification::couldNotCommunicateWithTelegram($exception); 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/TelegramBase.php: -------------------------------------------------------------------------------- 1 | telegram = app(Telegram::class); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/TelegramChannel.php: -------------------------------------------------------------------------------- 1 | toTelegram($notifiable); 30 | 31 | if (is_string($message)) { 32 | $message = TelegramMessage::create($message); 33 | } 34 | 35 | if (! $message->canSend()) { 36 | return null; 37 | } 38 | 39 | $to = $message->getPayloadValue('chat_id') ?: 40 | ($notifiable->routeNotificationFor('telegram', $notification) ?: 41 | $notifiable->routeNotificationFor(self::class, $notification)); 42 | 43 | if (! $to) { 44 | return null; 45 | } 46 | 47 | $message->to($to); 48 | 49 | if ($message->hasToken()) { 50 | $message->telegram->setToken($message->token); 51 | } 52 | 53 | try { 54 | $response = $message->send(); 55 | } catch (CouldNotSendNotification $exception) { 56 | $data = [ 57 | 'to' => $message->getPayloadValue('chat_id'), 58 | 'request' => $message->toArray(), 59 | 'exception' => $exception, 60 | ]; 61 | 62 | if ($message->exceptionHandler) { 63 | ($message->exceptionHandler)($data); 64 | } 65 | 66 | $this->dispatcher->dispatch(new NotificationFailed($notifiable, $notification, 'telegram', $data)); 67 | 68 | throw $exception; 69 | } 70 | 71 | return $response instanceof Response 72 | ? json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR) 73 | : $response; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/TelegramContact.php: -------------------------------------------------------------------------------- 1 | phoneNumber($phoneNumber); 18 | } 19 | 20 | public static function create(string $phoneNumber = ''): self 21 | { 22 | return new self($phoneNumber); 23 | } 24 | 25 | /** 26 | * Contact phone number. 27 | * 28 | * @return $this 29 | */ 30 | public function phoneNumber(string $phoneNumber): self 31 | { 32 | $this->payload['phone_number'] = $phoneNumber; 33 | 34 | return $this; 35 | } 36 | 37 | /** 38 | * Contact first name. 39 | * 40 | * @return $this 41 | */ 42 | public function firstName(string $firstName): self 43 | { 44 | $this->payload['first_name'] = $firstName; 45 | 46 | return $this; 47 | } 48 | 49 | /** 50 | * Contact last name. 51 | * 52 | * @return $this 53 | */ 54 | public function lastName(string $lastName): self 55 | { 56 | $this->payload['last_name'] = $lastName; 57 | 58 | return $this; 59 | } 60 | 61 | /** 62 | * Contact vCard. 63 | * 64 | * @return $this 65 | */ 66 | public function vCard(string $vCard): self 67 | { 68 | $this->payload['vcard'] = $vCard; 69 | 70 | return $this; 71 | } 72 | 73 | /** 74 | * @throws CouldNotSendNotification 75 | */ 76 | public function send(): ?ResponseInterface 77 | { 78 | return $this->telegram->sendContact($this->toArray()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/TelegramFile.php: -------------------------------------------------------------------------------- 1 | content($content); 37 | $this->parseMode(ParseMode::Markdown); 38 | } 39 | 40 | /** 41 | * Create a new instance of TelegramFile. 42 | */ 43 | public static function create(string $content = ''): self 44 | { 45 | return new self($content); 46 | } 47 | 48 | /** 49 | * Set notification caption for supported file types with markdown support. 50 | */ 51 | public function content(string $content): self 52 | { 53 | $this->payload['caption'] = $content; 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * Attach a file to the message. 60 | * 61 | * @param resource|StreamInterface|string $file The file content or path 62 | * @param FileType|string $type The file type 63 | * @param string|null $filename Optional custom filename 64 | * 65 | * @throws CouldNotSendNotification 66 | */ 67 | public function file(mixed $file, FileType|string $type, ?string $filename = null): self 68 | { 69 | $this->type = is_string($type) ? FileType::tryFrom($type) ?? FileType::Document : $type; 70 | $typeValue = $this->type->value; 71 | 72 | // Handle file URLs or Telegram file IDs 73 | if (is_string($file) && ! $this->isReadableFile($file) && $filename === null) { 74 | if (! filter_var($file, FILTER_VALIDATE_URL) && ! preg_match('/^[a-zA-Z0-9_-]+$/', $file)) { 75 | throw CouldNotSendNotification::invalidFileIdentifier($file); 76 | } 77 | 78 | $this->payload[$typeValue] = $file; 79 | 80 | return $this; 81 | } 82 | 83 | $contents = match (true) { 84 | $file instanceof StreamInterface => $file->detach(), 85 | is_resource($file) => $file, 86 | $this->isReadableFile($file) => @fopen($file, 'rb') ?: throw CouldNotSendNotification::fileAccessFailed($file), 87 | default => $file 88 | }; 89 | 90 | $fileData = [ 91 | 'name' => $typeValue, 92 | 'contents' => $contents, 93 | ]; 94 | 95 | if ($filename !== null) { 96 | $fileData['filename'] = $filename; 97 | } 98 | 99 | $this->payload['file'] = $fileData; 100 | 101 | return $this; 102 | } 103 | 104 | /** 105 | * Attach a photo. 106 | */ 107 | public function photo(string $file): self 108 | { 109 | return $this->file($file, FileType::Photo); 110 | } 111 | 112 | /** 113 | * Attach an audio file. 114 | */ 115 | public function audio(string $file): self 116 | { 117 | return $this->file($file, FileType::Audio); 118 | } 119 | 120 | /** 121 | * Attach a document file. 122 | */ 123 | public function document(string $file, ?string $filename = null): self 124 | { 125 | return $this->file($file, FileType::Document, $filename); 126 | } 127 | 128 | /** 129 | * Attach a video file. 130 | */ 131 | public function video(string $file): self 132 | { 133 | return $this->file($file, FileType::Video); 134 | } 135 | 136 | /** 137 | * Attach an animation file. 138 | */ 139 | public function animation(string $file): self 140 | { 141 | return $this->file($file, FileType::Animation); 142 | } 143 | 144 | /** 145 | * Attach a voice message file. 146 | */ 147 | public function voice(string $file): self 148 | { 149 | return $this->file($file, FileType::Voice); 150 | } 151 | 152 | /** 153 | * Attach a video note file. 154 | */ 155 | public function videoNote(string $file): self 156 | { 157 | return $this->file($file, FileType::VideoNote); 158 | } 159 | 160 | /** 161 | * Attach a sticker. 162 | */ 163 | public function sticker(string $file): self 164 | { 165 | return $this->file($file, FileType::Sticker); 166 | } 167 | 168 | /** 169 | * Use a Laravel Blade view as the content. 170 | */ 171 | public function view(string $view, array $data = [], array $mergeData = []): self 172 | { 173 | return $this->content(View::make($view, $data, $mergeData)->render()); 174 | } 175 | 176 | /** 177 | * Check if a file is attached. 178 | */ 179 | public function hasFile(): bool 180 | { 181 | return isset($this->payload['file']); 182 | } 183 | 184 | /** 185 | * Check if the current file type supports captions. 186 | */ 187 | protected function supportsCaptions(): bool 188 | { 189 | return ! in_array($this->type, $this->captionUnsupportedTypes); 190 | } 191 | 192 | /** 193 | * Convert the notification to an array for API consumption. 194 | */ 195 | public function toArray(): array 196 | { 197 | $payload = $this->payload; 198 | 199 | // Remove caption for unsupported file types 200 | if (! $this->supportsCaptions() && isset($payload['caption'])) { 201 | unset($payload['caption']); 202 | } 203 | 204 | return $this->hasFile() ? $this->toMultipart($payload) : $payload; 205 | } 206 | 207 | /** 208 | * Create multipart array for file uploads. 209 | */ 210 | public function toMultipart(?array $payload = null): array 211 | { 212 | $payload = $payload ?? $this->payload; 213 | $data = []; 214 | 215 | foreach ($payload as $name => $contents) { 216 | $data[] = ($name === 'file') 217 | ? $contents 218 | : compact('name', 'contents'); 219 | } 220 | 221 | return $data; 222 | } 223 | 224 | /** 225 | * Send the notification through Telegram. 226 | * 227 | * @throws CouldNotSendNotification 228 | */ 229 | public function send(): ?ResponseInterface 230 | { 231 | // Get the method endpoint based on file type 232 | $method = $this->type->value; 233 | 234 | return $this->telegram->sendFile( 235 | $this->toArray(), 236 | $method, 237 | $this->hasFile() 238 | ); 239 | } 240 | 241 | /** 242 | * Determine if it's a regular and readable file. 243 | */ 244 | protected function isReadableFile(string $file): bool 245 | { 246 | return is_file($file) && is_readable($file); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/TelegramLocation.php: -------------------------------------------------------------------------------- 1 | latitude($latitude); 21 | $this->longitude($longitude); 22 | } 23 | 24 | public static function create(float|string $latitude = '', float|string $longitude = ''): self 25 | { 26 | return new self($latitude, $longitude); 27 | } 28 | 29 | /** 30 | * Location's latitude. 31 | * 32 | * @return $this 33 | */ 34 | public function latitude(float|string $latitude): self 35 | { 36 | $this->payload['latitude'] = $latitude; 37 | 38 | return $this; 39 | } 40 | 41 | /** 42 | * Location's longitude. 43 | * 44 | * @return $this 45 | */ 46 | public function longitude(float|string $longitude): self 47 | { 48 | $this->payload['longitude'] = $longitude; 49 | 50 | return $this; 51 | } 52 | 53 | /** 54 | * @throws CouldNotSendNotification 55 | */ 56 | public function send(): ?ResponseInterface 57 | { 58 | return $this->telegram->sendLocation($this->toArray()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/TelegramMessage.php: -------------------------------------------------------------------------------- 1 | content($content); 27 | $this->parseMode(ParseMode::Markdown); 28 | } 29 | 30 | public static function create(string $content = ''): self 31 | { 32 | return new self($content); 33 | } 34 | 35 | public function content(string $content, ?int $limit = null): self 36 | { 37 | $this->payload['text'] = $content; 38 | if ($limit !== null) { 39 | $this->chunkSize = $limit; 40 | } 41 | 42 | return $this; 43 | } 44 | 45 | public function line(string $content): self 46 | { 47 | $this->payload['text'] .= "$content\n"; 48 | 49 | return $this; 50 | } 51 | 52 | public function lineIf(bool $condition, string $line): self 53 | { 54 | return $condition ? $this->line($line) : $this; 55 | } 56 | 57 | public function escapedLine(string $content): self 58 | { 59 | $content = str_replace('\\', '\\\\', $content); 60 | 61 | $escapedContent = preg_replace_callback( 62 | '/[_*[\]()~`>#\+\-=|{}.!]/', 63 | fn ($matches): string => "\\$matches[0]", 64 | $content 65 | ); 66 | 67 | return $this->line($escapedContent ?? $content); 68 | } 69 | 70 | public function view(string $view, array $data = [], array $mergeData = []): self 71 | { 72 | return $this->content(View::make($view, $data, $mergeData)->render()); 73 | } 74 | 75 | public function chunk(int $limit = self::DEFAULT_CHUNK_SIZE): self 76 | { 77 | $this->chunkSize = $limit; 78 | 79 | return $this; 80 | } 81 | 82 | public function shouldChunk(): bool 83 | { 84 | return $this->chunkSize > 0; 85 | } 86 | 87 | /** 88 | * @return array>|ResponseInterface|null 89 | * 90 | * @throws CouldNotSendNotification 91 | * @throws JsonException 92 | */ 93 | public function send(): array|ResponseInterface|null 94 | { 95 | return $this->shouldChunk() 96 | ? $this->sendChunkedMessage($this->toArray()) 97 | : $this->telegram->sendMessage($this->toArray()); 98 | } 99 | 100 | /** 101 | * @param array $params 102 | * @return array> 103 | * 104 | * @throws CouldNotSendNotification 105 | * @throws JsonException 106 | */ 107 | private function sendChunkedMessage(array $params): array 108 | { 109 | $replyMarkup = $this->getPayloadValue('reply_markup'); 110 | 111 | if ($replyMarkup) { 112 | unset($params['reply_markup']); 113 | } 114 | 115 | $messages = $this->chunkStrings($params['text'], $this->chunkSize); 116 | $lastIndex = count($messages) - 1; 117 | 118 | return Collection::make($messages) 119 | ->filter() 120 | ->map(function (string $text, int $index) use ($params, $replyMarkup, $lastIndex): ?array { 121 | $payload = [...$params, 'text' => $text]; 122 | if ($index === $lastIndex && $replyMarkup !== null) { 123 | $payload['reply_markup'] = $replyMarkup; 124 | } 125 | 126 | $response = $this->telegram->sendMessage($payload); 127 | sleep(1); // Rate limiting 128 | 129 | return $response ? json_decode( 130 | $response->getBody()->getContents(), 131 | true, 132 | 512, 133 | JSON_THROW_ON_ERROR 134 | ) : null; 135 | }) 136 | ->filter() 137 | ->values() 138 | ->all(); 139 | } 140 | 141 | /** 142 | * @return array 143 | */ 144 | private function chunkStrings(string $value, int $limit = self::DEFAULT_CHUNK_SIZE): array 145 | { 146 | if (mb_strwidth($value, 'UTF-8') <= $limit) { 147 | return [$value]; 148 | } 149 | 150 | $limit = min($limit, self::DEFAULT_CHUNK_SIZE); 151 | 152 | $output = explode(self::CHUNK_SEPARATOR, wordwrap($value, $limit, self::CHUNK_SEPARATOR)); 153 | 154 | return count($output) <= 1 155 | ? mb_str_split($value, $limit, 'UTF-8') 156 | : $output; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/TelegramPoll.php: -------------------------------------------------------------------------------- 1 | question($question); 18 | } 19 | 20 | public static function create(string $question = ''): self 21 | { 22 | return new self($question); 23 | } 24 | 25 | /** 26 | * Poll question. 27 | * 28 | * @return $this 29 | */ 30 | public function question(string $question): self 31 | { 32 | $this->payload['question'] = $question; 33 | 34 | return $this; 35 | } 36 | 37 | /** 38 | * Poll choices. 39 | * 40 | * @return $this 41 | */ 42 | public function choices(array $choices): self 43 | { 44 | $this->payload['options'] = json_encode($choices, JSON_THROW_ON_ERROR); 45 | 46 | return $this; 47 | } 48 | 49 | /** 50 | * @throws CouldNotSendNotification 51 | */ 52 | public function send(): ?ResponseInterface 53 | { 54 | return $this->telegram->sendPoll($this->toArray()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/TelegramServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->bind(Telegram::class, static fn () => new Telegram( 21 | config('services.telegram-bot-api.token'), 22 | app(HttpClient::class), 23 | config('services.telegram-bot-api.base_uri') 24 | )); 25 | 26 | Notification::resolved(static function (ChannelManager $service) { 27 | $service->extend('telegram', static fn ($app) => $app->make(TelegramChannel::class)); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/TelegramUpdates.php: -------------------------------------------------------------------------------- 1 | payload['limit'] = $limit; 25 | 26 | return $this; 27 | } 28 | 29 | /** 30 | * Additional options. 31 | * 32 | * @return $this 33 | */ 34 | public function options(array $options): self 35 | { 36 | $this->payload = array_merge($this->payload, $options); 37 | 38 | return $this; 39 | } 40 | 41 | public function latest(): self 42 | { 43 | $this->payload['offset'] = -1; 44 | 45 | return $this; 46 | } 47 | 48 | public function get(): array 49 | { 50 | $response = app(Telegram::class)->getUpdates($this->payload); 51 | 52 | return json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); 53 | } 54 | 55 | public function toArray(): array 56 | { 57 | return $this->payload; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/TelegramVenue.php: -------------------------------------------------------------------------------- 1 | latitude($latitude); 25 | $this->longitude($longitude); 26 | $this->title($title); 27 | $this->address($address); 28 | } 29 | 30 | public static function create( 31 | float|string $latitude = '', 32 | float|string $longitude = '', 33 | string $title = '', 34 | string $address = '' 35 | ): self { 36 | return new self($latitude, $longitude, $title, $address); 37 | } 38 | 39 | /** 40 | * Venue's latitude. 41 | * 42 | * @return $this 43 | */ 44 | public function latitude(float|string $latitude): self 45 | { 46 | $this->payload['latitude'] = $latitude; 47 | 48 | return $this; 49 | } 50 | 51 | /** 52 | * Venue's longitude. 53 | * 54 | * @return $this 55 | */ 56 | public function longitude(float|string $longitude): self 57 | { 58 | $this->payload['longitude'] = $longitude; 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * Venue's name/title. 65 | * 66 | * @return $this 67 | */ 68 | public function title(string $title): self 69 | { 70 | $this->payload['title'] = $title; 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * Venue's address. 77 | * 78 | * @return $this 79 | */ 80 | public function address(string $address): self 81 | { 82 | $this->payload['address'] = $address; 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * Optional: Foursquare ID. 89 | * 90 | * @return $this 91 | */ 92 | public function foursquareId(string $foursquareId): self 93 | { 94 | $this->payload['foursquare_id'] = $foursquareId; 95 | 96 | return $this; 97 | } 98 | 99 | /** 100 | * Optional: Foursquare Type. 101 | * 102 | * @return $this 103 | */ 104 | public function foursquareType(string $foursquareType): self 105 | { 106 | $this->payload['foursquare_type'] = $foursquareType; 107 | 108 | return $this; 109 | } 110 | 111 | /** 112 | * Optional: Google Place ID. 113 | * 114 | * @return $this 115 | */ 116 | public function googlePlaceId(string $googlePlaceId): self 117 | { 118 | $this->payload['google_place_id'] = $googlePlaceId; 119 | 120 | return $this; 121 | } 122 | 123 | /** 124 | * Optional: Google Place Type. 125 | * 126 | * @return $this 127 | */ 128 | public function googlePlaceType(string $googlePlaceType): self 129 | { 130 | $this->payload['google_place_type'] = $googlePlaceType; 131 | 132 | return $this; 133 | } 134 | 135 | /** 136 | * @throws CouldNotSendNotification 137 | */ 138 | public function send(): ?ResponseInterface 139 | { 140 | return $this->telegram->sendVenue($this->toArray()); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Traits/HasSharedLogic.php: -------------------------------------------------------------------------------- 1 | Params payload */ 25 | protected array $payload = []; 26 | 27 | /** @var array> Keyboard Buttons */ 28 | protected array $keyboards = []; 29 | 30 | /** @var array> Inline Keyboard Buttons */ 31 | protected array $buttons = []; 32 | 33 | /** @var bool|null Condition for sending the message */ 34 | private ?bool $sendCondition = null; 35 | 36 | /** @var Closure|null Callback function to handle exceptions */ 37 | public ?Closure $exceptionHandler = null; 38 | 39 | /** 40 | * Set the recipient's Chat ID. 41 | * 42 | * @param int|string $chatId The unique identifier for the target chat 43 | */ 44 | public function to(int|string $chatId): static 45 | { 46 | $this->payload['chat_id'] = $chatId; 47 | 48 | return $this; 49 | } 50 | 51 | /** 52 | * Set the keyboard markup for the message. 53 | * 54 | * @param array $markup The keyboard markup array 55 | * 56 | * @throws JsonException When JSON encoding fails 57 | */ 58 | public function keyboardMarkup(array $markup): static 59 | { 60 | $this->payload['reply_markup'] = json_encode($markup, JSON_THROW_ON_ERROR); 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * Unset parse mode of the message. 67 | */ 68 | public function normal(): static 69 | { 70 | unset($this->payload['parse_mode']); 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * Set the parse mode of the message. 77 | * 78 | * @param ParseMode|string $mode The parse mode to use 79 | */ 80 | public function parseMode(ParseMode|string $mode): static 81 | { 82 | $this->payload['parse_mode'] = ($mode instanceof ParseMode) ? $mode->value : $mode; 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * Add a normal keyboard button. 89 | * 90 | * @param string $text The text to display on the button 91 | * @param int $columns Number of columns for button layout 92 | * @param bool $requestContact Whether to request user's contact 93 | * @param bool $requestLocation Whether to request user's location 94 | * 95 | * @throws JsonException When JSON encoding fails 96 | */ 97 | public function keyboard( 98 | string $text, 99 | int $columns = 2, 100 | bool $requestContact = false, 101 | bool $requestLocation = false 102 | ): static { 103 | $this->keyboards[] = [ 104 | 'text' => $text, 105 | 'request_contact' => $requestContact, 106 | 'request_location' => $requestLocation, 107 | ]; 108 | 109 | $this->keyboardMarkup([ 110 | 'keyboard' => array_chunk($this->keyboards, $columns), 111 | 'one_time_keyboard' => true, 112 | 'resize_keyboard' => true, 113 | ]); 114 | 115 | return $this; 116 | } 117 | 118 | /** 119 | * Add an inline button with URL. 120 | * 121 | * @param string $text The text to display on the button 122 | * @param string $url The URL to open when button is pressed 123 | * @param int $columns Number of columns for button layout 124 | * 125 | * @throws JsonException When JSON encoding fails 126 | */ 127 | public function button(string $text, string $url, int $columns = 2): static 128 | { 129 | $this->buttons[] = compact('text', 'url'); 130 | 131 | return $this->updateInlineKeyboard($columns); 132 | } 133 | 134 | /** 135 | * Add an inline button with callback data. 136 | * 137 | * @param string $text The text to display on the button 138 | * @param string $callbackData The data to send when button is pressed 139 | * @param int $columns Number of columns for button layout 140 | * 141 | * @throws JsonException When JSON encoding fails 142 | */ 143 | public function buttonWithCallback(string $text, string $callbackData, int $columns = 2): static 144 | { 145 | $this->buttons[] = [ 146 | 'text' => $text, 147 | 'callback_data' => $callbackData, 148 | ]; 149 | 150 | return $this->updateInlineKeyboard($columns); 151 | } 152 | 153 | /** 154 | * Add an inline button with web app. 155 | * 156 | * @param string $text The text to display on the button 157 | * @param string $url The URL of the Web App to open 158 | * @param int $columns Number of columns for button layout 159 | * 160 | * @throws JsonException When JSON encoding fails 161 | */ 162 | public function buttonWithWebApp(string $text, string $url, int $columns = 2): static 163 | { 164 | $this->buttons[] = [ 165 | 'text' => $text, 166 | 'web_app' => ['url' => $url], 167 | ]; 168 | 169 | return $this->updateInlineKeyboard($columns); 170 | } 171 | 172 | /** 173 | * Send the message silently. Users will receive a notification with no sound. 174 | * 175 | * @param bool $disable Whether to disable the notification sound 176 | */ 177 | public function disableNotification(bool $disable = true): static 178 | { 179 | $this->payload['disable_notification'] = $disable; 180 | 181 | return $this; 182 | } 183 | 184 | /** 185 | * Set the Bot Token. Overrides default bot token with the given value for this notification. 186 | * 187 | * @param string $token The bot token to use 188 | */ 189 | public function token(string $token): static 190 | { 191 | $this->token = $token; 192 | 193 | return $this; 194 | } 195 | 196 | /** 197 | * Determine if bot token is given for this notification. 198 | */ 199 | public function hasToken(): bool 200 | { 201 | return $this->token !== null; 202 | } 203 | 204 | /** 205 | * Set additional options to pass to sendMessage method. 206 | * 207 | * @param array $options Additional options 208 | */ 209 | public function options(array $options): static 210 | { 211 | $this->payload = [...$this->payload, ...$options]; 212 | 213 | return $this; 214 | } 215 | 216 | /** 217 | * Registers a callback function to handle exceptions. 218 | * 219 | * This method allows you to define a custom error handler, 220 | * which will be invoked if an exception occurs during the 221 | * notification process. The callback must be a valid Closure. 222 | * 223 | * @param Closure $callback The closure that will handle exceptions. 224 | */ 225 | public function onError(Closure $callback): self 226 | { 227 | $this->exceptionHandler = $callback; 228 | 229 | return $this; 230 | } 231 | 232 | /** 233 | * Set a condition for sending the message. 234 | * 235 | * @param bool|callable $condition The condition to evaluate 236 | */ 237 | public function sendWhen(bool|callable $condition): static 238 | { 239 | $this->sendCondition = $this->when($condition, fn () => true, fn () => false); 240 | 241 | return $this; 242 | } 243 | 244 | /** 245 | * Determine if the message can be sent based on the condition. 246 | */ 247 | public function canSend(): bool 248 | { 249 | return $this->sendCondition ?? true; 250 | } 251 | 252 | /** 253 | * Determine if chat id is not given. 254 | */ 255 | public function toNotGiven(): bool 256 | { 257 | return ! isset($this->payload['chat_id']); 258 | } 259 | 260 | /** 261 | * Get payload value for given key. 262 | * 263 | * @param string $key The key to retrieve from payload 264 | * @return mixed The value from payload or null if not found 265 | */ 266 | public function getPayloadValue(string $key): mixed 267 | { 268 | return $this->payload[$key] ?? null; 269 | } 270 | 271 | /** 272 | * Get the complete payload as array. 273 | * 274 | * @return array 275 | */ 276 | public function toArray(): array 277 | { 278 | return $this->payload; 279 | } 280 | 281 | /** 282 | * Convert the object into something JSON serializable. 283 | * 284 | * @return array 285 | */ 286 | public function jsonSerialize(): array 287 | { 288 | return $this->toArray(); 289 | } 290 | 291 | /** 292 | * Update the inline keyboard markup. 293 | * 294 | * @param int $columns Number of columns for button layout 295 | * 296 | * @throws JsonException When JSON encoding fails 297 | */ 298 | private function updateInlineKeyboard(int $columns): static 299 | { 300 | return $this->keyboardMarkup([ 301 | 'inline_keyboard' => array_chunk($this->buttons, $columns), 302 | ]); 303 | } 304 | } 305 | --------------------------------------------------------------------------------