]> git.mxchange.org Git - fba.git/blob - fetch_blocks.py
Continued:
[fba.git] / fetch_blocks.py
1 #!/usr/bin/python3
2 # -*- coding: utf-8 -*-
3
4 # Fedi API Block - An aggregator for fetching blocking data from fediverse nodes
5 # Copyright (C) 2023 Free Software Foundation
6 #
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published
9 # by the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU Affero General Public License for more details.
16 #
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
19
20 import bs4
21 import itertools
22 import re
23 import reqto
24 import sys
25 import time
26 import validators
27 from fba import *
28
29 boot.acquire_lock()
30
31 fba.cursor.execute(
32     "SELECT domain, software, origin, nodeinfo_url FROM instances WHERE software IN ('pleroma', 'mastodon', 'friendica', 'misskey', 'gotosocial', 'bookwyrm', 'takahe') AND (last_blocked IS NULL OR last_blocked < ?) ORDER BY rowid DESC", [time.time() - config.get("recheck_block")]
33 )
34
35 rows = fba.cursor.fetchall()
36 print(f"INFO: Checking {len(rows)} entries ...")
37 for blocker, software, origin, nodeinfo_url in rows:
38     # DEBUG: print("DEBUG: BEFORE blocker,software,origin,nodeinfo_url:", blocker, software, origin, nodeinfo_url)
39     blockdict = []
40     blocker = fba.tidyup_domain(blocker)
41     # DEBUG: print("DEBUG: AFTER blocker,software:", blocker, software)
42
43     if blocker == "":
44         print("WARNING: blocker is now empty!")
45         continue
46     elif fba.is_blacklisted(blocker):
47         print(f"WARNING: blocker='{blocker}' is blacklisted now!")
48         continue
49
50     # DEBUG: print(f"DEBUG: blocker='{blocker}'")
51     fba.update_last_blocked(blocker)
52
53     if software == "pleroma":
54         print("INFO: blocker:", blocker)
55         try:
56             # Blocks
57             json = fba.fetch_nodeinfo(blocker, nodeinfo_url)
58             if json is None:
59                 print("WARNING: Could not fetch nodeinfo from blocker:", blocker)
60                 continue
61             elif not "metadata" in json:
62                 print(f"WARNING: json()={len(json)} does not have key 'metadata', blocker='{blocker}'")
63                 continue
64             elif not "federation" in json["metadata"]:
65                 print(f"WARNING: json()={len(json['metadata'])} does not have key 'federation', blocker='{blocker}'")
66                 continue
67
68             # DEBUG: print("DEBUG: Updating nodeinfo:", blocker)
69             fba.update_last_nodeinfo(blocker)
70
71             federation = json["metadata"]["federation"]
72
73             if "enabled" in federation:
74                 # DEBUG: print("DEBUG: Instance has no block list to analyze:", blocker)
75                 continue
76
77             if "mrf_simple" in federation:
78                 for block_level, blocks in (
79                     {**federation["mrf_simple"],
80                     **{"quarantined_instances": federation["quarantined_instances"]}}
81                 ).items():
82                     # DEBUG: print("DEBUG: block_level, blocks():", block_level, len(blocks))
83                     block_level = fba.tidyup_domain(block_level)
84                     # DEBUG: print("DEBUG: BEFORE block_level:", block_level)
85
86                     if block_level == "":
87                         print("WARNING: block_level is now empty!")
88                         continue
89
90                     # DEBUG: print(f"DEBUG: Checking {len(blocks)} entries from blocker='{blocker}',software='{software}',block_level='{block_level}' ...")
91                     for blocked in blocks:
92                         # DEBUG: print("DEBUG: BEFORE blocked:", blocked)
93                         blocked = fba.tidyup_domain(blocked)
94                         # DEBUG: print("DEBUG: AFTER blocked:", blocked)
95
96                         if blocked == "":
97                             print("WARNING: blocked is empty after fba.tidyup_domain():", blocker, block_level)
98                             continue
99                         elif fba.is_blacklisted(blocked):
100                             # DEBUG: print(f"DEBUG: blocked='{blocked}' is blacklisted - skipping!")
101                             continue
102                         elif blocked.count("*") > 1:
103                             # -ACK!-oma also started obscuring domains without hash
104                             fba.cursor.execute(
105                                 "SELECT domain, nodeinfo_url FROM instances WHERE domain LIKE ? ORDER BY rowid LIMIT 1", [blocked.replace("*", "_")]
106                             )
107                             searchres = fba.cursor.fetchone()
108                             # DEBUG: print("DEBUG: searchres[]:", type(searchres))
109
110                             if searchres == None:
111                                 print(f"WARNING: Cannot deobsfucate blocked='{blocked}' - SKIPPED!")
112                                 continue
113
114                             blocked = searchres[0]
115                             nodeinfo_url = searchres[1]
116                             # DEBUG: print("DEBUG: Looked up domain:", blocked)
117                         elif not validators.domain(blocked):
118                             print(f"WARNING: blocked='{blocked}',software='{software}' is not a valid domain name - skipped!")
119                             continue
120
121                         # DEBUG: print("DEBUG: Looking up instance by domain:", blocked)
122                         if not validators.domain(blocked):
123                             print(f"WARNING: blocked='{blocked}',software='{software}' is not a valid domain name - skipped!")
124                             continue
125                         elif not fba.is_instance_registered(blocked):
126                             # DEBUG: print(f"DEBUG: Domain blocked='{blocked}' wasn't found, adding ..., blocker='{blocker}',origin='{origin}',nodeinfo_url='{nodeinfo_url}'")
127                             fba.add_instance(blocked, blocker, sys.argv[0], nodeinfo_url)
128
129                         if not fba.is_instance_blocked(blocker, blocked, block_level):
130                             # DEBUG: print("DEBUG: Blocking:", blocker, blocked, block_level)
131                             fba.block_instance(blocker, blocked, "unknown", block_level)
132
133                             if block_level == "reject":
134                                 # DEBUG: print("DEBUG: Adding to blockdict:", blocked)
135                                 blockdict.append(
136                                     {
137                                         "blocked": blocked,
138                                         "reason" : None
139                                     })
140                         else:
141                             # DEBUG: print(f"DEBUG: Updating block last seen for blocker='{blocker}',blocked='{blocked}' ...")
142                             fba.update_last_seen(blocker, blocked, block_level)
143
144             # DEBUG: print("DEBUG: Committing changes ...")
145             fba.connection.commit()
146
147             # Reasons
148             if "mrf_simple_info" in federation:
149                 # DEBUG: print("DEBUG: Found mrf_simple_info:", blocker)
150                 for block_level, info in (
151                     {**federation["mrf_simple_info"],
152                     **(federation["quarantined_instances_info"]
153                     if "quarantined_instances_info" in federation
154                     else {})}
155                 ).items():
156                     # DEBUG: print("DEBUG: block_level, info.items():", block_level, len(info.items()))
157                     block_level = fba.tidyup_domain(block_level)
158                     # DEBUG: print("DEBUG: BEFORE block_level:", block_level)
159
160                     if block_level == "":
161                         print("WARNING: block_level is now empty!")
162                         continue
163
164                     # DEBUG: print(f"DEBUG: Checking {len(info.items())} entries from blocker='{blocker}',software='{software}',block_level='{block_level}' ...")
165                     for blocked, reason in info.items():
166                         # DEBUG: print("DEBUG: BEFORE blocked:", blocked)
167                         blocked = fba.tidyup_domain(blocked)
168                         # DEBUG: print("DEBUG: AFTER blocked:", blocked)
169
170                         if blocked == "":
171                             print("WARNING: blocked is empty after fba.tidyup_domain():", blocker, block_level)
172                             continue
173                         elif fba.is_blacklisted(blocked):
174                             # DEBUG: print(f"DEBUG: blocked='{blocked}' is blacklisted - skipping!")
175                             continue
176                         elif blocked.count("*") > 1:
177                             # same domain guess as above, but for reasons field
178                             fba.cursor.execute(
179                                 "SELECT domain, origin, nodeinfo_url FROM instances WHERE domain LIKE ? ORDER BY rowid LIMIT 1", [blocked.replace("*", "_")]
180                             )
181                             searchres = fba.cursor.fetchone()
182
183                             if searchres == None:
184                                 print(f"WARNING: Cannot deobsfucate blocked='{blocked}' - SKIPPED!")
185                                 continue
186
187                             blocked = searchres[0]
188                             origin = searchres[1]
189                             nodeinfo_url = searchres[2]
190                         elif not validators.domain(blocked):
191                             print(f"WARNING: blocked='{blocked}',software='{software}' is not a valid domain name - skipped!")
192                             continue
193
194                         # DEBUG: print("DEBUG: Looking up instance by domain:", blocked)
195                         if not validators.domain(blocked):
196                             print(f"WARNING: blocked='{blocked}',software='{software}' is not a valid domain name - skipped!")
197                             continue
198                         elif not fba.is_instance_registered(blocked):
199                             # DEBUG: print(f"DEBUG: Domain blocked='{blocked}' wasn't found, adding ..., blocker='{blocker}',origin='{origin}',nodeinfo_url='{nodeinfo_url}'")
200                             fba.add_instance(blocked, blocker, sys.argv[0], nodeinfo_url)
201
202                         # DEBUG: print("DEBUG: Updating block reason:", blocker, blocked, reason["reason"])
203                         fba.update_block_reason(reason["reason"], blocker, blocked, block_level)
204
205                         for entry in blockdict:
206                             if entry["blocked"] == blocked:
207                                 # DEBUG: print("DEBUG: Updating entry reason:", blocked)
208                                 entry["reason"] = reason["reason"]
209
210             fba.connection.commit()
211         except Exception as e:
212             print(f"ERROR: blocker='{blocker}',software='{software}',exception[{type(e)}]:'{str(e)}'")
213     elif software == "mastodon":
214         print("INFO: blocker:", blocker)
215         try:
216             # json endpoint for newer mastodongs
217             try:
218                 json = {
219                     "reject"        : [],
220                     "media_removal" : [],
221                     "followers_only": [],
222                     "report_removal": []
223                 }
224
225                 # handling CSRF, I've saw at least one server requiring it to access the endpoint
226                 # DEBUG: print("DEBUG: Fetching meta:", blocker)
227                 meta = bs4.BeautifulSoup(
228                     fba.get_response(blocker, "/", fba.headers, (config.get("connection_timeout"), config.get("read_timeout"))).text,
229                     "html.parser",
230                 )
231                 try:
232                     csrf = meta.find("meta", attrs={"name": "csrf-token"})["content"]
233                     # DEBUG: print("DEBUG: Adding CSRF token:", blocker, csrf)
234                     reqheaders = {**fba.api_headers, **{"X-CSRF-Token": csrf}}
235                 except BaseException as e:
236                     # DEBUG: print("DEBUG: No CSRF token found, using normal headers:", blocker, e)
237                     reqheaders = fba.api_headers
238
239                 # DEBUG: print("DEBUG: Querying API domain_blocks:", blocker)
240                 blocks = fba.get_response(blocker, "/api/v1/instance/domain_blocks", reqheaders, (config.get("connection_timeout"), config.get("read_timeout"))).json()
241
242                 print(f"INFO: Checking {len(blocks)} entries from blocker='{blocker}',software='{software}' ...")
243                 for block in blocks:
244                     entry = {
245                         'domain': block['domain'],
246                         'hash'  : block['digest'],
247                         'reason': block['comment']
248                     }
249
250                     # DEBUG: print("DEBUG: severity,domain,hash,comment:", block['severity'], block['domain'], block['digest'], block['comment'])
251                     if block['severity'] == 'suspend':
252                         # DEBUG: print(f"DEBUG: Adding entry='{entry}' with severity='{block['severity']}' ...")
253                         json['reject'].append(entry)
254                     elif block['severity'] == 'silence':
255                         # DEBUG: print(f"DEBUG: Adding entry='{entry}' with severity='{block['severity']}' ...")
256                         json['followers_only'].append(entry)
257                     elif block['severity'] == 'reject_media':
258                         # DEBUG: print(f"DEBUG: Adding entry='{entry}' with severity='{block['severity']}' ...")
259                         json['media_removal'].append(entry)
260                     elif block['severity'] == 'reject_reports':
261                         # DEBUG: print(f"DEBUG: Adding entry='{entry}' with severity='{block['severity']}' ...")
262                         json['report_removal'].append(entry)
263                     else:
264                         print("WARNING: Unknown severity:", block['severity'], block['domain'])
265             except BaseException as e:
266                 # DEBUG: print(f"DEBUG: Failed, trying mastodon-specific fetches: blocker='{blocker}',exception[{type(e)}]={str(e)}")
267                 json = fba.get_mastodon_blocks(blocker)
268
269             print(f"INFO: Checking {len(json.items())} entries from blocker='{blocker}',software='{software}' ...")
270             for block_level, blocks in json.items():
271                 # DEBUG: print("DEBUG: blocker,block_level,blocks():", blocker, block_level, len(blocks))
272                 block_level = fba.tidyup_domain(block_level)
273                 # DEBUG: print("DEBUG: AFTER-block_level:", block_level)
274                 if block_level == "":
275                     print("WARNING: block_level is empty, blocker:", blocker)
276                     continue
277
278                 # DEBUG: print(f"DEBUG: Checking {len(blocks)} entries from blocker='{blocker}',software='{software}',block_level='{block_level}' ...")
279                 for block in blocks:
280                     blocked, blocked_hash, reason = block.values()
281                     # DEBUG: print("DEBUG: blocked,hash,reason:", blocked, blocked_hash, reason)
282                     blocked = fba.tidyup_domain(blocked)
283                     # DEBUG: print("DEBUG: AFTER-blocked:", blocked)
284
285                     if blocked == "":
286                         print("WARNING: blocked is empty:", blocker)
287                         continue
288                     elif fba.is_blacklisted(blocked):
289                         # DEBUG: print(f"DEBUG: blocked='{blocked}' is blacklisted - skipping!")
290                         continue
291                     elif blocked.count("*") > 0:
292                         # Doing the hash search for instance names as well to tidy up DB
293                         fba.cursor.execute(
294                             "SELECT domain, origin, nodeinfo_url FROM instances WHERE hash = ? LIMIT 1", [blocked_hash]
295                         )
296                         searchres = fba.cursor.fetchone()
297
298                         if searchres == None:
299                             print(f"WARNING: Cannot deobsfucate blocked='{blocked}',blocked_hash='{blocked_hash}' - SKIPPED!")
300                             continue
301
302                         # DEBUG: print("DEBUG: Updating domain: ", searchres[0])
303                         blocked = searchres[0]
304                         origin = searchres[1]
305                         nodeinfo_url = searchres[2]
306
307                         # DEBUG: print("DEBUG: Looking up instance by domain:", blocked)
308                         if not validators.domain(blocked):
309                             print(f"WARNING: blocked='{blocked}',software='{software}' is not a valid domain name - skipped!")
310                             continue
311                         elif not fba.is_instance_registered(blocked):
312                             # DEBUG: print(f"DEBUG: Domain blocked='{blocked}' wasn't found, adding ..., blocker='{blocker}',origin='{origin}',nodeinfo_url='{nodeinfo_url}'")
313                             fba.add_instance(blocked, blocker, sys.argv[0], nodeinfo_url)
314                     elif not validators.domain(blocked):
315                         print(f"WARNING: blocked='{blocked}',software='{software}' is not a valid domain name - skipped!")
316                         continue
317
318                     # DEBUG: print("DEBUG: Looking up instance by domain:", blocked)
319                     if not validators.domain(blocked):
320                         print(f"WARNING: blocked='{blocked}',software='{software}' is not a valid domain name - skipped!")
321                         continue
322                     elif not fba.is_instance_registered(blocked):
323                         # DEBUG: print("DEBUG: Hash wasn't found, adding:", blocked, blocker)
324                         fba.add_instance(blocked, blocker, sys.argv[0], nodeinfo_url)
325
326                     blocking = blocked if blocked.count("*") <= 1 else blocked_hash
327                     # DEBUG: print(f"DEBUG: blocking='{blocking}',blocked='{blocked}',blocked_hash='{blocked_hash}'")
328
329                     if not fba.is_instance_blocked(blocker, blocked, block_level):
330                         # DEBUG: print("DEBUG: Blocking:", blocker, blocked, block_level)
331                         fba.block_instance(blocker, blocking, reason, block_level)
332
333                         if block_level == "reject":
334                             blockdict.append({
335                                 "blocked": blocked,
336                                 "reason" : reason
337                             })
338                     else:
339                         # DEBUG: print(f"DEBUG: Updating block last seen and reason for blocker='{blocker}',blocking='{blocking}' ...")
340                         fba.update_last_seen(blocker, blocking, block_level)
341                         fba.update_block_reason(reason, blocker, blocking, block_level)
342
343             # DEBUG: print("DEBUG: Committing changes ...")
344             fba.connection.commit()
345         except Exception as e:
346             print(f"ERROR: blocker='{blocker}',software='{software}',exception[{type(e)}]:'{str(e)}'")
347     elif software == "friendica" or software == "misskey" or software == "bookwyrm" or software == "takahe":
348         print("INFO: blocker:", blocker)
349         try:
350             if software == "friendica":
351                 json = fba.get_friendica_blocks(blocker)
352             elif software == "misskey":
353                 json = fba.get_misskey_blocks(blocker)
354             elif software == "bookwyrm":
355                 print("WARNING: bookwyrm is not fully supported for fetching blacklist!", blocker)
356                 #json = fba.get_bookwyrm_blocks(blocker)
357                 continue
358             elif software == "takahe":
359                 print("WARNING: takahe is not fully supported for fetching blacklist!", blocker)
360                 #json = fba.get_takahe_blocks(blocker)
361                 continue
362
363             print(f"INFO: Checking {len(json.items())} entries from blocker='{blocker}',software='{software}' ...")
364             for block_level, blocks in json.items():
365                 # DEBUG: print("DEBUG: blocker,block_level,blocks():", blocker, block_level, len(blocks))
366                 block_level = fba.tidyup_domain(block_level)
367                 # DEBUG: print("DEBUG: AFTER-block_level:", block_level)
368                 if block_level == "":
369                     print("WARNING: block_level is empty, blocker:", blocker)
370                     continue
371
372                 # DEBUG: print(f"DEBUG: Checking {len(blocks)} entries from blocker='{blocker}',software='{software}',block_level='{block_level}' ...")
373                 for block in blocks:
374                     blocked, reason = block.values()
375                     # DEBUG: print("DEBUG: BEFORE blocked:", blocked)
376                     blocked = fba.tidyup_domain(blocked)
377                     # DEBUG: print("DEBUG: AFTER blocked:", blocked)
378
379                     if blocked == "":
380                         print("WARNING: blocked is empty:", blocker)
381                         continue
382                     elif fba.is_blacklisted(blocked):
383                         # DEBUG: print(f"DEBUG: blocked='{blocked}' is blacklisted - skipping!")
384                         continue
385                     elif blocked.count("*") > 0:
386                         # Some friendica servers also obscure domains without hash
387                         fba.cursor.execute(
388                             "SELECT domain, origin, nodeinfo_url FROM instances WHERE domain LIKE ? ORDER BY rowid LIMIT 1", [blocked.replace("*", "_")]
389                         )
390
391                         searchres = fba.cursor.fetchone()
392
393                         if searchres == None:
394                             print(f"WARNING: Cannot deobsfucate blocked='{blocked}' - SKIPPED!")
395                             continue
396
397                         blocked = searchres[0]
398                         origin = searchres[1]
399                         nodeinfo_url = searchres[2]
400                     elif blocked.count("?") > 0:
401                         # Some obscure them with question marks, not sure if that's dependent on version or not
402                         fba.cursor.execute(
403                             "SELECT domain, origin, nodeinfo_url FROM instances WHERE domain LIKE ? ORDER BY rowid LIMIT 1", [blocked.replace("?", "_")]
404                         )
405
406                         searchres = fba.cursor.fetchone()
407
408                         if searchres == None:
409                             print(f"WARNING: Cannot deobsfucate blocked='{blocked}' - SKIPPED!")
410                             continue
411
412                         blocked = searchres[0]
413                         origin = searchres[1]
414                         nodeinfo_url = searchres[2]
415                     elif not validators.domain(blocked):
416                         print(f"WARNING: blocked='{blocked}',software='{software}' is not a valid domain name - skipped!")
417                         continue
418
419                     # DEBUG: print("DEBUG: Looking up instance by domain:", blocked)
420                     if not validators.domain(blocked):
421                         print(f"WARNING: blocked='{blocked}',software='{software}' is not a valid domain name - skipped!")
422                         continue
423                     elif not fba.is_instance_registered(blocked):
424                         # DEBUG: print("DEBUG: Hash wasn't found, adding:", blocked, blocker)
425                         fba.add_instance(blocked, blocker, sys.argv[0], nodeinfo_url)
426
427                     if not fba.is_instance_blocked(blocker, blocked, block_level):
428                         fba.block_instance(blocker, blocked, reason, block_level)
429
430                         if block_level == "reject":
431                             blockdict.append({
432                                 "blocked": blocked,
433                                 "reason" : reason
434                             })
435                     else:
436                         # DEBUG: print(f"DEBUG: Updating block last seen and reason for blocker='{blocker}',blocked='{blocked}' ...")
437                         fba.update_last_seen(blocker, blocked, block_level)
438                         fba.update_block_reason(reason, blocker, blocked, block_level)
439
440             # DEBUG: print("DEBUG: Committing changes ...")
441             fba.connection.commit()
442         except Exception as e:
443             print(f"ERROR: blocker='{blocker}',software='{software}',exception[{type(e)}]:'{str(e)}'")
444     elif software == "gotosocial":
445         print("INFO: blocker:", blocker)
446         try:
447             # Blocks
448             federation = fba.get_response(blocker, "{fba.get_peers_url}?filter=suspended", fba.api_headers, (config.get("connection_timeout"), config.get("read_timeout"))).json()
449
450             if (federation == None):
451                 print("WARNING: No valid response:", blocker);
452             elif "error" in federation:
453                 print("WARNING: API returned error:", federation["error"])
454             else:
455                 print(f"INFO: Checking {len(federation)} entries from blocker='{blocker}',software='{software}' ...")
456                 for peer in federation:
457                     blocked = peer["domain"].lower()
458                     # DEBUG: print("DEBUG: BEFORE blocked:", blocked)
459                     blocked = fba.tidyup_domain(blocked)
460                     # DEBUG: print("DEBUG: AFTER blocked:", blocked)
461
462                     if blocked == "":
463                         print("WARNING: blocked is empty:", blocker)
464                         continue
465                     elif fba.is_blacklisted(blocked):
466                         # DEBUG: print(f"DEBUG: blocked='{blocked}' is blacklisted - skipping!")
467                         continue
468                     elif blocked.count("*") > 0:
469                         # GTS does not have hashes for obscured domains, so we have to guess it
470                         fba.cursor.execute(
471                             "SELECT domain, origin, nodeinfo_url FROM instances WHERE domain LIKE ? ORDER BY rowid LIMIT 1", [blocked.replace("*", "_")]
472                         )
473                         searchres = fba.cursor.fetchone()
474
475                         if searchres == None:
476                             print(f"WARNING: Cannot deobsfucate blocked='{blocked}' - SKIPPED!")
477                             continue
478
479                         blocked = searchres[0]
480                         origin = searchres[1]
481                         nodeinfo_url = searchres[2]
482                     elif not validators.domain(blocked):
483                         print(f"WARNING: blocked='{blocked}',software='{software}' is not a valid domain name - skipped!")
484                         continue
485
486                     # DEBUG: print("DEBUG: Looking up instance by domain:", blocked)
487                     if not validators.domain(blocked):
488                         print(f"WARNING: blocked='{blocked}',software='{software}' is not a valid domain name - skipped!")
489                         continue
490                     elif not fba.is_instance_registered(blocked):
491                         # DEBUG: print(f"DEBUG: Domain blocked='{blocked}' wasn't found, adding ..., blocker='{blocker}',origin='{origin}',nodeinfo_url='{nodeinfo_url}'")
492                         fba.add_instance(blocked, blocker, sys.argv[0], nodeinfo_url)
493
494                     if not fba.is_instance_blocked(blocker, blocked, "reject"):
495                         # DEBUG: print(f"DEBUG: blocker='{blocker}' is blocking '{blocked}' for unknown reason at this point")
496                         fba.block_instance(blocker, blocked, "unknown", "reject")
497
498                         blockdict.append({
499                             "blocked": blocked,
500                             "reason" : None
501                         })
502                     else:
503                         # DEBUG: print(f"DEBUG: Updating block last seen for blocker='{blocker}',blocked='{blocked}' ...")
504                         fba.update_last_seen(blocker, blocked, "reject")
505
506                     if "public_comment" in peer:
507                         # DEBUG: print("DEBUG: Updating block reason:", blocker, blocked, peer["public_comment"])
508                         fba.update_block_reason(peer["public_comment"], blocker, blocked, "reject")
509
510                         for entry in blockdict:
511                             if entry["blocked"] == blocked:
512                                 # DEBUG: print(f"DEBUG: Setting block reason for blocked='{blocked}':'{peer['public_comment']}'")
513                                 entry["reason"] = peer["public_comment"]
514
515                 # DEBUG: print("DEBUG: Committing changes ...")
516                 fba.connection.commit()
517         except Exception as e:
518             print(f"ERROR: blocker='{blocker}',software='{software}',exception[{type(e)}]:'{str(e)}'")
519     else:
520         print("WARNING: Unknown software:", blocker, software)
521
522     if config.get("bot_enabled") and len(blockdict) > 0:
523         send_bot_post(blocker, blockdict)
524
525     blockdict = []
526
527 boot.shutdown()