diff --git a/src/pybliotecario/argument_parser.py b/src/pybliotecario/argument_parser.py index 35655d1..4f1dbc7 100644 --- a/src/pybliotecario/argument_parser.py +++ b/src/pybliotecario/argument_parser.py @@ -1,19 +1,19 @@ """ - Wrapper for argument parser and initialization + Wrapper for the argument parser and the initialization """ -import os +from argparse import Action, ArgumentParser, ArgumentTypeError +import configparser import glob -import pathlib import importlib -import configparser -from argparse import ArgumentParser, Action, ArgumentTypeError +import os +from pathlib import Path -CONFIG_FILE = "pybliotecario.ini" +from .customconf import default_config_path, default_data_path def validpath(value): """Check whether the received path is valid""" - path = pathlib.Path(value) + path = Path(value) if not path.exists(): raise ArgumentTypeError(f"The file '{value}' can't be found") if path.is_dir(): @@ -74,46 +74,63 @@ def config_module(module): return dict_list -def configure_telegram(main_folder): - """Configure Telegram""" +def configure_telegram(): + """Configure pybliotecario to use the Telegram API + This function walks the user through the process of creating a new bot + and storing the token and the chat_id of the user in the configuration file + """ # Initialize the bot in telegram print( """Welcome to The Wizard! -The first thing you will need is an authorization token from the botfather. -If you don't know how to get one, read here: https://core.telegram.org/bots#6-botfather""" +The first thing you will need is to create a new bot with the botfather in your telegram client. +Alternatively, you can get use a previously created bot. +Ask the botfather for an authorization token before continuing. +The instructions on how to get the token can be found here: https://core.telegram.org/bots#how-do-i-create-a-bot +""" ) token = input("Authorization token: ") - print("Thanks, let's test this out. Say something to your bot") + + # Try to fire up the bot with the given token from pybliotecario.backend import TelegramUtil - max_timeouts = 20 - tim = 0 + from pybliotecario.backend.telegram_util import TelegramMessage - teleAPI = TelegramUtil(token, timeout=20) - while True: - all_updates = teleAPI.raw_updates() - from pybliotecario.backend.telegram_util import TelegramMessage + telegram_API = TelegramUtil(token, timeout=20) + print("Thanks, let's test this out. Say something (anything!) to your bot in telegram") - try: - update = TelegramMessage(all_updates[0]) - except IndexError as e: - print("Timeout... waiting for updates again...") - tim += 1 - if tim > max_timeouts: - raise e - continue - print(f"Message received: {update.text}") - yn = input("Was this your msg? [y/n] ") - if yn.lower() in ("y", "s"): - chat_id = update.chat_id - print(f"Your chat id is: {chat_id} and your username is: {update.username}") + for _ in range(20): # Allow for 20 tries + all_updates = telegram_API.raw_updates() + + for update in all_updates: + msg = TelegramMessage(update) + print(f"Message received: {msg.text}") + yn = input("Was this your msg? [y/n] ") + if msg_found := yn.lower().startswith(("y", "s")): + chat_id = msg.chat_id + username = msg.username + print(f"Your chat id is: {chat_id} and your username: {username}") + break + + if msg_found: break - print("Try again") - yn = input("Do you want to enable the 'chivato' mode, so you get a warning if anyone other than you tries to use the bot? [yn] ") - chivato = yn.lower() in ("y", "s") + print("Please, try again") + + else: + raise ValueError("There was some problem with the configuration of Telegram") - # Fill the DEFAULT options - config_dict = {"DEFAULT": {"TOKEN": token, "chat_id": chat_id, "main_folder": main_folder, "chivato": chivato}} + yn = input( + "Do you want to enable the 'chivato' mode, so you get a warning if anyone other than you tries to use the bot? [yn] " + ) + chivato = yn.lower().startswith(("y", "s")) + + # Now prepare the dictionary with the DEFAULT field of the config file + config_dict = { + "DEFAULT": { + "TOKEN": token, + "chat_id": chat_id, + "chivato": chivato, + } + } return config_dict @@ -130,11 +147,13 @@ def configure_all(): module_components = components.__name__ modules = glob.glob(folder_components + "/*.py") for module_file in modules: - module_name = pathlib.Path(module_file).with_suffix("").name + module_name = Path(module_file).with_suffix("").name try: module = importlib.import_module(f"{module_components}.{module_name}") except ModuleNotFoundError as e: - print(f"In order to use the component '{module_name}' it is necessary to install its dependencies") + print( + f"In order to use the component '{module_name}' it is necessary to install its dependencies" + ) missing_dependencies = True continue dict_list = config_module(module) @@ -143,11 +162,12 @@ def configure_all(): config_dict[key] = item if missing_dependencies: - print("""To install missing dependencies for a particular component you can install them explicitly: + print( + """To install missing dependencies for a particular component you can install them explicitly: ~$ pip install pybliotecario[component] or install all dependencies with - ~$ pip install pybliotecario[full]""") - + ~$ pip install pybliotecario[full]""" + ) return config_dict @@ -163,36 +183,49 @@ class InitAction(Action): def __init__(self, nargs=0, **kwargs): super().__init__(nargs=nargs, **kwargs) - def __call__(self, parser, *args, **kwargs): + def __call__(self, parser, namespace, *args, **kwargs): """ - Configures the pybliotecario by first - configuring Telegram - and then calling the configure_me method of all components + Configures the pybliotecario by first configuring the Telegram API + and then calling the ``configure_me`` method of all components """ config_dict = {} - # Set up environmental stuff - home = os.environ["HOME"] - main_folder = home + "/.pybliotecario/" - os.makedirs(main_folder, exist_ok=True) - config_file = home + "/." + CONFIG_FILE - # Check whether a config file already exists - config_exists = os.path.isfile(config_file) - # If it does, you might not want to reconfigure Telegram, so let's ask - initialize = True - if config_exists: + + config_path = default_config_path() + data_folder = default_data_path() + + if namespace.config_file is not None: print( - """It seems pybliotecario's Telegram capabilities -have already been configured in this computer""" + f"WARNING! You are setting {namespace.config_file} as the configuration file instead of the default {config_path}" ) - yn = input("Do you want to configure it again? [y/n] ") + yn = input("Are you sure? Do you want to continue [y/n] ") if not yn.lower().startswith(("y", "s")): - initialize = False + print("Don't use --config_file with --init to avoid this!") + parser.exit(0) + config_path = namespace.config_file + data_folder = Path(input(f"Where do you want your data? e.g.: {data_folder}: ")) + + print(f"Note: configuration will be written to {config_path}") + + # Make sure the folders exist + data_folder.mkdir(exist_ok=True, parents=True) + config_path.parent.mkdir(exist_ok=True, parents=True) + + # Check whether a config file already exists, if it does, ask before doing the Telegram + initialize = True + if config_exists := config_path.exists(): + print("It seems pybliotecario has already been initialized in this computer") + yn = input("Do you want to start the configuration from scratch? [y/n] ") + initialize = yn.lower().startswith(("y", "s")) + if initialize: - config_dict.update(configure_telegram(main_folder)) - print("Let's loop over the pybliotecario modules to configure their options") + config_dict.update(configure_telegram()) + config_dict["DEFAULT"]["main_folder"] = data_folder + + print("Now let's loop over all pybliotecario modules to configure their options") config_dict.update(configure_all()) # And finally write the config file - write_config(config_dict, config_file, config_exists=config_exists) + write_config(config_dict, config_path, config_exists=config_exists) + print(f"The configuration of the pybliotecario has been written to: {config_path}") parser.exit(0) diff --git a/src/pybliotecario/backend/basic_backend.py b/src/pybliotecario/backend/basic_backend.py index 482d156..1e302b3 100644 --- a/src/pybliotecario/backend/basic_backend.py +++ b/src/pybliotecario/backend/basic_backend.py @@ -164,7 +164,8 @@ class Backend(ABC): @abstractmethod def _get_updates(self, not_empty=False): - """Retrieve updates""" + """Retrieve updates as a list""" + return [] def raw_updates(self): """Returns a raw version of the updates as implemented by the child class""" diff --git a/src/pybliotecario/components/component_core.py b/src/pybliotecario/components/component_core.py index 30fabf6..06a9855 100644 --- a/src/pybliotecario/components/component_core.py +++ b/src/pybliotecario/components/component_core.py @@ -15,7 +15,7 @@ import sys import logging -from pybliotecario.argument_parser import CONFIG_FILE +from ..customconf import default_config_path log = logging.getLogger(__name__) @@ -54,7 +54,10 @@ def __init__( def update_config(self): """Updates default ($HOME/.CONFIG_FILE) configuration file""" - default_config = "{0}/.{1}".format(os.environ.get("HOME"), CONFIG_FILE) + if self.configuration is not None: + print("Sorry, I forgot this set_trace here, my bad") + import ipdb; ipdb.set_trace() + default_config = default_config_path() new_section = self.configure_me() for key, item in new_section.items(): self.configuration[key] = item diff --git a/src/pybliotecario/components/github_component.py b/src/pybliotecario/components/github_component.py index abb64dd..779e981 100644 --- a/src/pybliotecario/components/github_component.py +++ b/src/pybliotecario/components/github_component.py @@ -52,7 +52,7 @@ def configure_me(cls): except ValueError: print("Only integer numbers accepted") dict_out = { - self.key_name: { + cls.key_name: { "token": access_token, "since_hours": hours, } diff --git a/src/pybliotecario/components/wiki.py b/src/pybliotecario/components/wiki.py index e8a5d38..3f4d4c6 100644 --- a/src/pybliotecario/components/wiki.py +++ b/src/pybliotecario/components/wiki.py @@ -33,23 +33,22 @@ def __init__(self, telegram_object, configuration=None, **kwargs): @classmethod def configure_me(cls): - print("") - print(" # Wikipedia Module ") - print("This is the configuration helper for the wikipedia module") - print("Introduce the length of the msgs you want to obtain: ") + print(f""" +# Wikipedia Module +This is the configuration helper for the wikipedia module +Introduce the length of the msgs you want to obtain (max: {MAX_SIZE}):""") summary_size = 0 while summary_size > MAX_SIZE or summary_size < 1: # The max msg in telegram is 4096 UTF char. try: - summary_size = int(input(" Max: {0} > ".format(MAX_SIZE))) + summary_size = int(input(f" > ".format(MAX_SIZE))) except ValueError: - print("Please, write a number between 0 and {0}".format(MAX_SIZE)) + print(f"Please, write a number between 0 and {MAX_SIZE}") possible_languages = ["EN", "ES", "IT"] language = None print("Introduce the default language for Wikipedia pages") while language not in possible_languages: - print("Possible choices: {0}".format(", ".join(possible_languages))) - language = input(" > (default: {0}".format(DEFAULT_LANGUAGE)) + language = input(f" > Possible choices: {possible_languages} (default: {DEFAULT_LANGUAGE}) > ") if language == "": language = DEFAULT_LANGUAGE dict_out = { diff --git a/src/pybliotecario/customconf.py b/src/pybliotecario/customconf.py index 3a75180..99cc354 100644 --- a/src/pybliotecario/customconf.py +++ b/src/pybliotecario/customconf.py @@ -1,8 +1,29 @@ """ Define custom parsers for the config reader + and default data/config locations """ from configparser import ConfigParser from copy import copy +from os import environ +from pathlib import Path + +NAME = "pybliotecario" + + +def default_config_path(name=NAME): + """Return the default config path looking at the value of XDG_CONFIG_HOME + Usually: $HOME/.config/pybliotecario/pybliotecario.ini + """ + config_folder = Path(environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) + return config_folder / name / "pybliotecario.ini" + + +def default_data_path(name=NAME): + """Return the default data path looking at the value of XDG_DATA_HOME + Usually: $HOME/.local/share/pybliotecario + """ + data_folder = Path(environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share")) + return data_folder / name def _parse_chat_id(value): diff --git a/src/pybliotecario/pybliotecario.py b/src/pybliotecario/pybliotecario.py index 1eac932..cc04667 100755 --- a/src/pybliotecario/pybliotecario.py +++ b/src/pybliotecario/pybliotecario.py @@ -9,18 +9,26 @@ from pybliotecario.backend import TelegramUtil, TestUtil, FacebookUtil from pybliotecario.core_loop import main_loop -from pybliotecario.customconf import CustomConfigParser +from pybliotecario.customconf import CustomConfigParser, default_config_path # Modify argument_parser.py to read new arguments -from pybliotecario.argument_parser import parse_args, CONFIG_FILE +from pybliotecario.argument_parser import parse_args import pybliotecario.on_cmdline as on_cmdline logger = logging.getLogger() def read_config(config_file=None): - """Reads the pybliotecario config file and uploads the global configuration""" - config_files = [Path.home() / f".{CONFIG_FILE}", CONFIG_FILE] + """Reads the pybliotecario config file and uploads the global configuration + By default looks always in the default file path (in XDG_CONFIG_HOME) and the current folder + """ + default_file_path = default_config_path() + # Checks as well (with lower priority) for ~/.pybliotecario.ini for backwards compatibility + old_path = Path.home() / ".pybliotecario.ini" + config_files = [old_path, default_file_path, default_file_path.name] + if old_path.exists(): + logger.error(f"Deprecation notice: ~/.pybliotecario.ini is now deprecated, please move the configuration to {default_file_path}") + if config_file is not None: config_files.append(config_file) config = CustomConfigParser()