a6e921200807c11952107a277f9e3e550045d7e8
[SonOfLokstallBot.git] / common.php
1 <?php declare(strict_types=1);
2
3 require_once('vendor/autoload.php');
4 require_once(__DIR__ . '/config.php');
5
6 use Telegram\Bot\Api;
7
8 function getTelegram(): \Telegram\Bot\Api {
9 STATIC $tg;
10 return $tg = $tg ?? new \Telegram\Bot\Api(BOT_TOKEN);
11 }
12
13 function splitBill($amount) {
14 return floor($amount/2);
15 }
16
17 function identity($x) {
18 return $x;
19 }
20
21 const notEmpty = 'notEmpty';
22 function notEmpty($value) {
23 return !empty($value);
24 }
25
26 function getMessageSender($update) {
27 return PARTICIPANT_IDS[$update->get('message')->get('from')->get('id')];
28 }
29
30 function getMessageSenderDisplayName($update) {
31 return $update->get('message')->get('from')->get('first_name');
32 }
33
34 function canChatWith($update) {
35 return in_array($update->get('message')->get('from')->get('id'), array_keys(PARTICIPANT_IDS));
36 }
37
38 function debug($whatever) {
39 echo '<pre>';
40 print_r($whatever);
41 echo '</pre>';
42 }
43
44 function partition(int $numPartitions, $array) {
45 $partitionSize = (int)ceil(count($array) / $numPartitions);
46
47 return
48 filter(notEmpty)(
49 map(function($p) use ($array, $partitionSize) {
50 return array_slice($array, $p*$partitionSize, $partitionSize);
51 })(range(0, $numPartitions-1))
52 );
53 }
54
55 function getInbox($inbox) {
56 STATIC $inboxes;
57
58 if (!isset($inboxes[$inbox])) {
59 $inboxes[$inbox] = imap_open(
60 '{imap.gmail.com:993/debug/imap/ssl/novalidate-cert}' . $inbox,
61 EMAIL,
62 PASSWORD
63 );
64 }
65
66 return $inboxes[$inbox];
67 }
68
69 function getRules() {
70 STATIC $rules;
71 return $rules = $rules ?? require 'rules.php';
72 }
73
74 const getString = 'getString';
75 function getString($identifier, ...$vars) {
76 STATIC $strings;
77 $strings = $strings ?? require 'strings.php';
78
79 return isset($strings[$identifier]) ? sprintf($strings[$identifier], ...$vars) : "[[$identifier]]";
80 }
81
82 const getStringAndCode = 'getStringAndCode';
83 function getStringAndCode($string) {
84 return getString($string) . " (" . $string . ")";
85 };
86
87 function formatDate($date) {
88 return $date->format(DATE_FORMAT);
89 }
90
91 function ssort($comparitor) {
92 return function($array) use ($comparitor) {
93 uasort($array, uncurry($comparitor));
94 return $array;
95 };
96 }
97
98 function uncurry($f) {
99 return function($a, $b) use ($f) {
100 return $f($a)($b);
101 };
102 }
103
104 const sendToGroupChat = 'sendToGroupChat';
105 function sendToGroupChat(string $message) {
106 return getTelegram()->sendMessage(['chat_id' => CHAT_ID, 'text' => $message]);
107 }
108
109 const generateReminderText = 'generateReminderText';
110 function generateReminderText($message) {
111 return getString('billreminder', REMIND_THRESHOLD, $message['service'], splitBill($message['amount']), formatDate($message['due']));
112 }
113
114 const generateNewBillText = 'generateNewBillText';
115 function generateNewBillText($message) {
116 return getString('newbill', $message['service'], splitBill($message['amount']), formatDate($message['due']));
117 }
118
119 const messageNeedsReminder = 'messageNeedsReminder';
120 function messageNeedsReminder($message) {
121 return $message['due']->diff(new DateTimeImmutable)->d == REMIND_THRESHOLD;
122 }
123
124 const lines = 'lines';
125 function lines(string $string): array {
126 return explode("\n", $string);
127 }
128
129 const glue = 'glue';
130 function glue(string $delim): callable {
131 return function(array $strings) use ($delim): string {
132 return implode($delim, $strings);
133 };
134 }
135
136 const unlines = 'unlines';
137 function unlines($lines) {
138 return implode("\n", $lines);
139 }
140
141 const ununlines = 'ununlines';
142 function ununlines($lines) {
143 return implode("\n\n", $lines);
144 }
145
146 const zipWith = 'zipWith';
147 function zipWith(callable $zipper, array $a, array $b) {
148 return array_map($zipper, $a, $b);
149 }
150
151 function field($field) {
152 return function($array) use ($field) {
153 return $array[$field];
154 };
155 }
156
157 const= '⬄';
158 function($a) {
159 return function($b) use ($a) {
160 return $a <=> $b;
161 };
162 }
163
164
165 function(...$fs) {
166 return function($arg) use ($fs) {
167 return array_reduce(array_reverse($fs), function($c, $f) {
168 return $f($c);
169 }, $arg);
170 };
171 }
172
173 function map($callable) {
174 return function($list) use ($callable) {
175 return array_map($callable, $list);
176 };
177 }
178
179 function aaray_column($column) {
180 return function($array) use ($column) {
181 return array_column($array, $column);
182 };
183 }
184
185 function aaray_slice($start) {
186 return function($length) use ($start) {
187 return function($array) use ($length, $start) {
188 return array_slice($array, $start, $length);
189 };
190 };
191 }
192
193 function filter($callable) {
194 return function($list) use ($callable) {
195 return array_filter($list, $callable);
196 };
197 }
198
199 function f∘(callable $f) {
200 return function(callable $g) use ($f) {
201 return function($arg) use($g, $f) {
202 return $f($g($arg));
203 };
204 };
205 }
206
207 functionf(callable $f) {
208 return function(callable $g) use ($f) {
209 return function($arg) use($g, $f) {
210 return $g($f($arg));
211 };
212 };
213 }
214
215 function($a, $b) {
216 return array_merge($a, $b);
217 }
218
219 function getSeason($monthNum) {
220 return ['summer', 'autumn', 'winter', 'spring'][floor(($monthNum)%12/3)];
221 }
222
223 function getMonthName($monthNum) {
224 return ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'][$monthNum-1];
225 }
226
227 function isStartOfSeason($monthNum, $dayNum) {
228 return ($monthNum)%3 == 0 && isStartOfMonth($dayNum);
229 }
230
231 function isStartOfMonth($dayNum) {
232 return $dayNum < 8;
233 }
234
235 function getTasksForTheSeason($season, $taskMatrix) {
236 return array_unique(
237 array_reduce(
238 $taskMatrix['annualy'][$season],
239 function($c, $v) {
240 return array_merge(
241 $c,
242 array_reduce($v, function($c, $v) {
243 return array_merge($c, is_array($v) ? $v : [$v]);
244 }, [])
245 );
246 },
247 []
248 )
249 );
250 }
251
252 function getTasksForTheMonth($monthNum, $taskMatrix) {
253 return array_merge(
254 $taskMatrix['monthly'],
255 $monthNum % 6 == 0 ? $taskMatrix['biannualy'] : [],
256 $monthNum % 3 == 0 ? $taskMatrix['quadriannualy'] : [],
257 array_filter(
258 $taskMatrix['annualy'][getSeason($monthNum)][getMonthName($monthNum)],
259 function($v) {
260 return !is_array($v);
261 }
262 )
263 );
264 }
265
266 // NB weeknum is 1-4 (the week of the month, not consistent with other things)
267 function getTasksForTheWeek($weekNum, $monthNum, $taskMatrix) {
268 return array_merge(
269 $weekNum % 2 == 0 ? $taskMatrix['bimonthly'] : [],
270 $taskMatrix['annualy'][getSeason($monthNum)][getMonthName($monthNum)]['weekly'] ?? [],
271 partition(4, getTasksForTheMonth($monthNum, $taskMatrix))[$weekNum]
272 );
273 }
274
275 function closest($n, $list) {
276 $a = array_filter($list, function($value) use ($n) {
277 return $value <= $n;
278 });
279
280 arsort($a);
281 return array_values($a)[0];
282 }
283
284 function getMessagesFromInbox($inbox, array $rules, $unseenOnly = true) {
285 return array_filter(
286 array_map(
287 function($rule, $service) use ($inbox, $unseenOnly) {
288 $emails = imap_search($inbox, ['SEEN ', 'UNSEEN '][$unseenOnly] . $rule['imapQuery'], SE_UID);
289
290 if(!$emails) {
291 return [];
292 }
293
294 $messageTransform = $rule['messageTransform'] ?? 'identity';
295 $dateTransform = $rule['dateTransform'] ?? 'identity';
296
297 $body = quoted_printable_decode(imap_fetchbody($inbox, $emails[0], '1', FT_UID));
298 preg_match($rule['regex'], $messageTransform($body), $matches);
299
300 return [
301 'service' => $service,
302 'id' => substr(md5($body), 0, 6),
303 'uid' => $emails[0],
304 'due' => new DateTimeImmutable($dateTransform($matches['due'])),
305 'amount' => $matches['amount']
306 ];
307 },
308 $rules,
309 array_keys($rules)
310 ),
311 function($e) {
312 return !!$e;
313 }
314 );
315 }