PageRenderTime 26ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/app/BotApi.php

https://gitlab.com/rasadeghnasab/advisor-telegrambot
PHP | 482 lines | 324 code | 92 blank | 66 comment | 28 complexity | 7b74f6d62059945c989c2e06173264ce MD5 | raw file
  1. <?php
  2. namespace App;
  3. use App\Events\TestComplete;
  4. use App\Http\Requests;
  5. use Illuminate\Support\Facades\Event;
  6. use Illuminate\Support\Facades\Log;
  7. use League\Flysystem\Exception;
  8. use Telegram\Bot\Api;
  9. class BotApi
  10. {
  11. public $message = null;
  12. public $keyboard = null;
  13. public $message_id;
  14. private $action;
  15. private $message_configs;
  16. private $keyboard_configs;
  17. private $keyboard_markup;
  18. private $response;
  19. private $save_current_state;
  20. // telegram values
  21. private $telegram;
  22. private $updates;
  23. private $customer_id;
  24. private $message_text;
  25. public function __construct($action = null, $configs = [])
  26. {
  27. $this->action = $action;
  28. $this->telegram = new Api();
  29. $this->updates = $this->telegram->getWebhookUpdates();
  30. $this->customer_id = $this->updates->getMessage()->getChat()->getId();
  31. $this->message_text = $this->updates->getMessage()->getText();
  32. $this->message_configs = isset($configs['message_configs']) ? array_merge($this->messageConfigs(), $configs['message_configs']) : $this->messageConfigs();
  33. $this->keyboard_configs = isset($configs['keyboard_configs']) ? array_merge($this->keyboardConfigs(), $configs['keyboard_configs']) : $this->keyboardConfigs();
  34. }
  35. public static function closeUserActivities($customer_id)
  36. {
  37. // close personal_info
  38. Result::where('customer_id', $customer_id)->update(['closed' => 1]);
  39. // close results
  40. PersonalInfo::where('customer_id', $customer_id)->update(['closed' => 1]);
  41. // close customer_end_results
  42. CustomerEndResult::where('customer_id', $customer_id)
  43. ->update(['closed' => 1]);
  44. // close answered_questions
  45. AnsweredQuestion::where('customer_id', $customer_id)
  46. ->update(['closed' => 1]);
  47. }
  48. public static function removeUserOpenActivities($customer_id)
  49. {
  50. // close personal_info
  51. Result::where('customer_id', $customer_id)->where('closed', 0)->delete();
  52. // close results
  53. PersonalInfo::where('customer_id', $customer_id)
  54. ->where('closed', 0)
  55. ->delete();
  56. // close customer_end_results
  57. CustomerEndResult::where('customer_id', $customer_id)
  58. ->where('closed', 0)
  59. ->delete();
  60. // close answered_questions
  61. AnsweredQuestion::where('customer_id', $customer_id)
  62. ->where('closed', 0)
  63. ->delete();
  64. }
  65. public function sendMessage($currentState)
  66. {
  67. if ($this->action == 'show_end_result') {
  68. $this->showEndResult($currentState);
  69. }
  70. elseif ($this->action == 'offer_product') {
  71. $this->offerProduct($currentState);
  72. }
  73. elseif($this->action == 'calc_points_result') {
  74. $this->showPointResult($currentState);
  75. }
  76. elseif ($this->action == 'show_all_questions') {
  77. $this->showAllQuestions($currentState);
  78. }
  79. else {
  80. $this->currentStateSendMessage($currentState);
  81. }
  82. return $this->result();
  83. }
  84. public function currentStateSendMessage($current_state)
  85. {
  86. $question = Question::findOrNew($current_state->current_sub_state);
  87. $this->message = $question->body;
  88. $this->getKeyboard($current_state, $question);
  89. isset($this->keyboard_configs['global_buttons']) && $current_state->last_state != null ? $this->keyboardGlobalButtons() : $this->keyboard;
  90. if ($current_state->body == 'قبل از نمایش نتیجه') {
  91. $this->keyboardGlobalButtons(['']);
  92. }
  93. $this->keyboardMarkup();
  94. $response = $this->botSendMessage();
  95. $this->message_id = $response->getMessageId();
  96. $this->response = $response;
  97. $this->save_current_state = true;
  98. }
  99. private function showEndResult($current_state)
  100. {
  101. // be careful about FIRST method. it might get the first one instead of the selected one.
  102. $personal_info = PersonalInfo::customerPersonalInfo($current_state->customer_id)
  103. ->first();
  104. $tags = [];
  105. // pg = personal gender, pa = personal age
  106. // $tags[] = 'pg' . $personal_info->getGender();
  107. // Log::info("BOTAPI:122, personal info age " . $personal_info->age);
  108. $tags[] = 'pa' . Answer::where('body', $personal_info->age)->first()->id;
  109. $results = Result::getResults($current_state->customer_id)->get();
  110. foreach ($results as $result) {
  111. $tags[] = 'st' . $result->result;
  112. }
  113. $diagnosis = TagEndResult::diagnosis($tags)->first();
  114. // Log::debug($diagnosis);
  115. $this->message = 'نوع پوستی با مشخصات شما موجود نمی باشد.';
  116. if ($diagnosis) {
  117. $diagnosis = $diagnosis->endResult;
  118. // save end result to customer_end_results table
  119. CustomerEndResult::create([
  120. 'end_result_id' => $diagnosis->id,
  121. 'customer_id' => $current_state->customer_id,
  122. ]);
  123. // TODO::$diagnostic [shortcode]; DONE
  124. // TODO:: must create dynamic [shortcodes]
  125. $this->message = $this->replaceUserInfo($diagnosis->body);
  126. }
  127. $question = Question::findOrNew($current_state->current_sub_state);
  128. // Initialize keyboard
  129. $this->getKeyboard($current_state, $question);
  130. $this->keyboardGlobalButtons(['']);
  131. $this->keyboardMarkup();
  132. $response = $this->botSendMessage();
  133. $this->message_id = $response->getMessageId();
  134. $this->response = $response;
  135. $this->save_current_state = true;
  136. // Fire event to end test
  137. Event::fire(new TestComplete($current_state));
  138. }
  139. private function offerProduct($current_state)
  140. {
  141. // close the answeredQuestions.
  142. // show the result to the user base on tags.
  143. // TODO:: add closed field to customer_end_result table.
  144. $customer_end_result = CustomerEndResult::where('customer_id', $current_state->customer_id)
  145. ->where('closed', 0)
  146. ->first();
  147. $products = RelatedProduct::where('end_result_id', $customer_end_result->end_result_id)
  148. ->pluck('description');
  149. $question = Question::findOrNew($current_state->current_sub_state);
  150. // Initialize keyboard
  151. $this->getKeyboard($current_state, $question);
  152. $this->keyboardGlobalButtons(['global_buttons' => '']);
  153. $this->keyboardMarkup();
  154. foreach ($products as $product) {
  155. $this->message = $product;
  156. $response = $this->botSendMessage();
  157. }
  158. $this->message_id = $response->getMessageId();
  159. $this->response = $response;
  160. $this->save_current_state = true;
  161. // Fire event to end test
  162. Event::fire(new TestComplete($current_state));
  163. }
  164. private function showAllQuestions($current_state)
  165. {
  166. // fetch all questions for this state
  167. // put pointer to the latest question
  168. //
  169. // close the answeredQuestions.
  170. // show the result to the user base on tags.
  171. // TODO:: add closed field to customer_end_result table.
  172. $customer_end_result = CustomerEndResult::where('customer_id', $current_state->customer_id)
  173. ->where('closed', 0)
  174. ->first();
  175. $products = RelatedProduct::where('end_result_id', $customer_end_result->end_result_id)
  176. ->pluck('description');
  177. $question = Question::findOrNew($current_state->current_sub_state);
  178. // Initialize keyboard
  179. $this->getKeyboard($current_state, $question);
  180. $this->keyboardGlobalButtons(['global_buttons' => '']);
  181. $this->keyboardMarkup();
  182. foreach ($products as $product) {
  183. $this->message = $product;
  184. $response = $this->botSendMessage();
  185. }
  186. $this->message_id = $response->getMessageId();
  187. $this->response = $response;
  188. $this->save_current_state = true;
  189. // Fire event to end test
  190. Event::fire(new TestComplete($current_state));
  191. }
  192. /**
  193. * @param $currentState
  194. */
  195. private function showPointResult($currentState)
  196. {
  197. $question = Question::findOrNew($currentState->current_sub_state);
  198. // Initialize keyboard
  199. $this->getKeyboard($currentState, $question);
  200. // $this->keyboardGlobalButtons(['global_buttons' => '']);
  201. $this->keyboardMarkup();
  202. $result = Result::where('customer_id', $currentState->customer_id)
  203. ->where('closed', 0)->pluck('result')->first();
  204. $diagnosis = collect(config('skin-type-points'));
  205. $this->message = $diagnosis->first(function($key, $item) use ($result) {
  206. $range = explode('-', $key);
  207. return ($range[0] <= $result) && ($result <= $range[1]);
  208. });
  209. $response = $this->botSendMessage();
  210. $this->message_id = $response->getMessageId();
  211. $this->response = $response;
  212. $this->save_current_state = true;
  213. Event::fire(new TestComplete($currentState));
  214. }
  215. private function keyboardMarkup()
  216. {
  217. $this->keyboard_markup = $this->telegram->replyKeyboardMarkup(array_merge($this->keyboard_configs, ['keyboard' => $this->keyboard]));
  218. }
  219. private function keyboardGlobalButtons($configs = null)
  220. {
  221. $configs = $configs != null ? $configs : $this->keyboard_configs['global_buttons'];
  222. $this->keyboard = array_merge($this->keyboard, [$configs]);
  223. }
  224. private function keyboardConfigs()
  225. {
  226. return [
  227. 'keyboard' => [],
  228. 'resize_keyboard' => true,
  229. 'one_time_keyboard' => true,
  230. 'global_buttons' => ['بازگشت'],
  231. ];
  232. }
  233. private function getKeyboard($current_state, $question)
  234. {
  235. $personal_info = PersonalInfo::customerPersonalInfo($current_state->customer_id)
  236. ->first();
  237. // Initialize keyboard
  238. $answers = $question->answers()->orderBy('weight')->get()->keyBy('id');
  239. //TODO:: This filter part must encapsulated
  240. $answer_ids = $answers->pluck('id');
  241. $features = Feature::answerFilter($answer_ids)->get();
  242. if (isset($personal_info->age) && $answer = Answer::where('body', $personal_info->age)->first()) {
  243. $personal_info->age = $answer->id;
  244. }
  245. foreach ($features as $feature) {
  246. if (isset($personal_info->{$feature->type}) && !in_array($personal_info->{$feature->type}, explode(', ', $feature->value))) {
  247. $answers->forget($feature->entity_id);
  248. }
  249. }
  250. $this->keyboard = $this->buttonsAutoSizing($question, $answers);
  251. /*
  252. $this->keyboard = array_values($this->keyboard->map(function ($item, $key) {
  253. return is_int($item) ? (string)$item : array_values($item->toArray());
  254. })->toArray());
  255. */
  256. // Log::warning($this->keyboard);
  257. }
  258. private function messageConfigs()
  259. {
  260. return [
  261. 'chat_id' => $this->updates->getMessage()->getChat()->getId(),
  262. 'parse_mode' => 'html',
  263. // 'disable_web_page_preview' => TRUE,
  264. ];
  265. }
  266. private function botSendMessage()
  267. {
  268. $messages = [];
  269. if(is_array($this->message)) {
  270. foreach ($this->message as $message) {
  271. $messages[] = [
  272. 'chat_id' => $this->message_configs['chat_id'],
  273. 'text' => str_replace('&nbsp;', '', strip_tags($message, '<b><i><a><code><pre>')),
  274. 'parse_mode' => 'HTML',
  275. ];
  276. }
  277. }
  278. else {
  279. $messages[] = [
  280. 'chat_id' => $this->message_configs['chat_id'],
  281. 'text' => str_replace('&nbsp;', '', strip_tags($this->message, '<b><i><a><code><pre>')),
  282. 'parse_mode' => 'HTML',
  283. ];
  284. }
  285. // Log::info($this->keyboard_markup);
  286. foreach ($messages as $message) {
  287. if ($this->keyboard_markup) {
  288. $message = array_merge($message, ['reply_markup' => $this->keyboard_markup]);
  289. }
  290. try {
  291. $result = $this->telegram->sendMessage($message);
  292. } catch (\Exception $e) {
  293. $message['text'] = 'اطلاعاتی برای این قسمت موجود نمی باشد، لطفا دوباره تلاش نمایید.';
  294. if (env('BOT_DEBUG', false)) {
  295. $message['text'] .= "<pre>" . htmlentities($e->getMessage()) . "</pre>";
  296. $message['chat_id'] .= 126506089;
  297. }
  298. $result = $this->telegram->sendMessage($message);
  299. }
  300. }
  301. return $result;
  302. }
  303. private function result()
  304. {
  305. return (object)[
  306. 'save' => $this->save_current_state,
  307. 'response' => $this->response,
  308. ];
  309. }
  310. private function replaceUserInfo($text)
  311. {
  312. $user = $this->updates->getMessage()->getChat();
  313. return str_replace([
  314. '[user_first_name]',
  315. '[user_last_name]',
  316. '[user_full_name]',
  317. ], [
  318. $user->getFirstName(),
  319. $user->getLastName(),
  320. $user->getFirstName() . ' ' . $user->getLastName(),
  321. ], $text);
  322. }
  323. /**
  324. * send search result to user
  325. * @param $current_state
  326. * @return int
  327. */
  328. public function sendSearchResult($current_state, $searchResult)
  329. {
  330. // show last keyboard (if we have sticky keyboard it remain by default);
  331. $question = Question::findOrNew($current_state->current_sub_state);
  332. $this->getKeyboard($current_state, $question);
  333. $this->keyboardMarkup();
  334. $this->message = $searchResult;
  335. return $this->botSendMessage()->getMessageId();
  336. }
  337. /**
  338. * @param $current_state
  339. * @param null $text
  340. * @return int
  341. */
  342. public function sendIDoNotKnowMessage($current_state, $text = null)
  343. {
  344. // show last keyboard (if we have sticky keyboard it remain by default);
  345. $question = Question::findOrNew($current_state->current_sub_state);
  346. $this->getKeyboard($current_state, $question);
  347. $this->keyboardMarkup();
  348. $this->message = $text ?: 'عبارت وارد شده صحیح نمیباشد، لطفا دوباره امتحان نمایید.';
  349. return $this->botSendMessage()->getMessageId();
  350. }
  351. /**
  352. * @param $question
  353. * @param $answers
  354. * @return array
  355. */
  356. private function buttonsAutoSizing($question, $answers)
  357. {
  358. $answers = $answers->lists('body');
  359. if (mb_strlen($question->body, 'utf8') < 260) {
  360. // button auto (smart) sizing
  361. $keyboard = array_values($answers->chunk(2)->map(function ($item, $key) {
  362. return is_int($item) ? (string)$item : array_values($item->toArray());
  363. })->toArray());
  364. } else {
  365. // button auto sizing
  366. $rowLength = 0;
  367. $keyboard = $row = [];
  368. $answer_is_short = true;
  369. foreach ($answers as $answer) {
  370. $rowLength += mb_strlen($answer, 'utf8') + 4;
  371. if ($rowLength >= 45) {
  372. $answer_is_short = false;
  373. if (empty($row)) {
  374. $keyboard[] = (array) $row;
  375. } else {
  376. $keyboard[] = $row;
  377. }
  378. $rowLength = 0;
  379. $row = [];
  380. } else {
  381. $row[] = $answer;
  382. }
  383. }
  384. if($answer_is_short) {
  385. $keyboard[] = $row;
  386. }
  387. }
  388. return $keyboard;
  389. }
  390. }