#!/usr/bin/python3 # inizio print("Inizio...\n") def exitp(): # funzione uscita print("\nPasso e chiudo...\n") exit() ################################################################################ # moduli ################################################################################ from os.path import dirname, realpath, isfile, sep import json from mastodon import Mastodon from mastodon.Mastodon import MastodonAPIError from datetime import datetime import dateutil.parser from lxml import html ################################################################################ # modalità ################################################################################ user_flag = True #user_flag = False # togliere cancelletto inizio riga per versione admin test_flag = True #test_flag = False # togliere cancelletto inizio riga per versione operativa (solo admin) if user_flag: print("Versione utente! Solo versione di test disponibile...\n") test_flag = True else: print("Versione admin! Maneggiare con cura se non è una versione di test...\n") if test_flag: print("Versione di TEST!\n") else: print("Versione operativa!\n") checkline = "Sì, so quel che faccio." try: rawline = input("Continuare?... Scrivere \"" + checkline + "\"\n") except EOFError: exitp() if rawline != checkline: print("Non vuoi continuare. Va bene!") # esci exitp() ################################################################################ # ausiliari ################################################################################ appname = "mannaggiapp" baseurl = 'https://mastodon.domain.tld' clientcredfname = 'clientcred.secret' usercredfname = 'usercred.secret' scriptdir = dirname(realpath(__file__)) usercredfpath = scriptdir + sep + usercredfname adminmail = 'admin@mail.tld' adminpz = 'passwordveramentebuona' default_following_count = 7 avatar_missing = baseurl + "/avatars/original/missing.png" header_missing = baseurl + "/headers/original/missing.png" def find_anchor(element): if element.text: anchor = element.find("a") if anchor is not None: return True return False ################################################################################ # registro app e token ################################################################################ print("Registro app e token, oppure uso token...") if not isfile(usercredfpath): ############################################################################ # token non registrato ############################################################################ print(" Creo app e token...") Mastodon.create_app( appname, api_base_url = baseurl, to_file = clientcredfname ) mastodon = Mastodon( client_id = clientcredfname, api_base_url = baseurl ) mastodon.log_in( adminmail, adminpz, to_file = usercredfname ) print("Buone notizie! La app \"{}\" è stata registrata!\nOra preoccupati di ***TOGLIERE*** la password dallo script!\nVa bene cambiare il valore di \'adminpz\' con qualsiasi altro valore valido!\nAnche una stringa vuota: letteralmente \"\"".format(appname)) print("Rilancia lo script dopo aver tolto la password.\n") exitp() else: ############################################################################ # token registrato ############################################################################ print(" Uso token...") if adminpz: print("Avviso! Hai tolto la password dallo script?\nSe no, ***TOGLILA***: va bene cambiare il valore di \'adminpz\' con qualsiasi altro valore valido!\nAnche una stringa vuota: letteralmente \"\"\n") mastodon = Mastodon( access_token = usercredfname, api_base_url = baseurl ) ################################################################################ # ottenimento degli accounts ################################################################################ if test_flag: ############################################################################ # ottenimento di account di prova ############################################################################ if isfile("accounts-json.txt"): ######################################################################## # ottenimento di account da file json ######################################################################## print("Ottieni accounts salvati su file json...") with open("accounts-json.txt", "r") as f: lines = f.read() accounts = json.loads(lines) else: ######################################################################## # ottenimento di account da istanza ######################################################################## print("Ottieni accounts nella directory dei profili pubblica...") print(" Seleziona accounts nella directory dei profili (n.b. id salvati su file)") with open("directory-profili.txt","r") as f: lines = f.read() splits = lines.split() accounts = [] print(" Ottieni ogni account tramite la API di Mastodon") print(" N.B.: default di 300 richieste ogni 5 minuti... per 12k accounts sono 3+ ore! Usare interfaccia admin.\nLimite di 270 qui per test.") nsplits = len(splits) fsplits = len(str(nsplits)) for isplit, split in enumerate(splits): fstr = " {:" + str(fsplits) +"d}/{} {}" print(fstr.format(isplit+1, nsplits, split)) try: account = mastodon.account(split) accounts.append(account) except MastodonAPIError as e: pass # account non trovato (sospeso oppure altro) if isplit == 271: break with open("accounts-json.txt", "w") as f: json_str = json.dumps(accounts, default=str) f.write(json_str) print(" Accounts scritti in formato JSON") else: ############################################################################ # ottenimento di tutti gli account di istanza ############################################################################ print("Ottieni tutti gli accounts di istanza...") try: accounts = mastodon.admin_accounts() # tutti gli account locali except MastodonAPIError: print("L'account \"\" non ha l'autorizzazione necessaria per ottenere tutti gli account di istanza") exitp() print("Accounts ottenuti!\n") ################################################################################ # selezione (+ sospensione se non è un test) accounts ################################################################################ for account in accounts: ############################################################################ # aggiusta interfaccia ############################################################################ # stessa interfaccia admin account dict e user account dict if "account" in account: # admin account dict that wraps account dict # https://mastodonpy.readthedocs.io/en/stable/#admin-account-dicts account = account["account"] date_ = account["created_at"] # controllo su data if type(date_) == datetime: date = date_ elif type(date_) == str: date = dateutil.parser.parse(date_) else: raise TypeError( "{} è di tipo {} e non è di un tipo appropriato (stringa o datetime)".format(date, type(date)) ) ############################################################################ # condizioni su account ############################################################################ # casi 0: quando non sospendere # salta: se l'account è creato dopo fine giugno 2020 if date > datetime(2020, 6, 30, 23, 59, 59).astimezone(): # luglio o poi continue # salta: se l'account ha almeno uno status if account["statuses_count"] > 0: # almeno uno status continue # variabili di servizio note = html.fromstring(account["note"]) # biografia del profilo fields = account["fields"] # metadati del profilo avatar = account["avatar"] header = account["header"] ############################################################################ # casi 1+: quando sospendere # caso 1: account "vuoto" # zero metadati, biografia vuota, zero toot, account vecchio if note.text is None and len(fields) == 0: # biografia vuota e zero metadati if account["following_count"] <= default_following_count: continue # salta: si assume no spam tramite seguiti che vanno in federata) #TODO: check contro i seguiti di default (n.b. questi cambiano di volta in volta) if avatar != avatar_missing or header != header_missing: continue # salta: account personalizzato in profilo o intestazione #nei casi restanti: sospendi print(account["url"] + " caso 1") # da sospendere if not test_flag: mastodon.admin_account_moderate(account["id"], action="suspend", send_email_notification=False) continue # salta loop sempre: altri casi sono esclusi (anche in caso di test) ############################################################################ # caso 2: account spam # link nei metadati o biografia, zero toot, account vecchio #TODO: check email, numeri di telefono # link nella biografia if find_anchor(note): print(account["url"] + " caso 2a") # da sospendere if not test_flag: mastodon.admin_account_moderate(account["id"], action="suspend", send_email_notification=False) continue # possono esserci altri valori di metadati con ancore (vs. caso 1) # link nei metadati for field in fields: namestr = field["name"] if namestr: name = html.fromstring(namestr) # link if find_anchor(name): print(account["url"] + " caso 2a1") # da sospendere if not test_flag: mastodon.admin_account_moderate(account["id"], action="suspend", send_email_notification=False) continue # possono esserci altri nomi di metadati con ancore (vs. caso 1) valuestr = field["value"] if valuestr: value = html.fromstring(valuestr) if find_anchor(value): print(account["url"] + " caso 2a2") # da sospendere if not test_flag: mastodon.admin_account_moderate(account["id"], action="suspend", send_email_notification=False) continue # possono esserci altri valori di metadati con ancore (vs. caso 1) ############################################################################ # fine exitp()