1 # Fedi API Block - An aggregator for fetching blocking data from fediverse nodes
2 # Copyright (C) 2023 Free Software Foundation
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.
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.
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/>.
24 from fba.helpers import config
25 from fba.helpers import domain as domain_helper
26 from fba.helpers import tidyup
28 from fba.http import federation
29 from fba.http import network
31 from fba.models import instances
33 logging.basicConfig(level=logging.INFO)
34 logger = logging.getLogger(__name__)
35 #logger.setLevel(logging.DEBUG)
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)
43 # No CSRF by default, you don't have to add network.api_headers by yourself here
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)
55 logger.debug("Fetching '/api/v3/site' from domain='%s' ...", domain)
56 data = network.get_json_api(
60 (config.get("connection_timeout"), config.get("read_timeout"))
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)
72 logger.warning("JSON response does not contain 'federated_instances', domain='%s' - trying /instances ...", domain)
73 peers = fetch_instances(domain, origin)
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)
79 logger.debug("peers()=%d - EXIT!", len(peers))
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)
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")
92 "Blocked Instances".lower(),
93 "Instàncies bloquejades".lower(),
94 "Blocáilte Ásc".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(),
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(),
121 "Instâncias bloqueadas".lower(),
127 # json endpoint for newer mastodongs
128 logger.debug("Fetching /instances from domain='%s'", domain)
129 response = network.fetch_response(
133 (config.get("connection_timeout"), config.get("read_timeout"))
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))
140 doc = bs4.BeautifulSoup(response.text, "html.parser")
141 logger.debug("doc[]='%s'", type(doc))
143 headers = doc.findAll("h5")
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]
150 logger.debug("content[%s]='%s'", type(content), content)
152 logger.debug("domain='%s' has returned empty header='%s' - SKIPPED!", domain, header)
154 elif content.lower() in translations:
155 logger.debug("Found header with blocked instances - BREAK!")
159 logger.debug("found[]='%s'", type(found))
161 logger.debug("domain='%s' is not blocking any instances - EXIT!", domain)
164 blocking = found.find_next(["ul","table"]).findAll("a")
165 logger.debug("Found %d blocked instance(s) ...", len(blocking))
167 logger.debug("tag[]='%s'", type(tag))
168 blocked = tidyup.domain(tag.contents[0])
169 logger.debug("blocked='%s'", blocked)
171 if not utils.is_domain_wanted(blocked):
172 logger.debug("blocked='%s' is not wanted - SKIPPED!", blocked)
175 logger.debug("Appending blocker='%s',blocked='%s',block_level='reject'", domain, blocked)
180 "block_level": "reject",
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)
187 logger.debug("blocklist()=%d - EXIT!", len(blocklist))
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)
197 # json endpoint for newer mastodongs
198 logger.debug("Fetching /instances from domain='%s'", domain)
199 response = network.fetch_response(
203 (config.get("connection_timeout"), config.get("read_timeout"))
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))
210 doc = bs4.BeautifulSoup(response.text, "html.parser")
211 logger.debug("doc[]='%s'", type(doc))
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)
218 rows = header.find_next(["ul","table"]).findAll("a")
219 logger.debug("Found %d blocked instance(s) ...", len(rows))
221 logger.debug("tag[]='%s'", type(tag))
222 peer = tidyup.domain(tag.contents[0])
223 logger.debug("peer='%s'", peer)
226 logger.debug("peer is empty - SKIPPED!")
228 elif not utils.is_domain_wanted(peer):
229 logger.debug("peer='%s' is not wanted - SKIPPED!", peer)
232 logger.debug("peer='%s' already added - SKIPPED!", peer)
235 logger.debug("Appending peer='%s' ...", peer)
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)
242 logger.debug("peers()=%d - EXIT!", len(peers))