commit 197a7949bd39852e2346c1fb587f889000f38900 Author: LIERLIER <1113093541@qq.com> Date: Sat Sep 16 10:23:58 2023 +0800 base 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(); +}