rules.pm 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. use Modern::Perl;
  2. use Data::Dumper;
  3. use Mojo::Base -strict, -signatures, -async_await;
  4. use Mojo::Util;
  5. use Scalar::Util;
  6. use experimental qw/switch/;
  7. use fsa;
  8. our $redis;
  9. our $config;
  10. our $client;
  11. our $commands;
  12. our $abon_client;
  13. use constant FAIL_ASK_SERIAL => 3;
  14. ##################################################
  15. my $rules = {
  16. logged_out => async sub
  17. {
  18. my ($fsa, $line, $info) = @_;
  19. if ($line ne "/start")
  20. {
  21. reply($info, _("Для начала работы наберите <b>/start</b>"));
  22. return "logged_out";
  23. }
  24. # Поступила команда /start
  25. # У нас есть uid в автомате? Это значит, что мы прочитали состояние из внешней базы
  26. if ($fsa->note("uid"))
  27. {
  28. reply_with($info, { button_menu => 1 }, _("Для получения списка доступных команд введите /help"));
  29. return "command";
  30. }
  31. # Проверяем, логинился ли уже пользователь в нашем боте
  32. my $res = eval { await $client->get_p("client", "/telegram/$info->{id}/client") };
  33. if ($@)
  34. {
  35. if ($@->{code} == 410)
  36. {
  37. reply($info,
  38. _("Вас приветствует провайдер") . " " . $config->{provider},
  39. _("Введите номер лицевого счёта или логин")
  40. );
  41. return "needs_login";
  42. }
  43. else
  44. {
  45. die $@;
  46. }
  47. }
  48. reply_with($info, { button_menu => 1 }, greet($res->{fio}), _("Для получения списка доступных команд введите /help"));
  49. $fsa->note(uid => $res->{uid});
  50. return "command";
  51. },
  52. needs_login => sub($fsa, $line, $info)
  53. {
  54. $fsa->note(login => $line);
  55. reply($info, _("Теперь введите пароль"));
  56. "needs_password";
  57. },
  58. needs_password => async sub
  59. {
  60. my ($fsa, $line, $info) = @_;
  61. request("deleteMessage", { chat_id=>$info->{id}, message_id=>$info->{msgid} });
  62. my $res = eval { await $client->post_p("client", "/telegram/client", {login=>$fsa->note("login"), password=>$line, telegram_id=>$info->{id}}) };
  63. if ($@)
  64. {
  65. $fsa->state("logged_out");
  66. $fsa->delete_note("login");
  67. die $@;
  68. }
  69. reply_with($info, { button_menu => 1 }, greet($res->{fio}), _("Для получения списка доступных команд введите /help"));
  70. $fsa->note(uid => $res->{uid});
  71. $fsa->delete_note("login");
  72. "command";
  73. },
  74. command => async sub
  75. {
  76. my ($fsa, $line, $info) = @_;
  77. await do_command($fsa, $line, $info);
  78. $fsa->state; # или все тот же command, или команда установила уже свое состояние
  79. },
  80. #### Перевод денег
  81. xfer_needs_uid => async sub
  82. {
  83. my ($fsa, $target_uid, $info) = @_;
  84. my $uid = $fsa->note("uid");
  85. $fsa->state("command"); # на случай die
  86. unless($target_uid)
  87. {
  88. reply($info, _("Перевод денег прерван"));
  89. }
  90. unless ($target_uid =~ /^\d+$/)
  91. {
  92. reply($info, _("Неправильный номер личного счета"), _("Введите его заново или пустую строку, если передумали пополнять"));
  93. return "xfer_needs_uid";
  94. }
  95. if ($uid==$target_uid)
  96. {
  97. reply($info, _("Нельзя перевести деньги себе самому"));
  98. return "command";
  99. }
  100. my $res = await $abon_client->get_p($info, "client", "/client/$target_uid");
  101. if ($res->{disabled})
  102. {
  103. reply($info, _("Абонент $target_uid отключен"));
  104. return "command";
  105. }
  106. my $nick = $res->{fio};
  107. my @fio = split(/\s+/, $res->{fio});
  108. if (@fio)
  109. {
  110. my $f = shift(@fio);
  111. $nick = join(" ", (substr($f, 0, 1) . ".", @fio));
  112. }
  113. reply(
  114. $info, _("Получатель денег: ") . $nick,
  115. "Теперь введите сумму, которую хотите перевести",
  116. );
  117. $fsa->note(xfer_to => $target_uid);
  118. return "xfer_needs_amount";
  119. },
  120. xfer_needs_amount => async sub
  121. {
  122. my ($fsa, $amount, $info) = @_;
  123. my $uid = $fsa->note("uid");
  124. my $to_uid = $fsa->note("xfer_to");
  125. unless($amount)
  126. {
  127. reply($info, _("Перевод денег прерван"));
  128. }
  129. $amount =~ s/,/./g;
  130. $amount =~ s/[^\d\.]//g;
  131. my $tmp = $amount;
  132. my $count = $amount =~ tr/.//;
  133. if (!$amount || $count>1)
  134. {
  135. reply($info, _("Вы ввели неправильную сумму"), _("Введите ее заново или пустую строку, если передумали пополнять"));
  136. return "xfer_needs_amount";
  137. }
  138. $fsa->note(xfer_amount => $amount);
  139. reply_with($info, {
  140. inline_menu => [[
  141. { text=>_("Подтвердите перевод"), callback_data=>"\x00/transfer" },
  142. ]]
  143. },
  144. sprintf("%.2f %s %s %d", $amount, $config->{currency}->{human}, _(" на счет абонента"), $to_uid),
  145. );
  146. return "command";
  147. },
  148. #### Заявки
  149. task_needs_descr => async sub
  150. {
  151. my ($fsa, $descr, $info) = @_;
  152. my $uid = $fsa->note("uid");
  153. $fsa->note(task_text => $descr);
  154. reply_with($info, {
  155. inline_menu => [[
  156. { text=>_("Отправить"), callback_data=>"\x00/task_post" },
  157. { text=>_("Отменить"), callback_data=>"\x00/task_cancel" },
  158. ]]
  159. },
  160. _("Перед отправкой заявки вы можете ее отредактировать средствами Телеграм")
  161. );
  162. return "command";
  163. },
  164. task_needs_comment => async sub
  165. {
  166. my ($fsa, $comment, $info) = @_;
  167. my $uid = $fsa->note("uid");
  168. my $task_id = $fsa->note("task_id");
  169. my $params = {
  170. text => $comment,
  171. "for-client" => $uid,
  172. };
  173. $fsa->state("command"); # на случай die
  174. my $res = await $client->post_p("task", "/task/$task_id/comment", $params);
  175. reply($info, "Ваш ответ добавлен");
  176. $fsa->delete_note("task_id");
  177. return "command";
  178. },
  179. ##### Карточка пополнения
  180. card_needs_code => sub
  181. {
  182. my ($fsa, $code, $info) = @_;
  183. my $uid = $fsa->note("uid");
  184. $fsa->state("command"); # на случай die
  185. $code =~ s/\D//g;
  186. unless($code)
  187. {
  188. reply($info, _("Пополнение карточкой прервано"));
  189. }
  190. unless ($code =~ /^\d{16}$/)
  191. {
  192. reply($info, _("Код карточки должен состоять из 16 цифр"), _("Введите его заново или пустую строку, если передумали пополнять"));
  193. return "card_needs_code";
  194. }
  195. my $key = "card-guess-$uid";
  196. my $fails = $redis->get($key) || 0;
  197. if ($fails > FAIL_ASK_SERIAL)
  198. {
  199. $fsa->note(card_code => $code);
  200. reply($info, _("Теперь введите номер карточки (7 цифр, можно разделять знаком'-')"));
  201. return "card_needs_serial";
  202. }
  203. else
  204. {
  205. pay_from_card($info, $uid, $code, "");
  206. return "command";
  207. }
  208. },
  209. card_needs_serial => sub
  210. {
  211. my ($fsa, $serial, $info) = @_;
  212. $fsa->state("command"); # на случай die
  213. unless($serial)
  214. {
  215. reply($info, _("Пополнение карточкой прервано"));
  216. }
  217. unless ($serial =~ /^\d{7}$/)
  218. {
  219. reply($info, _("Номер карточки должен состоять из 7 цифр"), _("Введите его заново или пустую строку, если передумали пополнять"));
  220. return "card_needs_serial";
  221. }
  222. pay_from_card($info, $fsa->note("uid"), $fsa->note("card_code"), $serial);
  223. return "command";
  224. },
  225. };
  226. sub greet($whom)
  227. {
  228. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
  229. my $greet = $hour>=4 && $hour<=10 ? _("Доброе утро") : $hour>10 && $hour<19 ? _("Добрый день") : _("Добрый вечер");
  230. "$greet, $whom";
  231. }
  232. async sub pay_from_card
  233. {
  234. my ($info, $uid, $code, $serial) = @_;
  235. my $args = {
  236. code => $code,
  237. uid => $uid,
  238. domain => $config->{domain},
  239. source => "abonbot",
  240. ip => "0.0.0.0",
  241. };
  242. $args->{serial} = $serial if $serial;
  243. my $res = eval { await $client->post_p("card", "/redemption", $args) };
  244. unless($@)
  245. {
  246. return reply($info, _("Ваш счёт пополнен на " . $res->{amount} . " " . $res->{currency_short_name_ru} . ". " . _("Оплачено до ") . $res->{paid_until}));
  247. };
  248. given($@->{code})
  249. {
  250. when(400)
  251. {
  252. reply($info, _("Вы ввели неправильный номер или код карточки"));
  253. my $key = "card-guess-$uid";
  254. $redis->incr($key);
  255. $redis->expire($key, 24 * 3600);
  256. }
  257. when(409)
  258. {
  259. reply($info, _("Карточка уже использована"));
  260. }
  261. when(406)
  262. {
  263. reply($info, _("Карточка не активирована. Обратитесь в службу поддержки"));
  264. }
  265. default
  266. {
  267. die $@;
  268. }
  269. }
  270. };
  271. ######################
  272. sub make_key($id)
  273. {
  274. return "abonbot-$id";
  275. }
  276. sub save_fsa($fsa, $chatid)
  277. {
  278. my $key = make_key($chatid);
  279. say "saving state ", $fsa->state, Dumper $fsa->notes;
  280. my $notes = $fsa->notes;
  281. utf8::encode($notes->{$_}) for keys %$notes;
  282. $redis->del($key);
  283. $redis->hmset($key, %$notes);
  284. $redis->expire($key, 3600*24);
  285. }
  286. sub make_fsa($chatid, $from)
  287. {
  288. my $id = make_key($chatid);
  289. my $notes = { $redis->hgetall($id) };
  290. if (keys %$notes)
  291. {
  292. utf8::decode($notes->{$_}) for keys %$notes;
  293. return fsa->new($rules, $notes->{_state}, $notes);
  294. }
  295. else
  296. {
  297. return fsa->new($rules, "logged_out", {});
  298. }
  299. }
  300. 1;