]> git.mxchange.org Git - fba.git/blob - fba/networks/lemmy.py
63829bb6deb662f0c3aa6a9e52bfc98fcc627d5b
[fba.git] / fba / networks / lemmy.py
1 # Fedi API Block - An aggregator for fetching blocking data from fediverse nodes
2 # Copyright (C) 2023 Free Software Foundation
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published
6 # by the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17 import inspect
18
19 import bs4
20 import validators
21
22 from fba import blacklist
23 from fba import config
24 from fba import csrf
25 from fba import fba
26 from fba import federation
27 from fba import network
28
29 from fba.models import blocks
30 from fba.models import instances
31
32 def fetch_peers(domain: str) -> list:
33     # DEBUG: print(f"DEBUG: domain({len(domain)})='{domain}',software='lemmy' - CALLED!")
34     if not isinstance(domain, str):
35         raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
36     elif domain == "":
37         raise ValueError("Parameter 'domain' is empty")
38
39     peers = list()
40
41     # No CSRF by default, you don't have to add network.api_headers by yourself here
42     headers = tuple()
43
44     try:
45         # DEBUG: print(f"DEBUG: Checking CSRF for domain='{domain}'")
46         headers = csrf.determine(domain, dict())
47     except network.exceptions as exception:
48         print(f"WARNING: Exception '{type(exception)}' during checking CSRF (fetch_peers,{__name__}) - EXIT!")
49         instances.set_last_error(domain, exception)
50         return peers
51
52     try:
53         # DEBUG: print(f"DEBUG: domain='{domain}' is Lemmy, fetching JSON ...")
54         data = network.get_json_api(
55             domain,
56             "/api/v3/site",
57             headers,
58             (config.get("connection_timeout"), config.get("read_timeout"))
59         )
60
61         # DEBUG: print(f"DEBUG: data[]='{type(data)}'")
62         if "error_message" in data:
63             print("WARNING: Could not reach any JSON API:", domain)
64             instances.set_last_error(domain, data)
65         elif "federated_instances" in data["json"]:
66             # DEBUG: print(f"DEBUG: Found federated_instances for domain='{domain}'")
67             peers = peers + federation.add_peers(data["json"]["federated_instances"])
68             # DEBUG: print("DEBUG: Added instance(s) to peers")
69         else:
70             print("WARNING: JSON response does not contain 'federated_instances':", domain)
71             instances.set_last_error(domain, data)
72
73     except network.exceptions as exception:
74         print(f"WARNING: Exception during fetching JSON: domain='{domain}',exception[{type(exception)}]:'{str(exception)}'")
75
76     # DEBUG: print(f"DEBUG: Adding '{len(peers)}' for domain='{domain}'")
77     instances.set_total_peers(domain, peers)
78
79     # DEBUG: print("DEBUG: Returning peers[]:", type(peers))
80     return peers
81
82 def fetch_blocks(domain: str, origin: str, nodeinfo_url: str):
83     # DEBUG: print(f"DEBUG: domain='{domain}',origin='{origin}',nodeinfo_url='{nodeinfo_url}' - CALLED!")
84     if not isinstance(domain, str):
85         raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
86     elif domain == "":
87         raise ValueError("Parameter 'domain' is empty")
88     elif not isinstance(origin, str) and origin is not None:
89         raise ValueError(f"Parameter origin[]='{type(origin)}' is not 'str'")
90     elif origin == "":
91         raise ValueError("Parameter 'origin' is empty")
92     elif not isinstance(nodeinfo_url, str):
93         raise ValueError(f"Parameter nodeinfo_url[]='{type(nodeinfo_url)}' is not 'str'")
94     elif nodeinfo_url == "":
95         raise ValueError("Parameter 'nodeinfo_url' is empty")
96
97     translations = [
98         "blocked instances",
99     ]
100
101     try:
102         # json endpoint for newer mastodongs
103         found_blocks = list()
104         blocklist = list()
105
106         rows = {
107             "reject"        : [],
108             "media_removal" : [],
109             "followers_only": [],
110             "report_removal": [],
111         }
112
113         # DEBUG: print(f"DEBUG: Fetching /instances from domain='{domain}'")
114         response = network.fetch_response(
115             domain,
116             "/instances",
117             network.web_headers,
118             (config.get("connection_timeout"), config.get("read_timeout"))
119         )
120
121         # DEBUG: print(f"DEBUG: response.ok='{response.ok}',response.status_code={response.status_code},response.text()={len(response.text)}")
122         if response.ok and response.status_code < 300 and response.text != "":
123             # DEBUG: print(f"DEBUG: Parsing {len(response.text)} Bytes ...")
124
125             doc = bs4.BeautifulSoup(response.text, "html.parser")
126             # DEBUG: print(f"DEBUG: doc[]={type(doc)}")
127
128             headers = doc.findAll("h5")
129             found = None
130             # DEBUG: print(f"DEBUG: Search in {len(headers)} header(s) ...")
131             for header in headers:
132                 # DEBUG: print(f"DEBUG: header[]={type(header)}")
133                 content = header.contents[0]
134  
135                 # DEBUG: print(f"DEBUG: content='{content}'")
136                 if content.lower() in translations:
137                     # DEBUG: print("DEBUG: Found header with blocked instances - BREAK!")
138                     found = header
139                     break
140
141             # DEBUG: print(f"DEBUG: found[]='{type(found)}'")
142             if found is None:
143                 # DEBUG: print(f"DEBUG: domain='{domain}' is not blocking any instances - EXIT!")
144                 return
145
146             blocking = found.find_next("ul").findAll("a")
147             # DEBUG: print(f"DEBUG: Found {len(blocking)} blocked instance(s) ...")
148             for tag in blocking:
149                 # DEBUG: print(f"DEBUG: tag[]='{type(tag)}'")
150                 blocked = tag.contents[0]
151
152                 # DEBUG: print(f"DEBUG: blocked='{blocked}'")
153                 if not validators.domain(blocked):
154                     # DEBUG: print(f"DEBUG: blocked='{blocked}' is not a valid domain - SKIPPED!")
155                     continue
156                 elif blacklist.is_blacklisted(blocked):
157                     # DEBUG: print(f"DEBUG: blocked='{blocked}' is blacklisted - SKIPPED!")
158                     continue
159                 elif blocked.endswith(".arpa"):
160                     print(f"WARNING: blocked='{blocked}' is a reversed .arpa domain and should not be used generally.")
161                     continue
162                 elif blocked.endswith(".tld"):
163                     print(f"WARNING: blocked='{blocked}' is a fake domain, please don't crawl them!")
164                     continue
165                 elif not instances.is_registered(blocked):
166                     # DEBUG: print("DEBUG: Hash wasn't found, adding:", blocked, domain)
167                     instances.add(blocked, domain, inspect.currentframe().f_code.co_name, nodeinfo_url)
168
169                 if not blocks.is_instance_blocked(domain, blocked, "reject"):
170                     # DEBUG: print("DEBUG: Blocking:", domain, blocked)
171                     blocks.add_instance(domain, blocked, None, "reject")
172
173                     found_blocks.append({
174                         "blocked": blocked,
175                         "reason" : None
176                     })
177                 else:
178                     # DEBUG: print(f"DEBUG: Updating block last seen for domain='{domain}',blocked='{blocked}' ...")
179                     blocks.update_last_seen(domain, blocked, "reject")
180
181         # DEBUG: print("DEBUG: Committing changes ...")
182         fba.connection.commit()
183     except network.exceptions as exception:
184         print(f"ERROR: domain='{domain}',software='mastodon',exception[{type(exception)}]:'{str(exception)}'")
185
186     # DEBUG: print("DEBUG: EXIT!")