123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- # Friendly Telegram (telegram userbot)
- # Copyright (C) 2018-2021 The Authors
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU Affero General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU Affero General Public License for more details.
- # You should have received a copy of the GNU Affero General Public License
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
- import ast
- import inspect
- import locale
- import os
- import string
- import sys
- import time
- from dialog import Dialog, ExecutableNotFound
- from . import main, utils
- def _safe_input(*args, **kwargs):
- """Try to invoke input(*), print an error message if an EOFError or OSError occurs)"""
- try:
- return input(*args, **kwargs)
- except (EOFError, OSError):
- raise
- except KeyboardInterrupt:
- print()
- return None
- class TDialog:
- """Reimplementation of dialog.Dialog without external dependencies"""
- OK = True
- NOT_OK = False
- def __init__(self):
- self._title = ""
- # Similar interface to pythondialog
- def menu(self, title, choices):
- """Print a menu and get a choice"""
- print(self._title)
- print()
- print()
- print(title)
- print()
- biggest = max(len(k) for k, d in choices)
- for i, (k, v) in enumerate(choices, 1):
- print(
- f" {str(i)}. {k}"
- + " " * (biggest + 2 - len(k))
- + (v.replace("\n", "...\n "))
- )
- while True:
- inp = _safe_input(
- "Please enter your selection as a number, or 0 to cancel: "
- )
- if inp is None:
- inp = 0
- try:
- inp = int(inp)
- if inp == 0:
- return self.NOT_OK, "Cancelled"
- return self.OK, choices[inp - 1][0]
- except (ValueError, IndexError):
- pass
- def inputbox(self, query):
- """Get a text input of the query"""
- print(self._title)
- print()
- print()
- print(query)
- print()
- inp = _safe_input("Please enter your response, or type nothing to cancel: ")
- if inp == "" or inp is None:
- return self.NOT_OK, "Cancelled"
- return self.OK, inp
- def msgbox(self, msg):
- """Print some info"""
- print(self._title)
- print()
- print()
- print(msg)
- return self.OK
- def set_background_title(self, title):
- """Set the internal variable"""
- self._title = title
- def yesno(self, question):
- """Ask yes or no, default to no"""
- print(self._title)
- print()
- return (
- self.OK
- if (_safe_input(f"{question} (y/N): ") or "").lower() == "y"
- else self.NOT_OK
- )
- TITLE = ""
- if sys.stdout.isatty():
- try:
- DIALOG = Dialog(dialog="dialog", autowidgetsize=True)
- locale.setlocale(locale.LC_ALL, "")
- except (ExecutableNotFound, locale.Error):
- # Fall back to a terminal based configurator.
- DIALOG = TDialog()
- else:
- DIALOG = TDialog()
- MODULES = None
- DB = None # eww... meh.
- # pylint: disable=W0603
- def validate_value(value):
- """Convert string to literal or return string"""
- try:
- return ast.literal_eval(value)
- except (ValueError, SyntaxError):
- return value
- def modules_config():
- """Show menu of all modules and allow user to enter one"""
- code, tag = DIALOG.menu(
- "Modules",
- choices=[
- (module.name, inspect.cleandoc(getattr(module, "__doc__", None) or ""))
- for module in MODULES.modules
- if getattr(module, "config", {})
- ],
- )
- if code == DIALOG.OK:
- for mod in MODULES.modules:
- if mod.name == tag:
- # Match
- while not module_config(mod):
- time.sleep(0.05)
- return modules_config()
- return None
- def module_config(mod):
- """Show menu for specific module and allow user to set config items"""
- choices = [
- (key, getattr(mod.config, "getdoc", lambda k: "Undocumented key")(key))
- for key in getattr(mod, "config", {}).keys()
- ]
- code, tag = DIALOG.menu(
- "Module configuration for {}".format(mod.name), choices=choices
- )
- if code == DIALOG.OK:
- code, value = DIALOG.inputbox(tag)
- if code == DIALOG.OK:
- DB.setdefault(mod.__class__.__name__, {}).setdefault("__config__", {})[
- tag
- ] = validate_value(value)
- DIALOG.msgbox("Config value set successfully")
- return False
- return True
- def run(database, data_root, phone, init, mods):
- """Launch configurator"""
- global DB, MODULES, TITLE
- DB = database
- MODULES = mods
- TITLE = "Userbot Configuration for {}"
- TITLE = TITLE.format(phone)
- DIALOG.set_background_title(TITLE)
- while main_config(init, data_root):
- time.sleep(0.05)
- return DB
- def api_config(data_root):
- """Request API config from user and set"""
- code, hash_value = DIALOG.inputbox("Enter your API Hash")
- if code == DIALOG.OK:
- if len(hash_value) != 32 or any(
- it not in string.hexdigits for it in hash_value
- ):
- DIALOG.msgbox("Invalid hash")
- return
- code, id_value = DIALOG.inputbox("Enter your API ID")
- if not id_value or any(it not in string.digits for it in id_value):
- DIALOG.msgbox("Invalid ID")
- return
- with open(
- os.path.join(
- data_root or os.path.dirname(utils.get_base_dir()), "api_token.txt"
- ),
- "w",
- ) as file:
- file.write(id_value + "\n" + hash_value)
- DIALOG.msgbox("API Token and ID set.")
- def logging_config():
- """Ask the user to choose a loglevel and save it"""
- code, tag = DIALOG.menu(
- "Log Level",
- choices=[
- ("50", "CRITICAL"),
- ("40", "ERROR"),
- ("30", "WARNING"),
- ("20", "INFO"),
- ("10", "DEBUG"),
- ("0", "ALL"),
- ],
- )
- if code == DIALOG.OK:
- DB.setdefault(main.__name__, {})["loglevel"] = int(tag)
- def factory_reset_check():
- """Make sure the user wants to factory reset"""
- global DB
- if (
- DIALOG.yesno(
- "Do you REALLY want to erase ALL userbot data stored in Telegram cloud?\n"
- "Your existing Telegram chats will not be affected."
- )
- == DIALOG.OK
- ):
- DB = None
- def main_config(init, data_root):
- """Main menu"""
- if init:
- return api_config(data_root)
- choices = [
- ("API Token and ID", "Configure API Token and ID"),
- ("Modules", "Modular configuration"),
- ("Logging", "Configure debug output"),
- ("Factory reset", "Removes all userbot data stored in Telegram cloud"),
- ]
- code, tag = DIALOG.menu("Main Menu", choices=choices)
- if code != DIALOG.OK:
- return False
- if tag == "Modules":
- modules_config()
- if tag == "API Token and ID":
- api_config(data_root)
- if tag == "Logging":
- logging_config()
- if tag == "Factory reset":
- factory_reset_check()
- return False
- return True
|