]> git.mxchange.org Git - fba.git/blob - fba/networks/lemmy.py
3cd96d1fde64e0fb3db4d0b40c55b30759e07afa
[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 csrf
23 from fba import fba
24
25 from fba.helpers import blacklist
26 from fba.helpers import config
27 from fba.helpers import tidyup
28
29 from fba.http import federation
30 from fba.http import network
31
32 from fba.models import blocks
33 from fba.models import instances
34
35 def fetch_peers(domain: str) -> list:
36     # DEBUG: print(f"DEBUG: domain({len(domain)})='{domain}',software='lemmy' - CALLED!")
37     if not isinstance(domain, str):
38         raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
39     elif domain == "":
40         raise ValueError("Parameter 'domain' is empty")
41     elif domain.lower() != domain:
42         raise ValueError(f"Parameter domain='{domain}' must be all lower-case")
43     elif not validators.domain(domain.split("/")[0]):
44         raise ValueError(f"domain='{domain}' is not a valid domain")
45     elif domain.endswith(".arpa"):
46         raise ValueError(f"domain='{domain}' is a domain for reversed IP addresses, please don't crawl them!")
47     elif domain.endswith(".tld"):
48         raise ValueError(f"domain='{domain}' is a fake domain, please don't crawl them!")
49
50     peers = list()
51
52     # No CSRF by default, you don't have to add network.api_headers by yourself here
53     headers = tuple()
54
55     try:
56         # DEBUG: print(f"DEBUG: Checking CSRF for domain='{domain}'")
57         headers = csrf.determine(domain, dict())
58     except network.exceptions as exception:
59         print(f"WARNING: Exception '{type(exception)}' during checking CSRF (fetch_peers,{__name__}) - EXIT!")
60         instances.set_last_error(domain, exception)
61         return peers
62
63     try:
64         # DEBUG: print(f"DEBUG: domain='{domain}' is Lemmy, fetching JSON ...")
65         data = network.get_json_api(
66             domain,
67             "/api/v3/site",
68             headers,
69             (config.get("connection_timeout"), config.get("read_timeout"))
70         )
71
72         # DEBUG: print(f"DEBUG: data[]='{type(data)}'")
73         if "error_message" in data:
74             print("WARNING: Could not reach any JSON API:", domain)
75             instances.set_last_error(domain, data)
76         elif "federated_instances" in data["json"]:
77             # DEBUG: print(f"DEBUG: Found federated_instances for domain='{domain}'")
78             peers = peers + federation.add_peers(data["json"]["federated_instances"])
79             # DEBUG: print("DEBUG: Added instance(s) to peers")
80         else:
81             print("WARNING: JSON response does not contain 'federated_instances':", domain)
82             instances.set_last_error(domain, data)
83
84     except network.exceptions as exception:
85         print(f"WARNING: Exception during fetching JSON: domain='{domain}',exception[{type(exception)}]:'{str(exception)}'")
86         instances.set_last_error(domain, exception)
87
88     # DEBUG: print(f"DEBUG: Adding '{len(peers)}' for domain='{domain}'")
89     instances.set_total_peers(domain, peers)
90
91     # DEBUG: print("DEBUG: Returning peers[]:", type(peers))
92     return peers
93
94 def fetch_blocks(domain: str, origin: str, nodeinfo_url: str):
95     # DEBUG: print(f"DEBUG: domain='{domain}',origin='{origin}',nodeinfo_url='{nodeinfo_url}' - CALLED!")
96     if not isinstance(domain, str):
97         raise ValueError(f"Parameter domain[]='{type(domain)}' is not 'str'")
98     elif domain == "":
99         raise ValueError("Parameter 'domain' is empty")
100     elif domain.lower() != domain:
101         raise ValueError(f"Parameter domain='{domain}' must be all lower-case")
102     elif not validators.domain(domain.split("/")[0]):
103         raise ValueError(f"domain='{domain}' is not a valid domain")
104     elif domain.endswith(".arpa"):
105         raise ValueError(f"domain='{domain}' is a domain for reversed IP addresses, please don't crawl them!")
106     elif domain.endswith(".tld"):
107         raise ValueError(f"domain='{domain}' is a fake domain, please don't crawl them!")
108     elif not isinstance(origin, str) and origin is not None:
109         raise ValueError(f"Parameter origin[]='{type(origin)}' is not 'str'")
110     elif origin == "":
111         raise ValueError("Parameter 'origin' is empty")
112     elif not isinstance(nodeinfo_url, str):
113         raise ValueError(f"Parameter nodeinfo_url[]='{type(nodeinfo_url)}' is not 'str'")
114     elif nodeinfo_url == "":
115         raise ValueError("Parameter 'nodeinfo_url' is empty")
116
117     translations = [
118         "Blocked Instances",
119         "Instàncies bloquejades",
120         "Blocáilte Ásc",
121         "封锁实例",
122         "Blokované instance",
123         "Geblokkeerde instanties",
124         "Blockerade instanser",
125         "Instàncias blocadas",
126         "Istanze bloccate",
127         "Instances bloquées",
128         "Letiltott példányok",
129         "Instancias bloqueadas",
130         "Blokeatuta dauden instantziak",
131         "차단된 인스턴스",
132         "Peladen Yang Diblokir",
133         "Blokerede servere",
134         "Blokitaj nodoj",
135         "Блокирани Инстанции",
136         "Blockierte Instanzen",
137         "Estetyt instanssit",
138         "Instâncias bloqueadas",
139         "Zablokowane instancje",
140         "Blokované inštancie",
141         "المثلاء المحجوبون",
142         "Užblokuoti serveriai",
143         "ブロックしたインスタンス",
144         "Блокированные Инстансы",
145         "Αποκλεισμένοι διακομιστές",
146         "封鎖站台",
147         "Instâncias bloqueadas",
148     ]
149
150     try:
151         # json endpoint for newer mastodongs
152         found_blocks = list()
153         blocklist = list()
154
155         rows = {
156             "reject"        : [],
157             "media_removal" : [],
158             "followers_only": [],
159             "report_removal": [],
160         }
161
162         # DEBUG: print(f"DEBUG: Fetching /instances from domain='{domain}'")
163         response = network.fetch_response(
164             domain,
165             "/instances",
166             network.web_headers,
167             (config.get("connection_timeout"), config.get("read_timeout"))
168         )
169
170         # DEBUG: print(f"DEBUG: response.ok='{response.ok}',response.status_code={response.status_code},response.text()={len(response.text)}")
171         if response.ok and response.status_code < 300 and response.text != "":
172             # DEBUG: print(f"DEBUG: Parsing {len(response.text)} Bytes ...")
173
174             doc = bs4.BeautifulSoup(response.text, "html.parser")
175             # DEBUG: print(f"DEBUG: doc[]={type(doc)}")
176
177             headers = doc.findAll("h5")
178             found = None
179             # DEBUG: print(f"DEBUG: Search in {len(headers)} header(s) ...")
180             for header in headers:
181                 # DEBUG: print(f"DEBUG: header[]={type(header)}")
182                 content = header.contents[0]
183
184                 # DEBUG: print(f"DEBUG: content='{content}'")
185                 if content in translations:
186                     # DEBUG: print("DEBUG: Found header with blocked instances - BREAK!")
187                     found = header
188                     break
189
190             # DEBUG: print(f"DEBUG: found[]='{type(found)}'")
191             if found is None:
192                 # DEBUG: print(f"DEBUG: domain='{domain}' is not blocking any instances - EXIT!")
193                 return
194
195             blocking = found.find_next("ul").findAll("a")
196             # DEBUG: print(f"DEBUG: Found {len(blocking)} blocked instance(s) ...")
197             for tag in blocking:
198                 # DEBUG: print(f"DEBUG: tag[]='{type(tag)}'")
199                 blocked = tidyup.domain(tag.contents[0])
200
201                 # DEBUG: print(f"DEBUG: blocked='{blocked}'")
202                 if not validators.domain(blocked):
203                     print(f"WARNING: blocked='{blocked}' is not a valid domain - SKIPPED!")
204                     continue
205                 elif blocked.endswith(".arpa"):
206                     print(f"WARNING: blocked='{blocked}' is a reversed .arpa domain and should not be used generally.")
207                     continue
208                 elif blocked.endswith(".tld"):
209                     print(f"WARNING: blocked='{blocked}' is a fake domain, please don't crawl them!")
210                     continue
211                 elif blacklist.is_blacklisted(blocked):
212                     # DEBUG: print(f"DEBUG: blocked='{blocked}' is blacklisted - SKIPPED!")
213                     continue
214                 elif not instances.is_registered(blocked):
215                     # DEBUG: print("DEBUG: Hash wasn't found, adding:", blocked, domain)
216                     instances.add(blocked, domain, inspect.currentframe().f_code.co_name, nodeinfo_url)
217
218                 if not blocks.is_instance_blocked(domain, blocked, "reject"):
219                     # DEBUG: print("DEBUG: Blocking:", domain, blocked)
220                     blocks.add_instance(domain, blocked, None, "reject")
221
222                     found_blocks.append({
223                         "blocked": blocked,
224                         "reason" : None
225                     })
226                 else:
227                     # DEBUG: print(f"DEBUG: Updating block last seen for domain='{domain}',blocked='{blocked}' ...")
228                     blocks.update_last_seen(domain, blocked, "reject")
229
230         # DEBUG: print("DEBUG: Committing changes ...")
231         fba.connection.commit()
232     except network.exceptions as exception:
233         print(f"ERROR: domain='{domain}',software='mastodon',exception[{type(exception)}]:'{str(exception)}'")
234         instances.set_last_error(domain, exception)
235
236     # DEBUG: print("DEBUG: EXIT!")