def _resetpass(self, update: Update, context: CallbackContext): log_command(update) if not check_auth(update): return bc.config.telegram["passphrase"] = uuid.uuid4().hex log.warning("New passphrase: " + bc.config.telegram["passphrase"]) reply(update, 'Passphrase has been reset!')
def optional_numba_jit(func) -> Any: try: numba = importlib.import_module("numba") return numba.njit(fastmath=True)(func) except ImportError: log.warning(f"Function {func.__name__} is missing numba package for better performance!") return func
def _run(self, args) -> None: while True: if bc is None or bc.secret_config is None: time.sleep(2) else: break if bc.secret_config.telegram["token"] is None: log.warning("Telegram backend is not configured. Missing token in secret config") return log.info("Starting Telegram instance...") updater = Updater(bc.secret_config.telegram["token"]) builtin_cmds = BuiltinCommands() builtin_cmds.add_handlers(updater.dispatcher) reminder_cmds = ReminderCommands() reminder_cmds.add_handlers(updater.dispatcher) auth_cmds = AuthCommands() auth_cmds.add_handlers(updater.dispatcher) updater.dispatcher.add_handler( MessageHandler( Filters.text & ~Filters.command & Filters.entity(MessageEntity.MENTION), self._handle_mentions)) updater.dispatcher.add_handler( MessageHandler( Filters.text & ~Filters.command & ~Filters.entity(MessageEntity.MENTION), self._handle_messages)) log.info("Telegram instance is started!") updater.start_polling() while True: time.sleep(1) if self._is_stopping: log.info("Stopping Telegram instance...") updater.stop() log.info("Telegram instance is stopped!") break
def check_updates(context: AutoUpdateContext) -> bool: """Function that performs updates check. It is called periodically""" old_sha = context.repo.head.object.hexsha try: context.repo.remotes.origin.fetch() except Exception as e: return log.error( f"Fetch failed: {e}. Skipping this cycle, will try to update on the next one" ) new_sha = context.repo.remotes.origin.refs['master'].object.name_rev.split( )[0] log.debug(f"{old_sha} {new_sha}") if old_sha == new_sha: return log.debug("No new updates") bot_cache = importlib.import_module("src.bot_cache").BotCache(True).parse() if bot_cache is None: return log.warning( "Could not read bot cache. Skipping this cycle, will try to update on the next one" ) if "do_not_update" not in bot_cache.keys(): return log.warning( "Could not find 'do_not_update' field in bot cache. " "Skipping this cycle, will try to update on the next one") if bot_cache["do_not_update"]: return log.debug( "Automatic update is not permitted. Skipping this cycle, will try to update on the next one" ) context.repo.git.reset("--hard") try: g = git.cmd.Git(os.getcwd()) g.pull() except git.exc.GitCommandError as e: if "Connection timed out" in e.stderr or "Could not resolve host" in e.stderr: log.warning(f"{e.command}: {e.stderr}") else: raise e subprocess.call(f"{sys.executable} -m pip install -r requirements.txt", shell=True) minibot_response = "WalBot automatic update is in progress. Please, wait..." subprocess.call( f"{sys.executable} walbot.py startmini --message '{minibot_response}' --nohup &", shell=True) subprocess.call(f"{sys.executable} walbot.py stop", shell=True) if context.check_versions(): subprocess.call(f"{sys.executable} walbot.py patch", shell=True) subprocess.call(f"{sys.executable} walbot.py start --fast_start --nohup &", shell=True) while True: time.sleep(1) bot_cache = importlib.import_module("src.bot_cache").BotCache( True).parse() if bot_cache is not None and bot_cache["ready"]: subprocess.call(f"{sys.executable} walbot.py stopmini", shell=True) log.info("Bot is fully loaded. MiniWalBot is stopped.") break log.debug("Bot is not fully loaded yet. Waiting...") return True
def check_version(name, actual, expected, solutions=None, fatal=True): if actual == expected: return True if not fatal: log.warning( f"{name} versions mismatch. Expected: {expected}, but actual: {actual}" ) else: log.error( f"{name} versions mismatch. Expected: {expected}, but actual: {actual}" ) if solutions: log.info("Possible solutions:") for solution in solutions: log.info(f" - {solution}") return not fatal
def parse(self) -> Optional[Dict]: if not os.path.exists(self.path): return cache = None for _ in range(10): try: with open(self.path, 'r') as f: cache = json.load(f) if cache is not None: if "pid" not in cache or not psutil.pid_exists(int(cache["pid"])): log.warning("Could validate pid from .bot_cache") os.remove(self.path) return return cache except json.decoder.JSONDecodeError: time.sleep(0.5)
def configure_iptables(init, args=None): """Sets iptables rules and ip forwarding according to KISS configuration.""" if platform == "linux": #platform is from scapy os.system("iptables --flush") if init: arps_activated = (args.A_ENABLED or args.N_PASSIVE_ARPS_EVERYONE) if arps_activated: os.system("echo 1 > /proc/sys/net/ipv4/ip_forward") if args.D_ENABLED: os.system("iptables -A FORWARD -p udp --dport 53 -j DROP") os.system( "iptables -A FORWARD -p tcp --dport 80 -m string --string 'GET' --algo bm -j DROP" ) #os.system("iptables -A FORWARD -p tcp --dport 80 -m string --string 'POST' --algo bm -m string --string 'GET' --algo bm -j DROP") if args.J_ENABLED: os.system( "iptables -A FORWARD -p tcp --sport 80 -m string --string 'ype: text/html' --algo bm -j DROP" ) log.info("CONFIG", "Iptables have been established.") else: os.system("echo 0 > /proc/sys/net/ipv4/ip_forward") log.info("CONFIG", "Iptables have been cleared.") else: if init: arps_activated = (args.A_ENABLED or args.N_PASSIVE_ARPS_EVERYONE) if arps_activated: log.warning( None, "Make sure Routing and Remote Access Service is activated." ) if args.D_ENABLED: log.warning( None, "Windows is not supported due to the lack of iptables. DNS Spoofing will probably not work correctly." )
async def wrapped(*args, **kwargs): try: return await func(*args, **kwargs) except Exception as e: try: bot_info = bc.info.get_full_info(2) except Exception: log.warning("Failed to get bot info to attach to e-mail", exc_info=True) bot_info = "ERROR: Failed to retrieve details, please refer to log file" if bc.secret_config.admin_email_list: mail = Mail(bc.secret_config) mail.send( bc.secret_config.admin_email_list, f"WalBot (instance: {bc.instance_name}) {func.__name__} failed", f"{func.__name__} failed:\n" f"{e}\n" "\n" f"Backtrace:\n" f"{traceback.format_exc()}\n" f"Details:\n" f"{bot_info}") log.error(f"{func.__name__} failed", exc_info=True)
def check_args(self): """Checks missing, empty and illogical attributes, and disables a module if its config is not correct, logging a warning or an error. """ if "D34D" in self.__dict__.values(): #si alguno de los atributos de la clase args contiene D34D, es porque no se ha encontrado alguno de los args log.error(None, "Missing conf parameters in config file. Leaving...") sys.exit() #PARTICULARES #NET if self.N_ENABLED and not (self.N_ACTIVE or self.N_PASSIVE): log.warning( None, "Net Analyzer is enabled, but mode was not chosen. Please enable active mode, passive mode or both. Disabling Net Analyzer..." ) self.N_ENABLED = False if self.N_ENABLED and not packet_utilities.is_it_an_ip( self.N_GATEWAY_IP): log.warning( None, "Gateway IP Address of module Net Analyzer is not valid. Disabling Net Analyzer..." ) self.N_ENABLED = False #SNIFF if self.S_ENABLED and self.S_ATTRIBUTES != "*": try: f = open(self.S_ATTRIBUTES) f.close() except FileNotFoundError: log.warning(None, "File", self.S_ATTRIBUTES, "could not be found. Disabling Sniffer...") self.S_ENABLED = False #ARPS if self.A_ENABLED and (not self.A_TARGET_IP or not self.A_GATEWAY_IP or not self.A_INTERVAL): #si algun valor esta vacio... solo TIME_SECS y DISCONNECT pueden estar vacios o ser None/False log.warning( None, "Missing ARPS parameters in config file. Disabling ARPS...") self.A_ENABLED = False if self.A_ENABLED and self.A_INTERVAL == 0: log.warning(None, "ARPS interval can't be 0. Setting to 1...") self.A_INTERVAL = 1 if self.A_ENABLED and not packet_utilities.is_it_an_ip( self.A_GATEWAY_IP): log.warning( None, "Gateway IP Address of module ARP Spoofing is not valid. Disabling ARP Spoofing..." ) self.A_ENABLED = False if self.A_ENABLED and (not packet_utilities.is_it_an_ip( self.A_TARGET_IP) and self.A_TARGET_IP != "everyone"): log.warning( None, "Target IP Address of module ARP Spoofing is not valid. Disabling ARP Spoofing..." ) self.A_ENABLED = False #DNS if self.D_ENABLED and not self.D_FILE: log.warning( None, "Missing DNS parameters in config file. Disabling DNS Spoofer..." ) self.D_ENABLED = False if self.D_ENABLED: try: f = open(self.D_FILE) f.close() except FileNotFoundError: log.warning(None, "File", self.D_FILE, "could not be found. Disabling DNS Spoofer...") self.D_ENABLED = False #JS if self.J_ENABLED and (not self.J_FILE or not self.J_TARGET_IP): log.warning( None, "Missing JS parameters in config file. Disabling JS Injecter..." ) self.J_ENABLED = False #NO PARTICULARES if self.D_ENABLED and not self.A_ENABLED: #dns sin arps esta feo log.warning( None, "DNS Spoofing can't be done without ARP Spoofing. Please enable ARPS. Disabling DNS..." ) self.D_ENABLED = False if self.S_ENABLED and not self.A_ENABLED: log.warning( None, "ARP Spoofing is disabled! Only packets from the local machine will be sniffed. You can activate it in the config file." ) if self.J_ENABLED and not self.A_ENABLED: #JS sin arps esta feo log.warning( None, "JS Injecting can't be done without ARP Spoofing. Please enable ARPS. Disabling JS..." ) self.J_ENABLED = False if not self.N_ENABLED and not self.S_ENABLED and not self.A_ENABLED and not self.D_ENABLED and not self.U_ENABLED and not self.J_ENABLED: log.error(None, "No module is enabled. Leaving...") sys.exit() log.info("CONFIG", "Config file is OK!")
def send_note(entity, argument, command_name, note_type, note_singular, note_plural, note_class, grammar_genre): if not entity: log.bug("entity non è un parametro valido: %r" % entity) return False # argument può essere una stringa vuota if not command_name: log.bug("command_name non è un parametro valido: %r" % command_name) return False if not note_type: log.bug("note_type non è un parametro valido: %r" % note_type) return False if not note_singular: log.bug("note_singular non è un parametro valido: %r" % note_singular) return False if not note_class: log.bug("note_class non è un parametro valido: %r" % note_class) return False if not grammar_genre: log.bug("grammar_genre non è un parametro valido: %r" % grammar_genre) return False # ------------------------------------------------------------------------- if not argument: syntax = get_command_syntax(entity, command_name) entity.send_output(syntax, break_line=False) return False if not entity.IS_PLAYER: entity.send_output( "Solo i giocatori possono inviare %s." % (add_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR))) return False if config.max_account_typos == 0: entity.send_output( "L'invio %s è stata disabilitata." % (add_article(note_plural, grammar_genre, GRAMMAR.INDETERMINATE, GRAMMAR.PLURAL))) return False # Non dovrebbe mai capitare, ma non si sa mai, in teoria comunque non è un baco if not entity.account: entity.send_output( "La sessione del tuo account è scaduta, devi riaccedere nuovamente al sito per poter inviare %s" % (add_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR))) return False # Anche questa non dovrebbe mai capitare if not entity.location: entity.send_output( "Ti è impossibile inviare %s da questo luogo." % (add_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR))) log.bug( "entity.location non è valido (%r) per entity %s mentre stava cercando di inviare una nota %s" % (entity.location, entity.code, note_type)) return False # Crea il codice per la nota code_to_check = entity.account.name + "#" sended_notes = getattr(entity.account, "sended_%ss" % note_type) code = code_to_check + str(sended_notes) # Se il codice già esiste nel database probabilmente c'è una dicrepanza # tra il dato di account e quelli delle note quindi il codice si # incrementa fino a trovare un numero libero counter = 1 while code in database[note_type + "s"]: code = code_to_check + str(sended_notes + counter) setattr(entity.account, "sended_%ss" % note_type, sended_notes + counter + 1) counter += 1 who = entity.code if entity.IS_PLAYER and entity.account: who += " dell'account %s" % entity.account.name if entity.location.IS_ROOM: where = "%s (%r)" % (entity.location.code, entity.location.get_destination()) else: where = "%s in %r " % (entity.location.get_name(), entity.get_in_room.get_destination()) when = "%s (%s %s %s %s)" % (datetime.datetime.now(), calendar.minute, calendar.hour, calendar.day, calendar.month) note = note_class(code, who, where, when, argument) if not note: log.bug("note non è valido (%r) per la tipologia %s" % (note, note_type)) entity.send_output( "Impossibile segnalare un%s nuov%s %s." % ("" if grammar_genre == GRAMMAR.MASCULINE else "a", "o" if grammar_genre == GRAMMAR.MASCULINE else "a", note_singular)) return False # Evitare di inviare una nota subito dopo averne inviata un'altra, # così da evitare eventuali spammer last_note_sended_at = getattr(entity.account, "last_%s_sended_at" % note_type) if (datetime.datetime.now() - last_note_sended_at).days <= 0: # (TD) Python 2.7, così non servirà più il check su days #total_seconds = (datetime.datetime.now() - last_note_sended_at).total_seconds() total_seconds = (datetime.datetime.now() - last_note_sended_at).seconds if total_seconds < config.sending_interval: remaining_seconds = config.sending_interval - total_seconds random_id = random.random() entity.send_output( '''Potrai inviare %s solo tra <span id="%s">%d %s</span><script>parent.countdown("%s");</script>''' % (add_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR, GRAMMAR.POSSESSIVE), random_id, remaining_seconds, "secondo" if remaining_seconds == 1 else "secondi", random_id)) return False # Inserisce un tetto massimo di note ancora aperte inviabili contando se # effettivamente le note dell'account ancora aperte sono così tante counter = 0 for note_code in database[note_type + "s"]: if note_code.startswith(code_to_check): counter += 1 if counter >= getattr(config, "max_account_%ss" % note_type): entity.send_output( "Hai raggiunto il massimo di %s attualmente segnalabili, riprova tra qualche minuto." % (note_plural, config.game_name)) log.monitor( "%s ha raggiunto il numero massimo di %s inviabili, controllare e chiudere quelle obsolete cosicché possa inviarne delle altre.", (entity.code, note_plural)) return False # Controlla che le altre note non abbiano lo stesso testo, altrimenti ci # troviamo davanti ad un possibile spammer, il sistema quindi fa finta # di salvarsi la nota e intanto segnala agli Amministratori lo spammer for other_note in database[note_type + "s"].itervalues(): if other_note.text == argument and other_note.code.startswith( code_to_check): entity.send_output("%s è stato salvato. Grazie!" % (add_article( note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR, GRAMMAR.POSSESSIVE).capitalize())) log.warning( "Qualcuno per sbaglio invia due volte la stessa nota %s. Se continua esageratemente è un possibile spammer (%s)" % (note_singular, entity.get_conn().get_id())) return False database[note_type + "s"][note.code] = note # Le note sono uno di quei dati slegati dal gioco che possono essere # scritti subito sul disco vista la relativa rarità del suo invio e quindi # la bassa probabilità che vada ad inficiare sulle prestazioni globali note_path = "data/%ss/%s.dat" % (note_type, note.code) try: note_file = open(note_path, "w") except IOError: entity.send_output( "Per un errore interno al server è impossibile salvare %s." % (add_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR, GRAMMAR.POSSESSIVE))) log.bug("Impossibile aprire il file %s in scrittura" % note_path) return False fwrite(note_file, note) note_file.close() # Poiché è stato salvata la nota bisogna anche salvare l'account che # contiene il numero di note inviate da sempre incrementato giusto qui # (TT) Bisogna fare attenzione al salvataggio facile dell'account, # potrebbero in futuro esservi informazioni duplicate o erroneamente # incrementate (dopo un crash) a seconda delle dipendenze tra i dati # nell'account ed altre tipologie di dati che non verrebbero salvate # se non alla chisura del Mud setattr(entity.account, "sended_%ss" % note_type, sended_notes + 1) setattr(entity.account, "last_%s_sended_at" % note_type, datetime.datetime.now()) account_path = "persistence/accounts/%s.dat" % entity.account.name try: account_file = open(account_path, "w") except IOError: entity.send_output( "Per un errore interno al server è impossibile salvare %s." % (add_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR, GRAMMAR.POSSESSIVE))) log.bug("Impossibile aprire il file %s in scrittura" % account_path) return False fwrite(account_file, entity.account) account_file.close() entity.send_output("Hai appena segnalato %s%d° %s. Grazie!" % (get_article( note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR, GRAMMAR.POSSESSIVE), sended_notes + 1, note_singular)) # Invia la mail subject = "Invio %s" % note_singular text = "L'account %s ha appena segnalato %s%d° %s." % ( entity.account.name, get_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR), sended_notes + 1, note_singular) text += "\nWho: %s" % note.who text += "\nWhere: %s" % note.where text += "\nWhen: %s" % note.when text += "\nText: %s" % note.text mail.send_to_admins(subject, text, show_players=False) return True
def start(self, args, main_bot=True): # Check whether bot is already running bot_cache = BotCache(main_bot).parse() if bot_cache is not None: pid = bot_cache["pid"] if pid is not None and psutil.pid_exists(pid): return log.error("Bot is already running!") # Some variable initializations config = None secret_config = None bc.restart_flag = False bc.args = args # Handle --nohup flag if sys.platform in ("linux", "darwin") and args.nohup: fd = os.open(const.NOHUP_FILE_PATH, os.O_WRONLY | os.O_CREAT | os.O_APPEND) log.info(f"Output is redirected to {const.NOHUP_FILE_PATH}") os.dup2(fd, sys.stdout.fileno()) os.dup2(sys.stdout.fileno(), sys.stderr.fileno()) os.close(fd) signal.signal(signal.SIGHUP, signal.SIG_IGN) # Selecting YAML parser bc.yaml_loader, bc.yaml_dumper = Util.get_yaml(verbose=True) # Saving application pd in order to safely stop it later BotCache(main_bot).dump_to_file() # Executing patch tool if it is necessary if args.patch: cmd = f"'{sys.executable}' '{os.path.dirname(__file__) + '/../tools/patch.py'}' all" log.info("Executing patch tool: " + cmd) subprocess.call(cmd) # Read configuration files config = Util.read_config_file(const.CONFIG_PATH) if config is None: config = Config() secret_config = Util.read_config_file(const.SECRET_CONFIG_PATH) if secret_config is None: secret_config = SecretConfig() bc.markov = Util.read_config_file(const.MARKOV_PATH) if bc.markov is None and os.path.isdir("backup"): # Check available backups markov_backups = sorted([ x for x in os.listdir("backup") if x.startswith("markov_") and x.endswith(".zip") ]) if markov_backups: # Restore Markov model from backup with zipfile.ZipFile("backup/" + markov_backups[-1], 'r') as zip_ref: zip_ref.extractall(".") log.info( f"Restoring Markov model from backup/{markov_backups[-1]}") shutil.move(markov_backups[-1][:-4], "markov.yaml") bc.markov = Util.read_config_file(const.MARKOV_PATH) if bc.markov is None: bc.markov = Markov() log.warning( "Failed to restore Markov model from backup. Creating new Markov model..." ) if bc.markov is None: bc.markov = Markov() log.info("Created empty Markov model") # Check config versions ok = True ok &= Util.check_version( "discord.py", discord.__version__, const.DISCORD_LIB_VERSION, solutions=[ "execute: python -m pip install -r requirements.txt", ]) ok &= Util.check_version( "Config", config.version, const.CONFIG_VERSION, solutions=[ "run patch tool", "remove config.yaml (settings will be lost!)", ]) ok &= Util.check_version( "Markov config", bc.markov.version, const.MARKOV_CONFIG_VERSION, solutions=[ "run patch tool", "remove markov.yaml (Markov model will be lost!)", ]) ok &= Util.check_version( "Secret config", secret_config.version, const.SECRET_CONFIG_VERSION, solutions=[ "run patch tool", "remove secret.yaml (your Discord authentication token will be lost!)", ]) if main_bot and not ok: sys.exit(const.ExitStatus.CONFIG_FILE_ERROR) config.commands.update() # Checking authentication token if secret_config.token is None: secret_config = SecretConfig() if not FF.is_enabled("WALBOT_FEATURE_NEW_CONFIG"): secret_config.token = input("Enter your token: ") # Constructing bot instance if main_bot: intents = discord.Intents.all() walbot = WalBot(args.name, config, secret_config, intents=intents) else: walbot = importlib.import_module("src.minibot").MiniWalBot( args.name, config, secret_config, args.message) # Starting the bot try: walbot.run(secret_config.token) except discord.errors.PrivilegedIntentsRequired: log.error( "Privileged Gateway Intents are not enabled! Shutting down the bot..." ) # After stopping the bot log.info("Bot is disconnected!") if main_bot: config.save(const.CONFIG_PATH, const.MARKOV_PATH, const.SECRET_CONFIG_PATH, wait=True) BotCache(main_bot).remove() if bc.restart_flag: cmd = f"'{sys.executable}' '{os.path.dirname(os.path.dirname(__file__)) + '/walbot.py'}' start" log.info("Calling: " + cmd) if sys.platform in ("linux", "darwin"): fork = os.fork() if fork == 0: subprocess.call(cmd) elif fork > 0: log.info("Stopping current instance of the bot") sys.exit(const.ExitStatus.NO_ERROR) else: subprocess.call(cmd)
def markov_yaml(self, config): """Update markov.yaml""" if config.version == "0.0.1": config.__dict__["min_chars"] = 1 config.__dict__["min_words"] = 1 self._bump_version(config, "0.0.2") if config.version == "0.0.2": config.__dict__["chains_generated"] = 0 self._bump_version(config, "0.0.3") if config.version == "0.0.3": config.__dict__["max_chars"] = 2000 config.__dict__["max_words"] = 500 self._bump_version(config, "0.0.4") if config.version == "0.0.4": config.model[""].__dict__["word"] = None self._bump_version(config, "0.0.5") if config.version == "0.0.5": for i, _ in enumerate(config.filters): config.__dict__["filters"][i] = re.compile(config.filters[i].pattern, re.DOTALL) self._bump_version(config, "0.0.6") if config.version == "0.0.6": config.__dict__["ignored_prefixes"] = dict() self._bump_version(config, "0.0.7") if config.version == "0.0.7": if FF.is_enabled("WALBOT_FEATURE_NEW_CONFIG") == "1": db = WalbotDatabase() def preprocess_key(key: str): return key.replace("$", "<__markov_dollar>").replace(".", "<__markov_dot>") markov_model = dict() for key, value in config.model.items(): if key is None or key == "": key = "__markov_null" next_list = dict() for k, v in value.next.items(): if k is None: k = "__markov_terminate" next_list[preprocess_key(k)] = v markov_model[preprocess_key(key)] = { "word": value.word, "next": next_list, "total_next": value.total_next, "type": value.type, } markov_ignored_prefixes = dict() for key, value in config.ignored_prefixes.items(): markov_ignored_prefixes[str(key)] = value db.markov.insert({ "chains_generated": config.chains_generated, "end_node": { "word": None, "next": { "__markov_null": 0, }, "total_next": 0, "type": 2, }, "filters": config.filters, "ignored_prefixes": markov_ignored_prefixes, "max_chars": config.max_chars, "max_words": config.max_words, "min_chars": config.min_chars, "min_words": config.min_words, "model": markov_model, "version": "0.1.0", }) self._bump_version(config, "0.1.0") log.warning("Markov model has been moved to MongoDB!") else: log.info(f"Version of {self.config_name} is up to date!") if config.version == "0.1.0": log.info(f"Version of {self.config_name} is up to date!") else: log.error(f"Unknown version {config.version} for {self.config_name}!")
def send_note(entity, argument, command_name, note_type, note_singular, note_plural, note_class, grammar_genre): if not entity: log.bug("entity non è un parametro valido: %r" % entity) return False # argument può essere una stringa vuota if not command_name: log.bug("command_name non è un parametro valido: %r" % command_name) return False if not note_type: log.bug("note_type non è un parametro valido: %r" % note_type) return False if not note_singular: log.bug("note_singular non è un parametro valido: %r" % note_singular) return False if not note_class: log.bug("note_class non è un parametro valido: %r" % note_class) return False if not grammar_genre: log.bug("grammar_genre non è un parametro valido: %r" % grammar_genre) return False # ------------------------------------------------------------------------- if not argument: syntax = get_command_syntax(entity, command_name) entity.send_output(syntax, break_line=False) return False if not entity.IS_PLAYER: entity.send_output("Solo i giocatori possono inviare %s." % ( add_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR))) return False if config.max_account_typos == 0: entity.send_output("L'invio %s è stata disabilitata." % ( add_article(note_plural, grammar_genre, GRAMMAR.INDETERMINATE, GRAMMAR.PLURAL))) return False # Non dovrebbe mai capitare, ma non si sa mai, in teoria comunque non è un baco if not entity.account: entity.send_output("La sessione del tuo account è scaduta, devi riaccedere nuovamente al sito per poter inviare %s" % ( add_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR))) return False # Anche questa non dovrebbe mai capitare if not entity.location: entity.send_output("Ti è impossibile inviare %s da questo luogo." % ( add_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR))) log.bug("entity.location non è valido (%r) per entity %s mentre stava cercando di inviare una nota %s" % ( entity.location, entity.code, note_type)) return False # Crea il codice per la nota code_to_check = entity.account.name + "#" sended_notes = getattr(entity.account, "sended_%ss" % note_type) code = code_to_check + str(sended_notes) # Se il codice già esiste nel database probabilmente c'è una dicrepanza # tra il dato di account e quelli delle note quindi il codice si # incrementa fino a trovare un numero libero counter = 1 while code in database[note_type + "s"]: code = code_to_check + str(sended_notes + counter) setattr(entity.account, "sended_%ss" % note_type, sended_notes + counter + 1) counter += 1 who = entity.code if entity.IS_PLAYER and entity.account: who += " dell'account %s" % entity.account.name if entity.location.IS_ROOM: where = "%s (%r)" % (entity.location.code, entity.location.get_destination()) else: where = "%s in %r " % (entity.location.get_name(), entity.get_in_room.get_destination()) when = "%s (%s %s %s %s)" % (datetime.datetime.now(), calendar.minute, calendar.hour, calendar.day, calendar.month) note = note_class(code, who, where, when, argument) if not note: log.bug("note non è valido (%r) per la tipologia %s" % (note, note_type)) entity.send_output("Impossibile segnalare un%s nuov%s %s." % ( "" if grammar_genre == GRAMMAR.MASCULINE else "a", "o" if grammar_genre == GRAMMAR.MASCULINE else "a", note_singular)) return False # Evitare di inviare una nota subito dopo averne inviata un'altra, # così da evitare eventuali spammer last_note_sended_at = getattr(entity.account, "last_%s_sended_at" % note_type) if (datetime.datetime.now() - last_note_sended_at).days <= 0: # (TD) Python 2.7, così non servirà più il check su days #total_seconds = (datetime.datetime.now() - last_note_sended_at).total_seconds() total_seconds = (datetime.datetime.now() - last_note_sended_at).seconds if total_seconds < config.sending_interval: remaining_seconds = config.sending_interval - total_seconds random_id = random.random() entity.send_output('''Potrai inviare %s solo tra <span id="%s">%d %s</span><script>parent.countdown("%s");</script>''' % ( add_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR, GRAMMAR.POSSESSIVE), random_id, remaining_seconds, "secondo" if remaining_seconds == 1 else "secondi", random_id)) return False # Inserisce un tetto massimo di note ancora aperte inviabili contando se # effettivamente le note dell'account ancora aperte sono così tante counter = 0 for note_code in database[note_type + "s"]: if note_code.startswith(code_to_check): counter += 1 if counter >= getattr(config, "max_account_%ss" % note_type): entity.send_output("Hai raggiunto il massimo di %s attualmente segnalabili, riprova tra qualche minuto." % (note_plural, config.game_name)) log.monitor("%s ha raggiunto il numero massimo di %s inviabili, controllare e chiudere quelle obsolete cosicché possa inviarne delle altre.", ( entity.code, note_plural)) return False # Controlla che le altre note non abbiano lo stesso testo, altrimenti ci # troviamo davanti ad un possibile spammer, il sistema quindi fa finta # di salvarsi la nota e intanto segnala agli Amministratori lo spammer for other_note in database[note_type + "s"].itervalues(): if other_note.text == argument and other_note.code.startswith(code_to_check): entity.send_output("%s è stato salvato. Grazie!" % ( add_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR, GRAMMAR.POSSESSIVE).capitalize())) log.warning("Qualcuno per sbaglio invia due volte la stessa nota %s. Se continua esageratemente è un possibile spammer (%s)" % ( note_singular, entity.get_conn().get_id())) return False database[note_type + "s"][note.code] = note # Le note sono uno di quei dati slegati dal gioco che possono essere # scritti subito sul disco vista la relativa rarità del suo invio e quindi # la bassa probabilità che vada ad inficiare sulle prestazioni globali note_path = "data/%ss/%s.dat" % (note_type, note.code) try: note_file = open(note_path, "w") except IOError: entity.send_output("Per un errore interno al server è impossibile salvare %s." % ( add_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR, GRAMMAR.POSSESSIVE))) log.bug("Impossibile aprire il file %s in scrittura" % note_path) return False fwrite(note_file, note) note_file.close() # Poiché è stato salvata la nota bisogna anche salvare l'account che # contiene il numero di note inviate da sempre incrementato giusto qui # (TT) Bisogna fare attenzione al salvataggio facile dell'account, # potrebbero in futuro esservi informazioni duplicate o erroneamente # incrementate (dopo un crash) a seconda delle dipendenze tra i dati # nell'account ed altre tipologie di dati che non verrebbero salvate # se non alla chisura del Mud setattr(entity.account, "sended_%ss" % note_type, sended_notes + 1) setattr(entity.account, "last_%s_sended_at" % note_type, datetime.datetime.now()) account_path = "persistence/accounts/%s.dat" % entity.account.name try: account_file = open(account_path, "w") except IOError: entity.send_output("Per un errore interno al server è impossibile salvare %s." % ( add_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR, GRAMMAR.POSSESSIVE))) log.bug("Impossibile aprire il file %s in scrittura" % account_path) return False fwrite(account_file, entity.account) account_file.close() entity.send_output("Hai appena segnalato %s%d° %s. Grazie!" % ( get_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR, GRAMMAR.POSSESSIVE), sended_notes+1, note_singular)) # Invia la mail subject = "Invio %s" % note_singular text = "L'account %s ha appena segnalato %s%d° %s." % ( entity.account.name, get_article(note_singular, grammar_genre, GRAMMAR.DETERMINATE, GRAMMAR.SINGULAR), sended_notes+1, note_singular) text += "\nWho: %s" % note.who text += "\nWhere: %s" % note.where text += "\nWhen: %s" % note.when text += "\nText: %s" % note.text mail.send_to_admins(subject, text, show_players=False) return True