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/>.
21 from fba import blacklist
22 from fba import config
23 from fba import instances
24 from fba import network
26 from fba.helpers import dicts
27 from fba.helpers import tidyup
29 def fetch_peers(domain: str) -> list:
30 # DEBUG: print(f"DEBUG: domain({len(domain)})={domain} - CALLED!")
31 if not isinstance(domain, str):
32 raise ValueError(f"Parameter domain[]={type(domain)} is not 'str'")
34 raise ValueError("Parameter 'domain' is empty")
36 # DEBUG: print(f"DEBUG: domain='{domain}' is misskey, sending API POST request ...")
39 step = config.get("misskey_limit")
41 # iterating through all "suspended" (follow-only in its terminology)
42 # instances page-by-page, since that troonware doesn't support
43 # sending them all at once
45 # DEBUG: print(f"DEBUG: Fetching offset='{offset}' from '{domain}' ...")
47 fetched = network.post_json_api(domain, "/api/federation/instances", json.dumps({
55 fetched = network.post_json_api(domain, "/api/federation/instances", json.dumps({
64 # DEBUG: print(f"DEBUG: fetched()={len(fetched)}")
66 # DEBUG: print(f"DEBUG: Returned zero bytes, exiting loop, domain='{domain}'")
68 elif len(fetched) != config.get("misskey_limit"):
69 # DEBUG: print(f"DEBUG: Fetched '{len(fetched)}' row(s) but expected: '{config.get('misskey_limit')}'")
70 offset = offset + (config.get("misskey_limit") - len(fetched))
72 # DEBUG: print(f"DEBUG: Raising offset by step={step}")
73 offset = offset + step
76 # DEBUG: print(f"DEBUG: fetched({len(fetched)})[]={type(fetched)}")
77 if isinstance(fetched, dict) and "error" in fetched and "message" in fetched["error"]:
78 print(f"WARNING: post_json_api() returned error: {fetched['error']['message']}")
79 instances.update_last_error(domain, fetched["error"]["message"])
81 elif "error_message" in fetched:
82 print(f"WARNING: post_json_api() for domain='{domain}' returned error message: {fetched['error_message']}")
83 instances.update_last_error(domain, fetched)
87 for row in fetched["json"]:
88 # DEBUG: print(f"DEBUG: row()={len(row)}")
90 print(f"WARNING: row()={len(row)} does not contain key 'host': {row},domain='{domain}'")
92 elif not isinstance(row["host"], str):
93 print(f"WARNING: row[host][]={type(row['host'])} is not 'str'")
95 elif blacklist.is_blacklisted(row["host"]):
96 # DEBUG: print(f"DEBUG: row[host]='{row['host']}' is blacklisted. domain='{domain}'")
98 elif row["host"] in peers:
99 # DEBUG: print(f"DEBUG: Not adding row[host]='{row['host']}', already found.")
100 already = already + 1
103 # DEBUG: print(f"DEBUG: Adding peer: '{row['host']}'")
104 peers.append(row["host"])
106 if already == len(fetched):
107 # DEBUG: print(f"DEBUG: Host returned same set of '{already}' instances, aborting loop!")
110 # DEBUG: print(f"DEBUG: Adding '{len(peers)}' for domain='{domain}'")
111 instances.set_data("total_peers", domain, len(peers))
113 # DEBUG: print(f"DEBUG: Updating last_instance_fetch for domain='{domain}' ...")
114 instances.update_last_instance_fetch(domain)
116 # DEBUG: print(f"DEBUG: Returning peers[]='{type(peers)}'")
119 def fetch_blocks(domain: str) -> dict:
120 # DEBUG: print(f"DEBUG: domain='{domain}' - CALLED!")
121 if not isinstance(domain, str):
122 raise ValueError(f"Parameter domain[]={type(domain)} is not 'str'")
124 raise ValueError("Parameter 'domain' is empty")
126 # DEBUG: print("DEBUG: Fetching misskey blocks from domain:", domain)
133 step = config.get("misskey_limit")
135 # iterating through all "suspended" (follow-only in its terminology)
136 # instances page-by-page, since that troonware doesn't support
137 # sending them all at once
139 # DEBUG: print(f"DEBUG: Fetching offset='{offset}' from '{domain}' ...")
141 # DEBUG: print("DEBUG: Sending JSON API request to domain,step,offset:", domain, step, offset)
142 fetched = network.post_json_api(domain, "/api/federation/instances", json.dumps({
151 # DEBUG: print("DEBUG: Sending JSON API request to domain,step,offset:", domain, step, offset)
152 fetched = network.post_json_api(domain, "/api/federation/instances", json.dumps({
157 "offset" : offset - 1
162 # DEBUG: print("DEBUG: fetched():", len(fetched))
163 if len(fetched) == 0:
164 # DEBUG: print("DEBUG: Returned zero bytes, exiting loop:", domain)
166 elif len(fetched) != config.get("misskey_limit"):
167 # DEBUG: print(f"DEBUG: Fetched '{len(fetched)}' row(s) but expected: '{config.get('misskey_limit')}'")
168 offset = offset + (config.get("misskey_limit") - len(fetched))
170 # DEBUG: print("DEBUG: Raising offset by step:", step)
171 offset = offset + step
174 for instance in fetched:
176 if instance["isSuspended"] and not dicts.has_key(blocklist["suspended"], "domain", instance):
178 blocklist["suspended"].append(
180 "domain": tidyup.domain(instance["host"]),
181 # no reason field, nothing
186 # DEBUG: print(f"DEBUG: count={count}")
188 # DEBUG: print("DEBUG: API is no more returning new instances, aborting loop!")
191 except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as exception:
192 print(f"WARNING: Caught error, exiting loop: domain='{domain}',exception[{type(exception)}]='{str(exception)}'")
193 instances.update_last_error(domain, exception)
198 # same shit, different asshole ("blocked" aka full suspend)
201 # DEBUG: print("DEBUG: Sending JSON API request to domain,step,offset:", domain, step, offset)
202 fetched = network.post_json_api(domain, "/api/federation/instances", json.dumps({
211 # DEBUG: print("DEBUG: Sending JSON API request to domain,step,offset:", domain, step, offset)
212 fetched = network.post_json_api(domain, "/api/federation/instances", json.dumps({
217 "offset" : offset - 1
222 # DEBUG: print("DEBUG: fetched():", len(fetched))
223 if len(fetched) == 0:
224 # DEBUG: print("DEBUG: Returned zero bytes, exiting loop:", domain)
226 elif len(fetched) != config.get("misskey_limit"):
227 # DEBUG: print(f"DEBUG: Fetched '{len(fetched)}' row(s) but expected: '{config.get('misskey_limit')}'")
228 offset = offset + (config.get("misskey_limit") - len(fetched))
230 # DEBUG: print("DEBUG: Raising offset by step:", step)
231 offset = offset + step
234 for instance in fetched:
236 if instance["isBlocked"] and not dicts.has_key(blocklist["blocked"], "domain", instance):
238 blocklist["blocked"].append({
239 "domain": tidyup.domain(instance["host"]),
243 # DEBUG: print(f"DEBUG: count={count}")
245 # DEBUG: print("DEBUG: API is no more returning new instances, aborting loop!")
248 except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as exception:
249 print(f"WARNING: Caught error, exiting loop: domain='{domain}',exception[{type(exception)}]='{str(exception)}'")
250 instances.update_last_error(domain, exception)
254 # DEBUG: print(f"DEBUG: Updating last_instance_fetch for domain='{domain}' ...")
255 instances.update_last_instance_fetch(domain)
257 # DEBUG: print(f"DEBUG: Returning for domain='{domain}',blocked()={len(blocklist['blocked'])},suspended()={len(blocklist['suspended'])}")
259 "reject" : blocklist["blocked"],
260 "followers_only": blocklist["suspended"]