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