253 lines
11 KiB
Python
253 lines
11 KiB
Python
|
#!/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()
|
||
|
|