· 7 years ago · Dec 11, 2018, 03:00 PM
1<?php
2
3/*
4CREATE TABLE IF NOT EXISTS `tasks` (
5 `id` int(11) NOT NULL,
6 `date_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
7 `date_task` datetime NOT NULL,
8 `original_request` mediumtext CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
9 `task_text` mediumtext CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
10 `chat_id` varchar(512) NOT NULL,
11 `done` tinyint(4) NOT NULL DEFAULT '0'
12) ENGINE=InnoDB DEFAULT CHARSET=utf8;
13
14
15ALTER TABLE `tasks`
16 ADD PRIMARY KEY (`id`),
17 ADD KEY `bot_tasks_target_date_index` (`date_task`) USING BTREE,
18 ADD KEY `done` (`done`);
19
20ALTER TABLE `tasks`
21 MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
22*/
23
24
25error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING & ~E_STRICT);
26
27define('WEBHOOK_URL', 'https://your-domain.ru/your/path/to/bot.php');
28define('BOT_TOKEN', "BOT_FATHER_GIVEN_TOKEN");
29
30define('API_URL', 'https://api.telegram.org/bot' . BOT_TOKEN . '/');
31
32function curl_custom_postfields($handle, array $assoc = array(), array $files = array()) {
33
34 // invalid characters for "name" and "filename"
35 static $disallow = array("\0", "\"", "\r", "\n");
36
37 // build normal parameters
38 foreach ($assoc as $key => $value) {
39 $key = str_replace($disallow, "_", $key);
40 $body[] = implode("\r\n", array(
41 "Content-Disposition: form-data; name=\"{$key}\"",
42 "",
43 filter_var($value),
44 ));
45 }
46
47 // build file parameters
48 foreach ($files as $key => $value) {
49 switch (true) {
50 case false === $value = realpath(filter_var($value)):
51 case !is_file($value):
52 case !is_readable($value):
53 continue; // or return false, throw new InvalidArgumentException
54 }
55 $data = file_get_contents($value);
56 $value = call_user_func("end", explode(DIRECTORY_SEPARATOR, $value));
57 $key = str_replace($disallow, "_", $key);
58 $value = str_replace($disallow, "_", $value);
59 $body[] = implode("\r\n", array(
60 "Content-Disposition: form-data; name=\"{$key}\"; filename=\"{$value}\"",
61 "Content-Type: application/octet-stream",
62 "",
63 $data,
64 ));
65 }
66
67 // generate safe boundary
68 do {
69 $boundary = "---------------------" . md5(mt_rand() . microtime());
70 } while (preg_grep("/{$boundary}/", $body));
71
72 // add boundary for each parameters
73 array_walk($body, function (&$part) use ($boundary) {
74 $part = "--{$boundary}\r\n{$part}";
75 });
76
77 // add final boundary
78 $body[] = "--{$boundary}--";
79 $body[] = "";
80
81 // set options
82 return @curl_setopt_array($handle, array(
83 CURLOPT_POST => true,
84 CURLOPT_POSTFIELDS => implode("\r\n", $body),
85 CURLOPT_HTTPHEADER => array(
86 "Expect: 100-continue",
87 "Content-Type: multipart/form-data; boundary={$boundary}", // change Content-Type
88 ),
89 ));
90}
91
92function exec_curl_request($handle) {
93
94 $response = curl_exec($handle);
95
96 if ($response === false) {
97 $errno = curl_errno($handle);
98 $error = curl_error($handle);
99 error_log("Curl returned error $errno: $error\n");
100 curl_close($handle);
101 return false;
102 }
103
104 $http_code = intval(curl_getinfo($handle, CURLINFO_HTTP_CODE));
105 curl_close($handle);
106
107 if ($http_code >= 500) {
108 // do not want to DDOS server if something goes wrong
109 sleep(10);
110 return false;
111 } else if ($http_code != 200) {
112 $response = json_decode($response, true);
113 error_log("Request has failed with error {$response['error_code']}: {$response['description']}\n");
114 if ($http_code == 401) {
115 error_log('Invalid access token provided');
116 // fall through to the core, do not proceed anything
117 throw new Exception('Invalid access token provided');
118 }
119 return false;
120 } else {
121 $response = json_decode($response, true);
122 // uncomment, if you want to log successful responses
123 // if (isset($response['description'])) {
124 // error_log("Request was successfull: {$response['description']}\n");
125 // }
126 $response = $response['result'];
127 }
128
129 return $response;
130}
131
132function api_request_webhook($method, $parameters) {
133 if (!is_string($method)) {
134 error_log("Method name must be a string\n");
135 return false;
136 }
137
138 if (!$parameters) {
139 $parameters = array();
140 } else if (!is_array($parameters)) {
141 error_log("Parameters must be an array\n");
142 return false;
143 }
144
145 $parameters["method"] = $method;
146
147 header("Content-Type: application/json");
148 echo json_encode($parameters);
149 return true;
150}
151
152function api_request($method, $parameters) {
153 if (!is_string($method)) {
154 error_log("Method name must be a string\n");
155 return false;
156 }
157
158 if (!$parameters) {
159 $parameters = array();
160 } else if (!is_array($parameters)) {
161 error_log("Parameters must be an array\n");
162 return false;
163 }
164 $url = API_URL . $method . '?' . http_build_query($parameters);
165
166 $handle = curl_init($url);
167 curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
168 curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 5);
169 curl_setopt($handle, CURLOPT_TIMEOUT, 60);
170
171 return exec_curl_request($handle);
172}
173
174function api_request_file($method, $parameters, $files) {
175 if (!is_string($method)) {
176 error_log("Method name must be a string\n");
177 return false;
178 }
179
180 if (!$parameters) {
181 $parameters = array();
182 } else if (!is_array($parameters)) {
183 error_log("Parameters must be an array\n");
184 return false;
185 }
186
187 $parameters["method"] = $method;
188
189 $handle = curl_init(API_URL);
190 curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
191 curl_custom_postfields($handle, $parameters, $files);
192
193 return exec_curl_request($handle);
194}
195
196$SESSION = './sessions_storage/allowed_chat_';
197
198function make_query($query) {
199 $mysqli = new mysqli("localhost", "db_username", "db_password", "db_basename");
200 if ($mysqli->connect_errno) {
201 error_log("Ðе удалоÑÑŒ подключитьÑÑ Ðº MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error);
202 exit;
203 }
204
205 if (!($result = $mysqli->query($query))) {
206 error_log("Ðе удалоÑÑŒ выполнить Ð·Ð°Ð¿Ñ€Ð¾Ñ SQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error);
207 exit;
208 }
209
210 return $result;
211}
212
213function handle_command($chat_id, $text) {
214 $chat_id = strval($chat_id); // for concatenation purposes
215
216 global $SESSION;
217
218 $tokens = array_map('filter_var', explode(' ', $text));
219 $authorized = file_get_contents($SESSION . $chat_id);
220
221 if ($authorized != 'authorized') {
222 api_request("sendMessage", array("chat_id" => $chat_id, "text" => 'Что-то пошло не так, друг.'));
223 }
224
225 switch (mb_strtolower($tokens[0], 'UTF-8')) {
226 case "напомни":
227
228 if (stripos($text, 'через') !== false) {
229 $re = '/^Ðапомни\sчерез\s(\d{1,2})\s(минут|минуты|минуту|минута|чаÑ|чаÑа|чаÑов|день|днÑ|дней)\s(.*)/i';
230 $result = preg_match($re, $text, $matches);
231 if ($result) {
232 $number = $matches[1];
233 $measure = $matches[2];
234 $what = $matches[3];
235 $when = date_create();
236 switch ($measure) {
237 case 'минут': case 'минуты': case 'минуту': case 'минута':
238 date_modify($when, "+" . $number . " minutes");
239 break;
240 case 'чаÑ': case 'чаÑа': case 'чаÑов':
241 date_modify($when, "+" . $number . " hours");
242 break;
243 case 'день': case 'днÑ': case 'дней':
244 date_modify($when, "+" . $number . " days");
245 break;
246 default: break;
247 }
248 }
249 } else {
250 $re = '/^Ðапомни\s(завтра|поÑлезавтра|\d{1,2})\sв\s(\d{1,2}):(\d{1,2})\s(.*)/i';
251 $result = preg_match($re, $text, $matches);
252 if ($result) {
253 $day = $matches[1];
254 $hour = $matches[2];
255 $minutes = $matches[3];
256 $what = $matches[4];
257 $when = date_create();
258 switch ($day) {
259 case 'завтра':
260 date_modify($when, "+1 day");
261 date_time_set($when, $hour, $minutes);
262 break;
263 case 'поÑлезавтра':
264 date_modify($when, "+2 day");
265 date_time_set($when, $hour, $minutes);
266 break;
267 default:
268 date_date_set($when, date('Y'), date('m'), $day);
269 if ($when < date_create())
270 date_modify($when, "+1 month");
271 date_time_set($when, $hour, $minutes);
272 break;
273 }
274 }
275 }
276
277 if (count($tokens) < 4 || !$what || !$result) {
278 api_request("sendMessage", array("chat_id" => $chat_id, "text" => 'Формат: Ðапомни [через] ВРЕМЯ Ñделать ЧТО.'));
279 } else {
280
281 make_query("INSERT INTO `tasks` (`id`, `date_created`, `date_task`, `original_request`, `task_text`, `chat_id`, `done`) VALUES (NULL, CURRENT_TIMESTAMP, '" . date_format($when, 'Y-m-d H:i:s') . "', '" . $text . "', '" . $what . "', '" . $chat_id . "', 0)");
282 api_request("sendMessage", array("chat_id" => $chat_id, "text" => 'Ðапомню ' . date_format($when, 'd.m.Y H:i')));
283 }
284 break;
285 case "нариÑуй":
286 $tasks_data = make_query('select MONTH(A.date_task) month, DAYOFMONTH(A.date_task) day, count(A.id) number_done, (select count(id) from tasks B where DATE(B.date_created) = DATE(A.date_task)) number_created from tasks A where (DATE(A.date_task) BETWEEN (CURDATE() - INTERVAL 15 DAY) AND (CURDATE() + INTERVAL 15 DAY)) group by day order by month * 31 + day;');
287
288 require_once './phplot.php';
289
290 $data = [];
291 foreach ($tasks_data as $task_data) {
292 $data[] = [strval($task_data['day']) . '.' . strval($task_data['month']), intval($task_data['number_created']), intval($task_data['number_done'])];
293 }
294
295 $plot = new PHPlot(900, 300);
296 $plot->SetImageBorderType('plain');
297
298 $plot->SetPlotType('bars');
299 $plot->SetDataType('text-data');
300 $plot->SetDataValues($data);
301
302 $plot->SetTitle('Tasks');
303 $plot->SetShading(0);
304
305 $plot->SetLegend(['Created', 'Done']);
306 $plot->SetXTickLabelPos('none');
307 $plot->SetXTickPos('none');
308
309 $plot->SetOutputFile($SESSION . $chat_id . '.png');
310 $plot->SetIsInline(true);
311 $plot->DrawGraph();
312
313 api_request_file("sendPhoto", array("chat_id" => $chat_id, "caption" => 'Диаграмма задач'), array("photo" => $SESSION . $chat_id . '.png'));
314
315 break;
316 case "напиши":
317 $tasks = make_query('select * from tasks where done = 0 and chat_id = "' . $chat_id . '" order by date_task asc;');
318 $tasks_text = '';
319 foreach ($tasks as $task) {
320 $tasks_text .= (date_format(date_create($task['date_task']), 'd.m.Y H:i') . ': ' . $task['task_text']) . PHP_EOL;
321 }
322 api_request("sendMessage", array("chat_id" => $chat_id, "text" => $tasks_text ?: 'Ðету дел.'));
323 break;
324 default: break;
325 }
326}
327
328function process_message($message, $data = null) {
329 // process incoming message
330 $message_id = $message['message_id'];
331 $chat_id = $message['chat']['id'];
332
333 $commands = ['напомни', 'напиши', 'нариÑуй'];
334
335 if (isset($message['text']) || !is_null($data)) {
336
337 $text = $message['text'];
338 if (!is_null($data))
339 $text = $data;
340
341 $command = mb_strtolower(filter_var(reset(explode(' ', $text))), 'UTF-8');
342
343 global $SESSION;
344
345 if (stripos($text, "/start") === 0) {
346 // start conversation request
347 api_request("sendMessage", array("chat_id" => $chat_id, "text" => 'Привет! Отправь мне кодовое Ñлово!'));
348 } else if (stripos($text, "/help") === 0) {
349 // send help message
350 api_request("sendMessage", array("chat_id" => $chat_id, "text" => 'Команды две: напомни и напиши. Ð’ÑÑ‘.'));
351 } else if (!file_exists($SESSION . strval($chat_id))) {
352 // check password
353 if ($text === 'Моё блин Ñпециальное кодовое Ñлово - оно же пароль - никто не угадает!') {
354 // work in sandbox directory only
355 file_put_contents($SESSION . strval($chat_id), "authorized");
356 api_request("sendMessage", array('chat_id' => $chat_id, "text" => 'Я буду тебе напоминать, когда и что попроÑишь.'));
357 } else
358 api_request("sendMessage", array('chat_id' => $chat_id, "text" => 'Ты никогда не отгадаешь код. Даже не пытайÑÑ.'));
359 } else if (in_array($command, $commands)) {
360 // allowed shell command
361 handle_command($chat_id, $text);
362 } else {
363 // unrecognized text request
364 api_request("sendMessage", array("chat_id" => $chat_id, "text" => "Ðичего не понÑл.", "reply_to_message_id" => $message_id));
365 }
366 } else
367 // incoming message is not a text (maybe sticker or image)
368 api_request("sendMessage", array('chat_id' => $chat_id, "text" => 'Я ничего не понÑл!'));
369}
370
371
372if (php_sapi_name() == 'cli') {
373 if (isset($argv[1]) && $argv[1] == 'notify') {
374 $current_tasks = make_query('select * from tasks where done = 0;');
375 foreach ($current_tasks as $task) {
376 $span = round((strtotime($task['date_task']) - strtotime('now')) / 60, 2);
377 if ($span < 5) { // five minutes before maybe
378 if (api_request("sendMessage", array('chat_id' => $task['chat_id'], "text" => $task['task_text']))) {
379 make_query('update tasks set done = 1 where id = ' . $task['id']);
380 }
381 }
382 }
383 } else
384 // if run from console, set or delete webhook
385 api_request('setWebhook', array('url' => isset($argv[1]) && $argv[1] == 'delete' ? '' : WEBHOOK_URL));
386 exit;
387}
388
389$content = file_get_contents("php://input");
390$update = json_decode($content, true);
391
392if (!$update) {
393 // receive wrong update, must not happen
394 exit;
395}
396
397if (isset($update["message"])) {
398 process_message($update["message"]);
399}
400
401if (isset($update["callback_query"])) {
402 process_message($update["callback_query"]["message"], $update["callback_query"]["data"]);
403}
404
405?>