From 197a7949bd39852e2346c1fb587f889000f38900 Mon Sep 17 00:00:00 2001 From: LIERLIER <1113093541@qq.com> Date: Sat, 16 Sep 2023 10:23:58 +0800 Subject: [PATCH] base --- .gitignore | 4 + Commands/Config/DateCommand.php | 245 +++++++++++++++++ Commands/Config/README.md | 28 ++ Commands/Config/WeatherCommand.php | 156 +++++++++++ Commands/Conversation/CancelCommand.php | 105 +++++++ .../Conversation/GenericmessageCommand.php | 85 ++++++ Commands/Conversation/README.md | 9 + Commands/Conversation/SurveyCommand.php | 258 ++++++++++++++++++ Commands/GenericCommand.php | 61 +++++ Commands/Group/GenericmessageCommand.php | 82 ++++++ Commands/Group/LeftchatmemberCommand.php | 58 ++++ Commands/Group/NewchatmembersCommand.php | 67 +++++ Commands/Group/README.md | 6 + .../InlineMode/ChoseninlineresultCommand.php | 54 ++++ Commands/InlineMode/InlinequeryCommand.php | 100 +++++++ Commands/InlineMode/README.md | 7 + Commands/Keyboard/CallbackqueryCommand.php | 61 +++++ Commands/Keyboard/ForcereplyCommand.php | 60 ++++ Commands/Keyboard/HidekeyboardCommand.php | 61 +++++ Commands/Keyboard/InlinekeyboardCommand.php | 72 +++++ Commands/Keyboard/KeyboardCommand.php | 102 +++++++ Commands/Keyboard/README.md | 13 + Commands/Message/ChannelpostCommand.php | 53 ++++ Commands/Message/EditedchannelpostCommand.php | 53 ++++ Commands/Message/EditedmessageCommand.php | 52 ++++ Commands/Message/EditmessageCommand.php | 82 ++++++ Commands/Message/GenericmessageCommand.php | 61 +++++ Commands/Message/README.md | 15 + Commands/Other/EchoCommand.php | 64 +++++ Commands/Other/HelpCommand.php | 133 +++++++++ Commands/Other/ImageCommand.php | 110 ++++++++ Commands/Other/MarkdownCommand.php | 68 +++++ Commands/Other/README.md | 5 + Commands/Other/SlapCommand.php | 67 +++++ Commands/Other/UploadCommand.php | 115 ++++++++ Commands/Other/WhoamiCommand.php | 124 +++++++++ Commands/Payments/GenericmessageCommand.php | 60 ++++ Commands/Payments/PaymentCommand.php | 125 +++++++++ Commands/Payments/PrecheckoutqueryCommand.php | 57 ++++ Commands/Payments/README.md | 39 +++ Commands/Payments/ShippingqueryCommand.php | 80 ++++++ Commands/README.MD | 25 ++ .../ServiceMessages/GenericmessageCommand.php | 74 +++++ Commands/StartCommand.php | 72 +++++ CustomCommands/README.md | 13 + README.md | 13 + composer.json | 31 +++ config.php | 92 +++++++ cron.php | 50 ++++ getUpdatesCLI.php | 53 ++++ hook.php | 78 ++++++ manager.php | 42 +++ phpcs.xml.dist | 26 ++ set.php | 42 +++ unset.php | 34 +++ 55 files changed, 3702 insertions(+) create mode 100644 .gitignore create mode 100644 Commands/Config/DateCommand.php create mode 100644 Commands/Config/README.md create mode 100644 Commands/Config/WeatherCommand.php create mode 100644 Commands/Conversation/CancelCommand.php create mode 100644 Commands/Conversation/GenericmessageCommand.php create mode 100644 Commands/Conversation/README.md create mode 100644 Commands/Conversation/SurveyCommand.php create mode 100644 Commands/GenericCommand.php create mode 100644 Commands/Group/GenericmessageCommand.php create mode 100644 Commands/Group/LeftchatmemberCommand.php create mode 100644 Commands/Group/NewchatmembersCommand.php create mode 100644 Commands/Group/README.md create mode 100644 Commands/InlineMode/ChoseninlineresultCommand.php create mode 100644 Commands/InlineMode/InlinequeryCommand.php create mode 100644 Commands/InlineMode/README.md create mode 100644 Commands/Keyboard/CallbackqueryCommand.php create mode 100644 Commands/Keyboard/ForcereplyCommand.php create mode 100644 Commands/Keyboard/HidekeyboardCommand.php create mode 100644 Commands/Keyboard/InlinekeyboardCommand.php create mode 100644 Commands/Keyboard/KeyboardCommand.php create mode 100644 Commands/Keyboard/README.md create mode 100644 Commands/Message/ChannelpostCommand.php create mode 100644 Commands/Message/EditedchannelpostCommand.php create mode 100644 Commands/Message/EditedmessageCommand.php create mode 100644 Commands/Message/EditmessageCommand.php create mode 100644 Commands/Message/GenericmessageCommand.php create mode 100644 Commands/Message/README.md create mode 100644 Commands/Other/EchoCommand.php create mode 100644 Commands/Other/HelpCommand.php create mode 100644 Commands/Other/ImageCommand.php create mode 100644 Commands/Other/MarkdownCommand.php create mode 100644 Commands/Other/README.md create mode 100644 Commands/Other/SlapCommand.php create mode 100644 Commands/Other/UploadCommand.php create mode 100644 Commands/Other/WhoamiCommand.php create mode 100644 Commands/Payments/GenericmessageCommand.php create mode 100644 Commands/Payments/PaymentCommand.php create mode 100644 Commands/Payments/PrecheckoutqueryCommand.php create mode 100644 Commands/Payments/README.md create mode 100644 Commands/Payments/ShippingqueryCommand.php create mode 100644 Commands/README.MD create mode 100644 Commands/ServiceMessages/GenericmessageCommand.php create mode 100644 Commands/StartCommand.php create mode 100644 CustomCommands/README.md create mode 100644 README.md create mode 100644 composer.json create mode 100644 config.php create mode 100644 cron.php create mode 100644 getUpdatesCLI.php create mode 100644 hook.php create mode 100644 manager.php create mode 100644 phpcs.xml.dist create mode 100644 set.php create mode 100644 unset.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4245590 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +composer.phar +composer.lock +#config.php +vendor diff --git a/Commands/Config/DateCommand.php b/Commands/Config/DateCommand.php new file mode 100644 index 0000000..f9f0444 --- /dev/null +++ b/Commands/Config/DateCommand.php @@ -0,0 +1,245 @@ + ['google_api_key' => 'your_google_api_key_here'] + */ + +namespace Longman\TelegramBot\Commands\UserCommands; + +use GuzzleHttp\Client; +use GuzzleHttp\Exception\RequestException; +use Longman\TelegramBot\Commands\UserCommand; +use Longman\TelegramBot\Entities\ServerResponse; +use Longman\TelegramBot\Exception\TelegramException; +use Longman\TelegramBot\Request; +use Longman\TelegramBot\TelegramLog; + +class DateCommand extends UserCommand +{ + /** + * @var string + */ + protected $name = 'date'; + + /** + * @var string + */ + protected $description = 'Show date/time by location'; + + /** + * @var string + */ + protected $usage = '/date '; + + /** + * @var string + */ + protected $version = '1.5.0'; + + /** + * Guzzle Client object + * + * @var Client + */ + private $client; + + /** + * Base URI for Google Maps API + * + * @var string + */ + private $google_api_base_uri = 'https://maps.googleapis.com/maps/api/'; + + /** + * The Google API Key from the command config + * + * @var string + */ + private $google_api_key; + + /** + * Date format + * + * @var string + */ + private $date_format = 'd-m-Y H:i:s'; + + /** + * Get coordinates of passed location + * + * @param string $location + * + * @return array + */ + private function getCoordinates($location): array + { + $path = 'geocode/json'; + $query = ['address' => urlencode($location)]; + + if ($this->google_api_key !== null) { + $query['key'] = $this->google_api_key; + } + + try { + $response = $this->client->get($path, ['query' => $query]); + } catch (RequestException $e) { + TelegramLog::error($e->getMessage()); + + return []; + } + + if (!($data = $this->validateResponseData($response->getBody()))) { + return []; + } + + $result = $data['results'][0]; + $lat = $result['geometry']['location']['lat']; + $lng = $result['geometry']['location']['lng']; + $acc = $result['geometry']['location_type']; + $types = $result['types']; + + return [$lat, $lng, $acc, $types]; + } + + /** + * Get date for location passed via coordinates + * + * @param string $lat + * @param string $lng + * + * @return array + * @throws \Exception + */ + private function getDate($lat, $lng): array + { + $path = 'timezone/json'; + + $date_utc = new \DateTimeImmutable(null, new \DateTimeZone('UTC')); + $timestamp = $date_utc->format('U'); + + $query = [ + 'location' => urlencode($lat) . ',' . urlencode($lng), + 'timestamp' => urlencode($timestamp), + ]; + + if ($this->google_api_key !== null) { + $query['key'] = $this->google_api_key; + } + + try { + $response = $this->client->get($path, ['query' => $query]); + } catch (RequestException $e) { + TelegramLog::error($e->getMessage()); + + return []; + } + + if (!($data = $this->validateResponseData($response->getBody()))) { + return []; + } + + $local_time = $timestamp + $data['rawOffset'] + $data['dstOffset']; + + return [$local_time, $data['timeZoneId']]; + } + + /** + * Evaluate the response data and see if the request was successful + * + * @param string $data + * + * @return array + */ + private function validateResponseData($data): array + { + if (empty($data)) { + return []; + } + + $data = json_decode($data, true); + if (empty($data)) { + return []; + } + + if (isset($data['status']) && $data['status'] !== 'OK') { + return []; + } + + return $data; + } + + /** + * Get formatted date at the passed location + * + * @param string $location + * + * @return string + * @throws \Exception + */ + private function getFormattedDate($location): string + { + if ($location === null || $location === '') { + return 'The time in nowhere is never'; + } + + [$lat, $lng] = $this->getCoordinates($location); + if (empty($lat) || empty($lng)) { + return 'It seems that in "' . $location . '" they do not have a concept of time.'; + } + + [$local_time, $timezone_id] = $this->getDate($lat, $lng); + + $date_utc = new \DateTimeImmutable(gmdate('Y-m-d H:i:s', $local_time), new \DateTimeZone($timezone_id)); + + return 'The local time in ' . $timezone_id . ' is: ' . $date_utc->format($this->date_format); + } + + /** + * Main command execution + * + * @return ServerResponse + * @throws TelegramException + */ + public function execute(): ServerResponse + { + // First we set up the necessary member variables. + $this->client = new Client(['base_uri' => $this->google_api_base_uri]); + if (($this->google_api_key = trim($this->getConfig('google_api_key'))) === '') { + $this->google_api_key = null; + } + + $message = $this->getMessage(); + + $chat_id = $message->getChat()->getId(); + $location = $message->getText(true); + + $text = 'You must specify location in format: /date '; + + if ($location !== '') { + $text = $this->getFormattedDate($location); + } + + $data = [ + 'chat_id' => $chat_id, + 'text' => $text, + ]; + + return Request::sendMessage($data); + } +} diff --git a/Commands/Config/README.md b/Commands/Config/README.md new file mode 100644 index 0000000..b69ccc7 --- /dev/null +++ b/Commands/Config/README.md @@ -0,0 +1,28 @@ +# Config + +Custom configurations can be passed to commands that support them. + +This feature is mainly used to pass secrets or special values to the commands. + +## Adding configurations to your config + +It is very easy to add configurations to `config.php`: +```php +'commands' => [ + 'configs' => [ + 'yourcommand' => ['your_config_key' => 'your_config_value'], + ], +], +``` + +Alternatively, you can set them directly via code in your `hook.php`: +```php +$telegram->setCommandConfig('yourcommand', ['your_config_key' => 'your_config_value']); +``` + +## Reading configurations in your command + +To read any command configurations, you can fetch them from within your command like this: +```php +$my_config = $this->getConfig('your_config_key'); // 'your_config_value' +``` diff --git a/Commands/Config/WeatherCommand.php b/Commands/Config/WeatherCommand.php new file mode 100644 index 0000000..ce7df1d --- /dev/null +++ b/Commands/Config/WeatherCommand.php @@ -0,0 +1,156 @@ + ['owm_api_key' => 'your_owm_api_key_here'] + */ + +namespace Longman\TelegramBot\Commands\UserCommands; + +use Exception; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\RequestException; +use Longman\TelegramBot\Commands\UserCommand; +use Longman\TelegramBot\Entities\ServerResponse; +use Longman\TelegramBot\Exception\TelegramException; +use Longman\TelegramBot\TelegramLog; + +class WeatherCommand extends UserCommand +{ + /** + * @var string + */ + protected $name = 'weather'; + + /** + * @var string + */ + protected $description = 'Show weather by location'; + + /** + * @var string + */ + protected $usage = '/weather '; + + /** + * @var string + */ + protected $version = '1.3.0'; + + /** + * Base URI for OpenWeatherMap API + * + * @var string + */ + private $owm_api_base_uri = 'http://api.openweathermap.org/data/2.5/'; + + /** + * Get weather data using HTTP request + * + * @param string $location + * + * @return string + */ + private function getWeatherData($location): string + { + $client = new Client(['base_uri' => $this->owm_api_base_uri]); + $path = 'weather'; + $query = [ + 'q' => $location, + 'units' => 'metric', + 'APPID' => trim($this->getConfig('owm_api_key')), + ]; + + try { + $response = $client->get($path, ['query' => $query]); + } catch (RequestException $e) { + TelegramLog::error($e->getMessage()); + + return ''; + } + + return (string) $response->getBody(); + } + + /** + * Get weather string from weather data + * + * @param array $data + * + * @return string + */ + private function getWeatherString(array $data): string + { + try { + if (!(isset($data['cod']) && $data['cod'] === 200)) { + return ''; + } + + //http://openweathermap.org/weather-conditions + $conditions = [ + 'clear' => ' ☀️', + 'clouds' => ' ☁️', + 'rain' => ' ☔', + 'drizzle' => ' ☔', + 'thunderstorm' => ' ⚡️', + 'snow' => ' ❄️', + ]; + $conditions_now = strtolower($data['weather'][0]['main']); + + return sprintf( + 'The temperature in %s (%s) is %s°C' . PHP_EOL . + 'Current conditions are: %s%s', + $data['name'], //city + $data['sys']['country'], //country + $data['main']['temp'], //temperature + $data['weather'][0]['description'], //description of weather + $conditions[$conditions_now] ?? '' + ); + } catch (Exception $e) { + TelegramLog::error($e->getMessage()); + + return ''; + } + } + + /** + * Main command execution + * + * @return ServerResponse + * @throws TelegramException + */ + public function execute(): ServerResponse + { + // Check to make sure the required OWM API key has been defined. + $owm_api_key = $this->getConfig('owm_api_key'); + if (empty($owm_api_key)) { + return $this->replyToChat('OpenWeatherMap API key not defined.'); + } + + $location = trim($this->getMessage()->getText(true)); + if ($location === '') { + return $this->replyToChat('You must specify a location as: ' . $this->getUsage()); + } + + $text = 'Cannot find weather for location: ' . $location; + if ($weather_data = json_decode($this->getWeatherData($location), true)) { + $text = $this->getWeatherString($weather_data); + } + return $this->replyToChat($text); + } +} diff --git a/Commands/Conversation/CancelCommand.php b/Commands/Conversation/CancelCommand.php new file mode 100644 index 0000000..4fa034d --- /dev/null +++ b/Commands/Conversation/CancelCommand.php @@ -0,0 +1,105 @@ +removeKeyboard('Nothing to cancel.'); + } + + /** + * Main command execution + * + * @return ServerResponse + * @throws TelegramException + */ + public function execute(): ServerResponse + { + $text = 'No active conversation!'; + + // Cancel current conversation if any + $conversation = new Conversation( + $this->getMessage()->getFrom()->getId(), + $this->getMessage()->getChat()->getId() + ); + + if ($conversation_command = $conversation->getCommand()) { + $conversation->cancel(); + $text = 'Conversation "' . $conversation_command . '" cancelled!'; + } + + return $this->removeKeyboard($text); + } + + /** + * Remove the keyboard and output a text. + * + * @param string $text + * + * @return ServerResponse + * @throws TelegramException + */ + private function removeKeyboard(string $text): ServerResponse + { + return $this->replyToChat($text, [ + 'reply_markup' => Keyboard::remove(['selective' => true]), + ]); + } +} diff --git a/Commands/Conversation/GenericmessageCommand.php b/Commands/Conversation/GenericmessageCommand.php new file mode 100644 index 0000000..5d10822 --- /dev/null +++ b/Commands/Conversation/GenericmessageCommand.php @@ -0,0 +1,85 @@ +getMessage(); + + // If a conversation is busy, execute the conversation command after handling the message. + $conversation = new Conversation( + $message->getFrom()->getId(), + $message->getChat()->getId() + ); + + // Fetch conversation command if it exists and execute it. + if ($conversation->exists() && $command = $conversation->getCommand()) { + return $this->telegram->executeCommand($command); + } + + return Request::emptyResponse(); + } +} diff --git a/Commands/Conversation/README.md b/Commands/Conversation/README.md new file mode 100644 index 0000000..ca4e84f --- /dev/null +++ b/Commands/Conversation/README.md @@ -0,0 +1,9 @@ +# Conversation + +Conversations can be used to create dialogues with users, to collect information in a "conversational" style. + +Look at the [`SurveyCommand`](SurveyCommand.php) to see how a conversation can be made. + +For conversations to work, you must add the code provided in [`GenericmessageCommand.php`](GenericmessageCommand.php) at the beginning of your custom `GenericmessageCommand::execute()` method. + +The [`CancelCommand`](CancelCommand.php) allows users to cancel any active conversation. diff --git a/Commands/Conversation/SurveyCommand.php b/Commands/Conversation/SurveyCommand.php new file mode 100644 index 0000000..6dd710c --- /dev/null +++ b/Commands/Conversation/SurveyCommand.php @@ -0,0 +1,258 @@ +getMessage(); + + $chat = $message->getChat(); + $user = $message->getFrom(); + $text = trim($message->getText(true)); + $chat_id = $chat->getId(); + $user_id = $user->getId(); + + // Preparing response + $data = [ + 'chat_id' => $chat_id, + // Remove any keyboard by default + 'reply_markup' => Keyboard::remove(['selective' => true]), + ]; + + if ($chat->isGroupChat() || $chat->isSuperGroup()) { + // Force reply is applied by default so it can work with privacy on + $data['reply_markup'] = Keyboard::forceReply(['selective' => true]); + } + + // Conversation start + $this->conversation = new Conversation($user_id, $chat_id, $this->getName()); + + // Load any existing notes from this conversation + $notes = &$this->conversation->notes; + !is_array($notes) && $notes = []; + + // Load the current state of the conversation + $state = $notes['state'] ?? 0; + + $result = Request::emptyResponse(); + + // State machine + // Every time a step is achieved the state is updated + switch ($state) { + case 0: + if ($text === '') { + $notes['state'] = 0; + $this->conversation->update(); + + $data['text'] = 'Type your name:'; + + $result = Request::sendMessage($data); + break; + } + + $notes['name'] = $text; + $text = ''; + + // No break! + case 1: + if ($text === '') { + $notes['state'] = 1; + $this->conversation->update(); + + $data['text'] = 'Type your surname:'; + + $result = Request::sendMessage($data); + break; + } + + $notes['surname'] = $text; + $text = ''; + + // No break! + case 2: + if ($text === '' || !is_numeric($text)) { + $notes['state'] = 2; + $this->conversation->update(); + + $data['text'] = 'Type your age:'; + if ($text !== '') { + $data['text'] = 'Age must be a number'; + } + + $result = Request::sendMessage($data); + break; + } + + $notes['age'] = $text; + $text = ''; + + // No break! + case 3: + if ($text === '' || !in_array($text, ['M', 'F'], true)) { + $notes['state'] = 3; + $this->conversation->update(); + + $data['reply_markup'] = (new Keyboard(['M', 'F'])) + ->setResizeKeyboard(true) + ->setOneTimeKeyboard(true) + ->setSelective(true); + + $data['text'] = 'Select your gender:'; + if ($text !== '') { + $data['text'] = 'Choose a keyboard option to select your gender'; + } + + $result = Request::sendMessage($data); + break; + } + + $notes['gender'] = $text; + + // No break! + case 4: + if ($message->getLocation() === null) { + $notes['state'] = 4; + $this->conversation->update(); + + $data['reply_markup'] = (new Keyboard( + (new KeyboardButton('Share Location'))->setRequestLocation(true) + )) + ->setOneTimeKeyboard(true) + ->setResizeKeyboard(true) + ->setSelective(true); + + $data['text'] = 'Share your location:'; + + $result = Request::sendMessage($data); + break; + } + + $notes['longitude'] = $message->getLocation()->getLongitude(); + $notes['latitude'] = $message->getLocation()->getLatitude(); + + // No break! + case 5: + if ($message->getPhoto() === null) { + $notes['state'] = 5; + $this->conversation->update(); + + $data['text'] = 'Insert your picture:'; + + $result = Request::sendMessage($data); + break; + } + + $photo = $message->getPhoto()[0]; + $notes['photo_id'] = $photo->getFileId(); + + // No break! + case 6: + if ($message->getContact() === null) { + $notes['state'] = 6; + $this->conversation->update(); + + $data['reply_markup'] = (new Keyboard( + (new KeyboardButton('Share Contact'))->setRequestContact(true) + )) + ->setOneTimeKeyboard(true) + ->setResizeKeyboard(true) + ->setSelective(true); + + $data['text'] = 'Share your contact information:'; + + $result = Request::sendMessage($data); + break; + } + + $notes['phone_number'] = $message->getContact()->getPhoneNumber(); + + // No break! + case 7: + $this->conversation->update(); + $out_text = '/Survey result:' . PHP_EOL; + unset($notes['state']); + foreach ($notes as $k => $v) { + $out_text .= PHP_EOL . ucfirst($k) . ': ' . $v; + } + + $data['photo'] = $notes['photo_id']; + $data['caption'] = $out_text; + + $this->conversation->stop(); + + $result = Request::sendPhoto($data); + break; + } + + return $result; + } +} diff --git a/Commands/GenericCommand.php b/Commands/GenericCommand.php new file mode 100644 index 0000000..ad1e4aa --- /dev/null +++ b/Commands/GenericCommand.php @@ -0,0 +1,61 @@ +getMessage(); + $user_id = $message->getFrom()->getId(); + $command = $message->getCommand(); + + // To enable proper use of the /whois command. + // If the user is an admin and the command is in the format "/whoisXYZ", call the /whois command + if (stripos($command, 'whois') === 0 && $this->telegram->isAdmin($user_id)) { + return $this->telegram->executeCommand('whois'); + } + + return $this->replyToChat("Command /{$command} not found.. :("); + } +} diff --git a/Commands/Group/GenericmessageCommand.php b/Commands/Group/GenericmessageCommand.php new file mode 100644 index 0000000..0fb5b72 --- /dev/null +++ b/Commands/Group/GenericmessageCommand.php @@ -0,0 +1,82 @@ +getMessage(); + + // Handle new chat members + if ($message->getNewChatMembers()) { + return $this->getTelegram()->executeCommand('newchatmembers'); + } + + // Handle left chat members + if ($message->getLeftChatMember()) { + return $this->getTelegram()->executeCommand('leftchatmember'); + } + + // The chat photo was changed + if ($new_chat_photo = $message->getNewChatPhoto()) { + // Whatever... + } + + // The chat title was changed + if ($new_chat_title = $message->getNewChatTitle()) { + // Whatever... + } + + // A message has been pinned + if ($pinned_message = $message->getPinnedMessage()) { + // Whatever... + } + + return Request::emptyResponse(); + } +} diff --git a/Commands/Group/LeftchatmemberCommand.php b/Commands/Group/LeftchatmemberCommand.php new file mode 100644 index 0000000..af41a2d --- /dev/null +++ b/Commands/Group/LeftchatmemberCommand.php @@ -0,0 +1,58 @@ +getMessage(); + $member = $message->getLeftChatMember(); + + return $this->replyToChat('Sorry to see you go, ' . $member->getFirstName()); + } +} diff --git a/Commands/Group/NewchatmembersCommand.php b/Commands/Group/NewchatmembersCommand.php new file mode 100644 index 0000000..6e37b41 --- /dev/null +++ b/Commands/Group/NewchatmembersCommand.php @@ -0,0 +1,67 @@ +getMessage(); + $members = $message->getNewChatMembers(); + + if ($message->botAddedInChat()) { + return $this->replyToChat('Hi there, you BOT!'); + } + + $member_names = []; + foreach ($members as $member) { + $member_names[] = $member->tryMention(); + } + + return $this->replyToChat('Hi ' . implode(', ', $member_names) . '!'); + } +} diff --git a/Commands/Group/README.md b/Commands/Group/README.md new file mode 100644 index 0000000..a9bf646 --- /dev/null +++ b/Commands/Group/README.md @@ -0,0 +1,6 @@ +# Group or Channel + +Requests specific to groups and channels all get handled in [`GenericmessageCommand.php`](GenericmessageCommand.php). + +The two extra commands [`NewchatmembersCommand`](NewchatmembersCommand.php) and [`LeftchatmemberCommand`](LeftchatmemberCommand.php) are simply files that can be called as commands from within a command, not by a user. +Have a look at [`GenericmessageCommand.php`](GenericmessageCommand.php) to understand what you can do. diff --git a/Commands/InlineMode/ChoseninlineresultCommand.php b/Commands/InlineMode/ChoseninlineresultCommand.php new file mode 100644 index 0000000..2719d05 --- /dev/null +++ b/Commands/InlineMode/ChoseninlineresultCommand.php @@ -0,0 +1,54 @@ +getChosenInlineResult(); + $query = $inline_query->getQuery(); + + return parent::execute(); + } +} diff --git a/Commands/InlineMode/InlinequeryCommand.php b/Commands/InlineMode/InlinequeryCommand.php new file mode 100644 index 0000000..63f31c1 --- /dev/null +++ b/Commands/InlineMode/InlinequeryCommand.php @@ -0,0 +1,100 @@ +getInlineQuery(); + $query = $inline_query->getQuery(); + + $results = []; + + if ($query !== '') { + // https://core.telegram.org/bots/api#inlinequeryresultarticle + $results[] = new InlineQueryResultArticle([ + 'id' => '001', + 'title' => 'Simple text using InputTextMessageContent', + 'description' => 'this will return Text', + + // Here you can put any other Input...MessageContent you like. + // It will keep the style of an article, but post the specific message type back to the user. + 'input_message_content' => new InputTextMessageContent([ + 'message_text' => 'The query that got you here: ' . $query, + ]), + ]); + + // https://core.telegram.org/bots/api#inlinequeryresultcontact + $results[] = new InlineQueryResultContact([ + 'id' => '002', + 'phone_number' => '12345678', + 'first_name' => 'Best', + 'last_name' => 'Friend', + ]); + + // https://core.telegram.org/bots/api#inlinequeryresultlocation + $results[] = new InlineQueryResultLocation([ + 'id' => '003', + 'title' => 'The center of the world!', + 'latitude' => 40.866667, + 'longitude' => 34.566667, + ]); + + // https://core.telegram.org/bots/api#inlinequeryresultvenue + $results[] = new InlineQueryResultVenue([ + 'id' => '004', + 'title' => 'No-Mans-Land', + 'address' => 'In the middle of Nowhere', + 'latitude' => 33, + 'longitude' => -33, + ]); + } + + return $inline_query->answer($results); + } +} diff --git a/Commands/InlineMode/README.md b/Commands/InlineMode/README.md new file mode 100644 index 0000000..24ac74d --- /dev/null +++ b/Commands/InlineMode/README.md @@ -0,0 +1,7 @@ +# Inline Mode + +The files in this folder demonstrate how to use the [inline mode](https://core.telegram.org/bots/api#inline-mode) of your bot. + +The [`InlinequeryCommand.php`](InlinequeryCommand.php) catches any inline queries and answers with a set of results. + +When a result is selected, this selection is then handled by [`ChoseninlineresultCommand.php`](ChoseninlineresultCommand.php). diff --git a/Commands/Keyboard/CallbackqueryCommand.php b/Commands/Keyboard/CallbackqueryCommand.php new file mode 100644 index 0000000..635a6ac --- /dev/null +++ b/Commands/Keyboard/CallbackqueryCommand.php @@ -0,0 +1,61 @@ +getCallbackQuery(); + $callback_data = $callback_query->getData(); + + return $callback_query->answer([ + 'text' => 'Content of the callback data: ' . $callback_data, + 'show_alert' => (bool) random_int(0, 1), // Randomly show (or not) as an alert. + 'cache_time' => 5, + ]); + } +} diff --git a/Commands/Keyboard/ForcereplyCommand.php b/Commands/Keyboard/ForcereplyCommand.php new file mode 100644 index 0000000..8d0308c --- /dev/null +++ b/Commands/Keyboard/ForcereplyCommand.php @@ -0,0 +1,60 @@ +replyToChat('Write something in reply:', [ + 'reply_markup' => Keyboard::forceReply(), + ]); + } +} diff --git a/Commands/Keyboard/HidekeyboardCommand.php b/Commands/Keyboard/HidekeyboardCommand.php new file mode 100644 index 0000000..3593cae --- /dev/null +++ b/Commands/Keyboard/HidekeyboardCommand.php @@ -0,0 +1,61 @@ +replyToChat('Keyboard Hidden', [ + 'reply_markup' => Keyboard::remove(), + ]); + } +} diff --git a/Commands/Keyboard/InlinekeyboardCommand.php b/Commands/Keyboard/InlinekeyboardCommand.php new file mode 100644 index 0000000..af98ac0 --- /dev/null +++ b/Commands/Keyboard/InlinekeyboardCommand.php @@ -0,0 +1,72 @@ + 'Inline Query (current chat)', 'switch_inline_query_current_chat' => 'inline query...'], + ['text' => 'Inline Query (other chat)', 'switch_inline_query' => 'inline query...'], + ], [ + ['text' => 'Callback', 'callback_data' => 'identifier'], + ['text' => 'Open URL', 'url' => 'https://github.com/php-telegram-bot/example-bot'], + ]); + + return $this->replyToChat('Inline Keyboard', [ + 'reply_markup' => $inline_keyboard, + ]); + } +} diff --git a/Commands/Keyboard/KeyboardCommand.php b/Commands/Keyboard/KeyboardCommand.php new file mode 100644 index 0000000..579fadc --- /dev/null +++ b/Commands/Keyboard/KeyboardCommand.php @@ -0,0 +1,102 @@ + 'A'], + 'B', + ['C', 'D'] + ); + + // Buttons to perform Contact or Location sharing + $keyboards[] = new Keyboard([ + ['text' => 'Send my contact', 'request_contact' => true], + ['text' => 'Send my location', 'request_location' => true], + ]); + + // Shuffle our example keyboards and return a random one + shuffle($keyboards); + $keyboard = end($keyboards) + ->setResizeKeyboard(true) + ->setOneTimeKeyboard(true) + ->setSelective(false); + + return $this->replyToChat('Press a Button!', [ + 'reply_markup' => $keyboard, + ]); + } +} diff --git a/Commands/Keyboard/README.md b/Commands/Keyboard/README.md new file mode 100644 index 0000000..c7a258b --- /dev/null +++ b/Commands/Keyboard/README.md @@ -0,0 +1,13 @@ +# Keyboards + +The files in this folder demonstrate how to create normal and inline keyboards. + +## Normal Keyboard + +Have a look at [`KeyboardCommand.php`](KeyboardCommand.php) for usage examples. + +## Inline Keyboard + +Have a look at [`InlinekeyboardCommand.php`](InlinekeyboardCommand.php) for usage examples. + +To handle inline keyboard buttons, you need to handle all callbacks inside [`CallbackqueryCommand.php`](CallbackqueryCommand.php). diff --git a/Commands/Message/ChannelpostCommand.php b/Commands/Message/ChannelpostCommand.php new file mode 100644 index 0000000..5b04ae4 --- /dev/null +++ b/Commands/Message/ChannelpostCommand.php @@ -0,0 +1,53 @@ +getChannelPost(); + + return parent::execute(); + } +} diff --git a/Commands/Message/EditedchannelpostCommand.php b/Commands/Message/EditedchannelpostCommand.php new file mode 100644 index 0000000..f70d2a3 --- /dev/null +++ b/Commands/Message/EditedchannelpostCommand.php @@ -0,0 +1,53 @@ +getEditedChannelPost(); + + return parent::execute(); + } +} diff --git a/Commands/Message/EditedmessageCommand.php b/Commands/Message/EditedmessageCommand.php new file mode 100644 index 0000000..7d4b565 --- /dev/null +++ b/Commands/Message/EditedmessageCommand.php @@ -0,0 +1,52 @@ +getEditedMessage(); + + return parent::execute(); + } +} diff --git a/Commands/Message/EditmessageCommand.php b/Commands/Message/EditmessageCommand.php new file mode 100644 index 0000000..8b0432c --- /dev/null +++ b/Commands/Message/EditmessageCommand.php @@ -0,0 +1,82 @@ +getMessage(); + $chat_id = $message->getChat()->getId(); + $reply_to_message = $message->getReplyToMessage(); + $text = $message->getText(true); + + if ($reply_to_message && $message_to_edit = $reply_to_message->getMessageId()) { + // Try to edit the selected message. + $result = Request::editMessageText([ + 'chat_id' => $chat_id, + 'message_id' => $message_to_edit, + 'text' => $text ?: 'Edited message', + ]); + + // If successful, delete this editing reply message. + if ($result->isOk()) { + Request::deleteMessage([ + 'chat_id' => $chat_id, + 'message_id' => $message->getMessageId(), + ]); + } + + return $result; + } + + return $this->replyToChat(sprintf("Reply to any bots' message and use /%s to edit it.", $this->getName())); + } +} diff --git a/Commands/Message/GenericmessageCommand.php b/Commands/Message/GenericmessageCommand.php new file mode 100644 index 0000000..0aa6346 --- /dev/null +++ b/Commands/Message/GenericmessageCommand.php @@ -0,0 +1,61 @@ +getMessage(); + + /** + * Handle any kind of message here + */ + + $message_text = $message->getText(true); + + return Request::emptyResponse(); + } +} diff --git a/Commands/Message/README.md b/Commands/Message/README.md new file mode 100644 index 0000000..574ed1b --- /dev/null +++ b/Commands/Message/README.md @@ -0,0 +1,15 @@ +# Message + +You bot can handle all types of messages. + +## Private and Group chats + +Messages in private and group chats get handled by [`GenericmessageCommand.php`](GenericmessageCommand.php). +When a message gets edited, it gets handled by [`EditedmessageCommand.php`](EditedmessageCommand.php) + +(Have a look at [`EditmessageCommand.php`](EditmessageCommand.php) for an example of how to edit messages via your bot) + +## Channels + +For channels, the messages (or posts) get handled by [`ChannelpostCommand.php`](ChannelpostCommand.php). +When a channel post gets edited, it gets handled by [`EditedchannelpostCommand.php`](EditedchannelpostCommand.php) diff --git a/Commands/Other/EchoCommand.php b/Commands/Other/EchoCommand.php new file mode 100644 index 0000000..a3b1d5c --- /dev/null +++ b/Commands/Other/EchoCommand.php @@ -0,0 +1,64 @@ +'; + + /** + * @var string + */ + protected $version = '1.2.0'; + + /** + * Main command execution + * + * @return ServerResponse + * @throws TelegramException + */ + public function execute(): ServerResponse + { + $message = $this->getMessage(); + $text = $message->getText(true); + + if ($text === '') { + return $this->replyToChat('Command usage: ' . $this->getUsage()); + } + + return $this->replyToChat($text); + } +} diff --git a/Commands/Other/HelpCommand.php b/Commands/Other/HelpCommand.php new file mode 100644 index 0000000..7cc99c6 --- /dev/null +++ b/Commands/Other/HelpCommand.php @@ -0,0 +1,133 @@ +'; + + /** + * @var string + */ + protected $version = '1.4.0'; + + /** + * Main command execution + * + * @return ServerResponse + * @throws TelegramException + */ + public function execute(): ServerResponse + { + $message = $this->getMessage(); + $command_str = trim($message->getText(true)); + + // Admin commands shouldn't be shown in group chats + $safe_to_show = $message->getChat()->isPrivateChat(); + + [$all_commands, $user_commands, $admin_commands] = $this->getUserAndAdminCommands(); + + // If no command parameter is passed, show the list. + if ($command_str === '') { + $text = '*Commands List*:' . PHP_EOL; + foreach ($user_commands as $user_command) { + $text .= '/' . $user_command->getName() . ' - ' . $user_command->getDescription() . PHP_EOL; + } + + if ($safe_to_show && count($admin_commands) > 0) { + $text .= PHP_EOL . '*Admin Commands List*:' . PHP_EOL; + foreach ($admin_commands as $admin_command) { + $text .= '/' . $admin_command->getName() . ' - ' . $admin_command->getDescription() . PHP_EOL; + } + } + + $text .= PHP_EOL . 'For exact command help type: /help '; + + return $this->replyToChat($text, ['parse_mode' => 'markdown']); + } + + $command_str = str_replace('/', '', $command_str); + if (isset($all_commands[$command_str]) && ($safe_to_show || !$all_commands[$command_str]->isAdminCommand())) { + $command = $all_commands[$command_str]; + + return $this->replyToChat(sprintf( + 'Command: %s (v%s)' . PHP_EOL . + 'Description: %s' . PHP_EOL . + 'Usage: %s', + $command->getName(), + $command->getVersion(), + $command->getDescription(), + $command->getUsage() + ), ['parse_mode' => 'markdown']); + } + + return $this->replyToChat('No help available: Command `/' . $command_str . '` not found', ['parse_mode' => 'markdown']); + } + + /** + * Get all available User and Admin commands to display in the help list. + * + * @return Command[][] + * @throws TelegramException + */ + protected function getUserAndAdminCommands(): array + { + /** @var Command[] $all_commands */ + $all_commands = $this->telegram->getCommandsList(); + + // Only get enabled Admin and User commands that are allowed to be shown. + $commands = array_filter($all_commands, function ($command): bool { + return !$command->isSystemCommand() && $command->showInHelp() && $command->isEnabled(); + }); + + // Filter out all User commands + $user_commands = array_filter($commands, function ($command): bool { + return $command->isUserCommand(); + }); + + // Filter out all Admin commands + $admin_commands = array_filter($commands, function ($command): bool { + return $command->isAdminCommand(); + }); + + ksort($commands); + ksort($user_commands); + ksort($admin_commands); + + return [$commands, $user_commands, $admin_commands]; + } +} diff --git a/Commands/Other/ImageCommand.php b/Commands/Other/ImageCommand.php new file mode 100644 index 0000000..4968f2b --- /dev/null +++ b/Commands/Other/ImageCommand.php @@ -0,0 +1,110 @@ +getMessage(); + + // Use any extra parameters as the caption text. + $caption = trim($message->getText(true)); + + // Make sure the Upload path has been defined and exists. + $upload_path = $this->telegram->getUploadPath(); + if (!is_dir($upload_path)) { + return $this->replyToChat('Upload path has not been defined or does not exist.'); + } + + // Get a random picture from the Upload path. + $random_image = $this->getRandomImagePath($upload_path); + if ('' === $random_image) { + return $this->replyToChat('No image found!'); + } + + // If no caption is set, use the filename. + if ('' === $caption) { + $caption = basename($random_image); + } + + return Request::sendPhoto([ + 'chat_id' => $message->getFrom()->getId(), + 'caption' => $caption, + 'photo' => $random_image, + ]); + } + + /** + * Return the path to a random image in the passed directory. + * + * @param string $dir + * + * @return string + */ + private function getRandomImagePath($dir): string + { + if (!is_dir($dir)) { + return ''; + } + + // Filter the file list to only return images. + $image_list = array_filter(scandir($dir), function ($file) { + $extension = pathinfo($file, PATHINFO_EXTENSION); + return in_array($extension, ['png', 'jpg', 'jpeg', 'gif']); + }); + if (!empty($image_list)) { + shuffle($image_list); + return $dir . '/' . $image_list[0]; + } + + return ''; + } +} diff --git a/Commands/Other/MarkdownCommand.php b/Commands/Other/MarkdownCommand.php new file mode 100644 index 0000000..b004ada --- /dev/null +++ b/Commands/Other/MarkdownCommand.php @@ -0,0 +1,68 @@ +replyToChat(' +*bold* _italic_ `inline fixed width code` + +``` +preformatted code block +code block +``` + +[Best Telegram bot api!!](https://github.com/php-telegram-bot/core)', [ + 'parse_mode' => 'markdown', + ]); + } +} diff --git a/Commands/Other/README.md b/Commands/Other/README.md new file mode 100644 index 0000000..cafac7b --- /dev/null +++ b/Commands/Other/README.md @@ -0,0 +1,5 @@ +# Other Commands + +In this folder you can find a few example commands, that demonstrate how to use different features of the library. + +Best just take a look at them and see what they do! diff --git a/Commands/Other/SlapCommand.php b/Commands/Other/SlapCommand.php new file mode 100644 index 0000000..be9b37a --- /dev/null +++ b/Commands/Other/SlapCommand.php @@ -0,0 +1,67 @@ +'; + + /** + * @var string + */ + protected $version = '1.2.0'; + + /** + * Main command execution + * + * @return ServerResponse + * @throws TelegramException + */ + public function execute(): ServerResponse + { + $message = $this->getMessage(); + $text = $message->getText(true); + + $sender = '@' . $message->getFrom()->getUsername(); + + // Username validation (simply checking for `@something` in the text) + if (0 === preg_match('/@[\w_]{5,}/', $text)) { + return $this->replyToChat('Sorry, no one to slap around...'); + } + + return $this->replyToChat($sender . ' slaps ' . $text . ' around a bit with a large trout'); + } +} diff --git a/Commands/Other/UploadCommand.php b/Commands/Other/UploadCommand.php new file mode 100644 index 0000000..1eb1d4d --- /dev/null +++ b/Commands/Other/UploadCommand.php @@ -0,0 +1,115 @@ +getMessage(); + $chat = $message->getChat(); + $chat_id = $chat->getId(); + $user_id = $message->getFrom()->getId(); + + // Make sure the Download path has been defined and exists + $download_path = $this->telegram->getDownloadPath(); + if (!is_dir($download_path)) { + return $this->replyToChat('Download path has not been defined or does not exist.'); + } + + // Initialise the data array for the response + $data = ['chat_id' => $chat_id]; + + if ($chat->isGroupChat() || $chat->isSuperGroup()) { + // Reply to message id is applied by default + $data['reply_to_message_id'] = $message->getMessageId(); + // Force reply is applied by default to work with privacy on + $data['reply_markup'] = Keyboard::forceReply(['selective' => true]); + } + + // Start conversation + $conversation = new Conversation($user_id, $chat_id, $this->getName()); + $message_type = $message->getType(); + + if (in_array($message_type, ['audio', 'document', 'photo', 'video', 'voice'], true)) { + $doc = $message->{'get' . ucfirst($message_type)}(); + + // For photos, get the best quality! + ($message_type === 'photo') && $doc = end($doc); + + $file_id = $doc->getFileId(); + $file = Request::getFile(['file_id' => $file_id]); + if ($file->isOk() && Request::downloadFile($file->getResult())) { + $data['text'] = $message_type . ' file is located at: ' . $download_path . '/' . $file->getResult()->getFilePath(); + } else { + $data['text'] = 'Failed to download.'; + } + + $conversation->notes['file_id'] = $file_id; + $conversation->update(); + $conversation->stop(); + } else { + $data['text'] = 'Please upload the file now'; + } + + return Request::sendMessage($data); + } +} diff --git a/Commands/Other/WhoamiCommand.php b/Commands/Other/WhoamiCommand.php new file mode 100644 index 0000000..ba75f66 --- /dev/null +++ b/Commands/Other/WhoamiCommand.php @@ -0,0 +1,124 @@ +getMessage(); + + $from = $message->getFrom(); + $user_id = $from->getId(); + $chat_id = $message->getChat()->getId(); + $message_id = $message->getMessageId(); + + $data = [ + 'chat_id' => $chat_id, + 'reply_to_message_id' => $message_id, + ]; + + // Send chat action "typing..." + Request::sendChatAction([ + 'chat_id' => $chat_id, + 'action' => ChatAction::TYPING, + ]); + + $caption = sprintf( + 'Your Id: %d' . PHP_EOL . + 'Name: %s %s' . PHP_EOL . + 'Username: %s', + $user_id, + $from->getFirstName(), + $from->getLastName(), + $from->getUsername() + ); + + // Fetch the most recent user profile photo + $limit = 1; + $offset = null; + + $user_profile_photos_response = Request::getUserProfilePhotos([ + 'user_id' => $user_id, + 'limit' => $limit, + 'offset' => $offset, + ]); + + if ($user_profile_photos_response->isOk()) { + /** @var UserProfilePhotos $user_profile_photos */ + $user_profile_photos = $user_profile_photos_response->getResult(); + + if ($user_profile_photos->getTotalCount() > 0) { + $photos = $user_profile_photos->getPhotos(); + + // Get the best quality of the profile photo + $photo = end($photos[0]); + $file_id = $photo->getFileId(); + + $data['photo'] = $file_id; + $data['caption'] = $caption; + + return Request::sendPhoto($data); + } + } + + // No Photo just send text + $data['text'] = $caption; + + return Request::sendMessage($data); + } +} diff --git a/Commands/Payments/GenericmessageCommand.php b/Commands/Payments/GenericmessageCommand.php new file mode 100644 index 0000000..06be16e --- /dev/null +++ b/Commands/Payments/GenericmessageCommand.php @@ -0,0 +1,60 @@ +getMessage(); + $user_id = $message->getFrom()->getId(); + + // Handle successful payment + if ($payment = $message->getSuccessfulPayment()) { + return PaymentCommand::handleSuccessfulPayment($payment, $user_id); + } + + return Request::emptyResponse(); + } +} diff --git a/Commands/Payments/PaymentCommand.php b/Commands/Payments/PaymentCommand.php new file mode 100644 index 0000000..75c801a --- /dev/null +++ b/Commands/Payments/PaymentCommand.php @@ -0,0 +1,125 @@ + ['payment_provider_token' => 'your_payment_provider_token_here'] + * + * You will also need to copy the `Precheckoutquerycommand.php` file. + */ + +namespace Longman\TelegramBot\Commands\UserCommands; + +use Longman\TelegramBot\Commands\UserCommand; +use Longman\TelegramBot\Entities\Payments\LabeledPrice; +use Longman\TelegramBot\Entities\Payments\SuccessfulPayment; +use Longman\TelegramBot\Entities\ServerResponse; +use Longman\TelegramBot\Exception\TelegramException; +use Longman\TelegramBot\Request; + +class PaymentCommand extends UserCommand +{ + /** + * @var string + */ + protected $name = 'payment'; + + /** + * @var string + */ + protected $description = 'Create an invoice for the user using Telegram Payments'; + + /** + * @var string + */ + protected $usage = '/payment'; + + /** + * @var string + */ + protected $version = '0.1.0'; + + /** + * Main command execution + * + * @return ServerResponse + */ + public function execute(): ServerResponse + { + // Who to send this invoice to. (Use the current user.) + $chat_id = $this->getMessage()->getFrom()->getId(); + + // The currency of this invoice. + // Supported currencies: https://core.telegram.org/bots/payments#supported-currencies + $currency = 'EUR'; + + // List all items that will be shown on your invoice. + // Amounts are in cents. So 1 Euro would be put as 100. + $prices = [ + new LabeledPrice(['label' => 'Small thing', 'amount' => 100]), // 1€ + new LabeledPrice(['label' => 'Bigger thing', 'amount' => 2000]), // 20€ + new LabeledPrice(['label' => 'Huge thing', 'amount' => 50000]), // 500€ + ]; + + // Request a shipping address if necessary. + $need_shipping_address = false; + + // If you have flexible pricing, depending on the shipping method chosen, set this to true. + // You will also need to copy and adapt the `ShippingqueryCommand.php` file. + $is_flexible = false; + + // Send the actual invoice! + // Adjust any parameters to your needs. + return Request::sendInvoice([ + 'chat_id' => $chat_id, + 'title' => 'Payment with PHP Telegram Bot', + 'description' => 'A simple invoice to test Telegram Payments', + 'payload' => 'payment_demo', + 'start_parameter' => 'payment_demo', + 'provider_token' => $this->getConfig('payment_provider_token'), + 'currency' => $currency, + 'prices' => $prices, + 'need_shipping_address' => $need_shipping_address, + 'is_flexible' => $is_flexible, + ]); + } + + /** + * Send "Thank you" message to user who paid + * + * You will need to add some code to your custom `GenericmessageCommand::execute()` method. + * Check the `GenericmessageCommand.php` file included in this folder. + * + * @param SuccessfulPayment $payment + * @param int $user_id + * + * @return ServerResponse + * @throws TelegramException + */ + public static function handleSuccessfulPayment($payment, $user_id): ServerResponse + { + // Send a message to the user after they have completed the payment. + return Request::sendMessage([ + 'chat_id' => $user_id, + 'text' => 'Thank you for your order!', + ]); + } +} diff --git a/Commands/Payments/PrecheckoutqueryCommand.php b/Commands/Payments/PrecheckoutqueryCommand.php new file mode 100644 index 0000000..5e874e2 --- /dev/null +++ b/Commands/Payments/PrecheckoutqueryCommand.php @@ -0,0 +1,57 @@ +getPreCheckoutQuery()->answer(true); + + // If we do make certain checks, you can define the error message displayed to the user like this. + // return $this->getPreCheckoutQuery()->answer(false, [ + // 'error_message' => 'Registration (or whatever) required...', + // ]); + } +} diff --git a/Commands/Payments/README.md b/Commands/Payments/README.md new file mode 100644 index 0000000..2c936ec --- /dev/null +++ b/Commands/Payments/README.md @@ -0,0 +1,39 @@ +# Telegram Payments + +With the files in this folder, you can create and send invoices to your users, require their shipping details, define custom / flexible shipping methods and send a confirmation message after payment. + +## Enable payments for your bot + +Read all about Telegram Payments and follow the setup guide here: +https://core.telegram.org/bots/payments + +## Configuring the `/payment` command + +First of all, as a bare minimum, you need to copy the [`PaymentCommand.php`](PaymentCommand.php) and [`PrecheckoutqueryCommand.php`](PrecheckoutqueryCommand.php) files in this folder to your custom commands folder. + +If you want to allow flexible shipping options, you will also need to copy [`ShippingqueryCommand.php`](ShippingqueryCommand.php) to your custom commands folder. + +Should you want to send a message on a successful payment, you will need to copy the [`GenericmessageCommand.php`](GenericmessageCommand.php) file as well. +If you already have a `GenericmessageCommand.php` file, you'll need to copy the code from the `execute` method into your file. + +Next, you will need to add the Payment Provider Token (that you received in the previous step when linking your bot), to your `hook.php` or `manager.php` config. + +For `hook.php`: +```php +$telegram->setCommandConfig('payment', ['payment_provider_token' => 'your_payment_provider_token_here']); +``` + +For `manager.php` or using a general `config.php`, in the config array add: +```php +... +'commands' => [ + 'configs' => [ + 'payment' => ['payment_provider_token' => 'your_payment_provider_token_here'], + ], +], +... +``` + +Now, when sending the `/payment` command to your bot, you should receive an invoice. + +Have fun with Telegram Payments! diff --git a/Commands/Payments/ShippingqueryCommand.php b/Commands/Payments/ShippingqueryCommand.php new file mode 100644 index 0000000..c47fac2 --- /dev/null +++ b/Commands/Payments/ShippingqueryCommand.php @@ -0,0 +1,80 @@ +getShippingQuery()->answer(true, [ + 'shipping_options' => [ + new ShippingOption([ + 'id' => 'basic', + 'title' => 'Basic Shipping', + 'prices' => [ + new LabeledPrice(['label' => 'Basic Shipping', 'amount' => 800]), + ], + ]), + new ShippingOption([ + 'id' => 'premium', + 'title' => 'Premium Shipping', + 'prices' => [ + new LabeledPrice(['label' => 'Premium Shipping', 'amount' => 1500]), + new LabeledPrice(['label' => 'Extra speedy', 'amount' => 300]), + ], + ]), + ], + ]); + + // If we do make certain checks, you can define the error message displayed to the user like this. + // return $this->getShippingQuery()->answer(false, [ + // 'error_message' => 'We do not ship to your location :-(', + // ]); + } +} diff --git a/Commands/README.MD b/Commands/README.MD new file mode 100644 index 0000000..2565b7a --- /dev/null +++ b/Commands/README.MD @@ -0,0 +1,25 @@ +This Folder contains some examples how to use the Telegram Bot API with the Telegram PHP Bot library + + +**:exclamation: DO NOT USE THE COMMANDS FOLDER** + +As the title says, do not include the Commands folder into your config to use it as it is. + +Why? This folder contains some files multiple times which is an issue for the bot software, since every file can only exist once! + +For example: GenericmessageCommand.php +This file exist in the following folder: +- Conversation +- Group +- Message +- Payments + +Having any Command file more than once will cause conflicts between those file, causing only one file to be executed and the others ignored. + +Please copy each file and/or folder that you need into the CustomCommans folder. + +If you want to create your own Command file, please do it so in the CustomCommands folder as well. + +If you need for example the GenericmessageCommand.php from the Conversation and Group folder, you will need to compara and merge those files. + + diff --git a/Commands/ServiceMessages/GenericmessageCommand.php b/Commands/ServiceMessages/GenericmessageCommand.php new file mode 100644 index 0000000..dbbf2cf --- /dev/null +++ b/Commands/ServiceMessages/GenericmessageCommand.php @@ -0,0 +1,74 @@ +getMessage(); + + /** + * Catch and handle any service messages here. + */ + + // The chat photo was deleted + $delete_chat_photo = $message->getDeleteChatPhoto(); + + // The group has been created + $group_chat_created = $message->getGroupChatCreated(); + + // The supergroup has been created + $supergroup_chat_created = $message->getSupergroupChatCreated(); + + // The channel has been created + $channel_chat_created = $message->getChannelChatCreated(); + + // Information about the payment + $successful_payment = $message->getSuccessfulPayment(); + + return Request::emptyResponse(); + } +} diff --git a/Commands/StartCommand.php b/Commands/StartCommand.php new file mode 100644 index 0000000..379a0d7 --- /dev/null +++ b/Commands/StartCommand.php @@ -0,0 +1,72 @@ +getMessage()->getText(true); + + return $this->replyToChat( + 'Hi there!' . PHP_EOL . + 'Type /help to see all commands!' + ); + } +} diff --git a/CustomCommands/README.md b/CustomCommands/README.md new file mode 100644 index 0000000..68f6bcc --- /dev/null +++ b/CustomCommands/README.md @@ -0,0 +1,13 @@ +# CustomCommands +Uncomment line 47 in your config.php from +`// __DIR__ . '/CustomCommands',` +to +`__DIR__ . '/CustomCommands',` +to enable this Folder. +You then can copy example Commands from the Command folder. + +**:exclamation: Important!** + +DO NOT COPY THE ENTIRE COMMAND FOLDER CONTENT!! + +Some Command Files are Duplicated and may interfere with each other. diff --git a/README.md b/README.md new file mode 100644 index 0000000..26f602a --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# telegram-bot + +### 创建 Bot + +发送 `/newbot`到`@BotFather`,根据提示完成创建机器人。 + +### 开发环境 + +- PHP >= 7.3 +- composer +- 境外服务器 (https) +- MySQL(非必要) +- 依赖:longman/telegram-bot diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..327f16e --- /dev/null +++ b/composer.json @@ -0,0 +1,31 @@ +{ + "name": "telegram-bot/example-bot", + "type": "project", + "description": "PHP Telegram Bot Example", + "keywords": ["telegram", "bot", "example"], + "license": "MIT", + "homepage": "https://github.com/php-telegram-bot/example-bot", + "support": { + "issues": "https://github.com/php-telegram-bot/example-bot/issues", + "source": "https://github.com/php-telegram-bot/example-bot" + }, + "authors": [ + { + "name": "PHP Telegram Bot Team", + "homepage": "https://github.com/php-telegram-bot/example-bot/graphs/contributors", + "role": "Developer" + } + ], + "require": { + "longman/telegram-bot": "*" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "squizlabs/php_codesniffer": "^3.5" + }, + "scripts": { + "check-code": [ + "\"vendor/bin/phpcs\"" + ] + } +} diff --git a/config.php b/config.php new file mode 100644 index 0000000..008f2fb --- /dev/null +++ b/config.php @@ -0,0 +1,92 @@ + 'your:bot_api_key', + 'bot_username' => 'username_bot', // Without "@" + + // [Manager Only] Secret key required to access the webhook + 'secret' => 'super_secret', + + // When using the getUpdates method, this can be commented out + 'webhook' => [ + 'url' => 'https://your-domain/path/to/hook-or-manager.php', + // Use self-signed certificate + // 'certificate' => __DIR__ . '/path/to/your/certificate.crt', + // Limit maximum number of connections + // 'max_connections' => 5, + ], + + // All command related configs go here + 'commands' => [ + // Define all paths for your custom commands + // DO NOT PUT THE COMMAND FOLDER THERE. IT WILL NOT WORK. + // Copy each needed Commandfile into the CustomCommand folder and uncommend the Line 49 below + 'paths' => [ + // __DIR__ . '/CustomCommands', + ], + // Here you can set any command-specific parameters + 'configs' => [ + // - Google geocode/timezone API key for /date command (see DateCommand.php) + // 'date' => ['google_api_key' => 'your_google_api_key_here'], + // - OpenWeatherMap.org API key for /weather command (see WeatherCommand.php) + // 'weather' => ['owm_api_key' => 'your_owm_api_key_here'], + // - Payment Provider Token for /payment command (see Payments/PaymentCommand.php) + // 'payment' => ['payment_provider_token' => 'your_payment_provider_token_here'], + ], + ], + + // Define all IDs of admin users + 'admins' => [ + // 123, + ], + + // Enter your MySQL database credentials + // 'mysql' => [ + // 'host' => '127.0.0.1', + // 'user' => 'root', + // 'password' => 'root', + // 'database' => 'telegram_bot', + // ], + + // Logging (Debug, Error and Raw Updates) + // 'logging' => [ + // 'debug' => __DIR__ . '/php-telegram-bot-debug.log', + // 'error' => __DIR__ . '/php-telegram-bot-error.log', + // 'update' => __DIR__ . '/php-telegram-bot-update.log', + // ], + + // Set custom Upload and Download paths + 'paths' => [ + 'download' => __DIR__ . '/Download', + 'upload' => __DIR__ . '/Upload', + ], + + // Requests Limiter (tries to prevent reaching Telegram API limits) + 'limiter' => [ + 'enabled' => true, + ], +]; diff --git a/cron.php b/cron.php new file mode 100644 index 0000000..1f7b517 --- /dev/null +++ b/cron.php @@ -0,0 +1,50 @@ +runCommands($commands); + +} catch (Longman\TelegramBot\Exception\TelegramException $e) { + // Log telegram errors + Longman\TelegramBot\TelegramLog::error($e); + + // Uncomment this to output any errors (ONLY FOR DEVELOPMENT!) + // echo $e; +} catch (Longman\TelegramBot\Exception\TelegramLogException $e) { + // Uncomment this to output log initialisation errors (ONLY FOR DEVELOPMENT!) + // echo $e; +} diff --git a/getUpdatesCLI.php b/getUpdatesCLI.php new file mode 100644 index 0000000..5ca7bcc --- /dev/null +++ b/getUpdatesCLI.php @@ -0,0 +1,53 @@ +#!/usr/bin/env php +handleGetUpdates(); + + if ($server_response->isOk()) { + $update_count = count($server_response->getResult()); + echo date('Y-m-d H:i:s') . ' - Processed ' . $update_count . ' updates'; + } else { + echo date('Y-m-d H:i:s') . ' - Failed to fetch updates' . PHP_EOL; + echo $server_response->printError(); + } + +} catch (Longman\TelegramBot\Exception\TelegramException $e) { + // Log telegram errors + Longman\TelegramBot\TelegramLog::error($e); + + // Uncomment this to output any errors (ONLY FOR DEVELOPMENT!) + // echo $e; +} catch (Longman\TelegramBot\Exception\TelegramLogException $e) { + // Uncomment this to output log initialisation errors (ONLY FOR DEVELOPMENT!) + // echo $e; +} diff --git a/hook.php b/hook.php new file mode 100644 index 0000000..c8e3ddb --- /dev/null +++ b/hook.php @@ -0,0 +1,78 @@ +enableAdmins($config['admins']); + + // Add commands paths containing your custom commands + $telegram->addCommandsPaths($config['commands']['paths']); + + // Enable MySQL if required + // $telegram->enableMySql($config['mysql']); + + // Logging (Error, Debug and Raw Updates) + // https://github.com/php-telegram-bot/core/blob/master/doc/01-utils.md#logging + // + // (this example requires Monolog: composer require monolog/monolog) + // Longman\TelegramBot\TelegramLog::initialize( + // new Monolog\Logger('telegram_bot', [ + // (new Monolog\Handler\StreamHandler($config['logging']['debug'], Monolog\Logger::DEBUG))->setFormatter(new Monolog\Formatter\LineFormatter(null, null, true)), + // (new Monolog\Handler\StreamHandler($config['logging']['error'], Monolog\Logger::ERROR))->setFormatter(new Monolog\Formatter\LineFormatter(null, null, true)), + // ]), + // new Monolog\Logger('telegram_bot_updates', [ + // (new Monolog\Handler\StreamHandler($config['logging']['update'], Monolog\Logger::INFO))->setFormatter(new Monolog\Formatter\LineFormatter('%message%' . PHP_EOL)), + // ]) + // ); + + // Set custom Download and Upload paths + // $telegram->setDownloadPath($config['paths']['download']); + // $telegram->setUploadPath($config['paths']['upload']); + + // Load all command-specific configurations + // foreach ($config['commands']['configs'] as $command_name => $command_config) { + // $telegram->setCommandConfig($command_name, $command_config); + // } + + // Requests Limiter (tries to prevent reaching Telegram API limits) + $telegram->enableLimiter($config['limiter']); + + // Handle telegram webhook request + $telegram->handle(); + +} catch (Longman\TelegramBot\Exception\TelegramException $e) { + // Log telegram errors + Longman\TelegramBot\TelegramLog::error($e); + + // Uncomment this to output any errors (ONLY FOR DEVELOPMENT!) + // echo $e; +} catch (Longman\TelegramBot\Exception\TelegramLogException $e) { + // Uncomment this to output log initialisation errors (ONLY FOR DEVELOPMENT!) + // echo $e; +} diff --git a/manager.php b/manager.php new file mode 100644 index 0000000..06e7ed5 --- /dev/null +++ b/manager.php @@ -0,0 +1,42 @@ +run(); + +} catch (Longman\TelegramBot\Exception\TelegramException $e) { + // Log telegram errors + Longman\TelegramBot\TelegramLog::error($e); + + // Uncomment this to output any errors (ONLY FOR DEVELOPMENT!) + // echo $e; +} catch (Longman\TelegramBot\Exception\TelegramLogException $e) { + // Uncomment this to output log initialisation errors (ONLY FOR DEVELOPMENT!) + // echo $e; +} diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..434bfc6 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,26 @@ + + + PHP Code Sniffer + + + + + + + + + . + */vendor/* + + + + + + + + + + + + + diff --git a/set.php b/set.php new file mode 100644 index 0000000..6668836 --- /dev/null +++ b/set.php @@ -0,0 +1,42 @@ + 'https://your-domain/path/to/hook.php' + */ + + // Set the webhook + $result = $telegram->setWebhook($config['webhook']['url']); + + // To use a self-signed certificate, use this line instead + // $result = $telegram->setWebhook($config['webhook']['url'], ['certificate' => $config['webhook']['certificate']]); + + echo $result->getDescription(); +} catch (Longman\TelegramBot\Exception\TelegramException $e) { + echo $e->getMessage(); +} diff --git a/unset.php b/unset.php new file mode 100644 index 0000000..02d52e9 --- /dev/null +++ b/unset.php @@ -0,0 +1,34 @@ +deleteWebhook(); + + echo $result->getDescription(); +} catch (Longman\TelegramBot\Exception\TelegramException $e) { + echo $e->getMessage(); +}