class FunServer(BaseServer): name = 'FunServer' guid = 'D24972621DAF4E35AA6BE68AB55BB46F' command_list = [ Command( name='насмеши', uri='/execute', description='Случайная цитата башорга. Например: Бот, насмеши', priority=10, ), ] # Путь к файлу сервера file_name = os.path.abspath(__file__) def _execute_body(self, rq: dict, **params: dict) -> typing.Union[dict, str]: # TODO: # command = rq['command'] # command_name = rq['command_name'] from commands.command__fun.fun import get_random_quote result = get_random_quote() return result
class GetImageInfoServer(BaseServer): name = 'GetImageInfoServer' guid = 'F89FA403EA244F489F0AC630BEE4CA56' command_list = [ Command( name='получить информацию о картинке', uri='/execute', description= 'Команда получения информации о картинке. Например: Бот, получить информацию о картинке.', priority=9, ), ] # Путь к файлу сервера file_name = os.path.abspath(__file__) def _execute_body(self, rq: dict, **params: str) -> typing.Union[dict, str]: command = rq['command'] command_name = rq['command_name'] attachment = rq['attachment'] if not attachment: raise Exception( "Неправильная команда 'получить информацию о картинке': нужно передавать картинку." ) img_file_io = create_io(attachment) info = get_image_info(img_file_io, pretty_json_str=True) return info
class WeatherServer(BaseServer): name = 'WeatherServer' guid = 'EF3D2E05CBAA49F2867C742EA7D856D0' command_list = [ Command( name='погода', uri='/execute', description= 'Погода в указанном населенном пункте. Например: Бот, погода магнитогорск', priority=10, ), ] # Путь к файлу сервера file_name = os.path.abspath(__file__) def _execute_body(self, rq: dict, **params: dict) -> typing.Union[dict, str]: command = rq['command'] command_name = rq['command_name'] if not command: raise Exception( "Неправильная команда 'погода': не указан населенный пункт") from commands.command__weather_in_city.weather_in_city import get_weather result = get_weather(command) return result
class ExchangeRateServer(BaseServer): name = 'ExchangeRateServer' guid = '21535ECEF2104BFD8F1CD1DC715309AA' command_list = [ Command( name='курс валют', uri='/execute', description= 'Показать текущий курс евро и доллара. Например: Бот, курс валют', priority=9, ), ] # Путь к файлу сервера file_name = os.path.abspath(__file__) def __init__(self): super().__init__() # Поле для сохранения результата self.last_result = None # Поле для сохранения времени последнего запроса self.last_request_time = None # Поле для сохранения времени, когда кэш испортится self.cache_update_time = None def _execute_body(self, rq: dict, **params: dict) -> typing.Union[dict, str]: # command = rq['command'] # command_name = rq['command_name'] delta = timedelta(hours=1) if self.last_request_time is None or self.cache_update_time < datetime.now( ): rate_list = currency.exchange_rate(['EUR', 'USD']) self.last_result = ', '.join(rate_list) self.last_request_time = datetime.now() self.cache_update_time = self.last_request_time + delta print('Обновляю кэшированный курс валют: "{}", кэш испортится ' 'в {}.'.format(self.last_result, self.cache_update_time)) else: print('Возвращаю кэш за {}, обновление кэша будет через {} в {}'. format(self.last_request_time, self.cache_update_time - datetime.now(), self.cache_update_time)) return self.last_result
class QRCodeServer(BaseServer): name = 'QRCodeServer' guid = '60BC4D9FB8BB461E996E0F22C37F7498' command_list = [ Command( name='qrcode', uri='/execute', description= 'Команда для генерации QR Code. Например: Бот, qrcode Привет мир!. ' 'Или: Бот, qrcode https://github.com/gil9red', priority=9, ), ] # Путь к файлу сервера file_name = os.path.abspath(__file__) def _execute_body(self, rq: dict, **params: dict) -> typing.Union[dict, str]: command = rq['command'] command_name = rq['command_name'] if not command: raise Exception( "Неправильная команда 'qrcode': нужно указать текст, например: Привет мир!" ) # https://github.com/gil9red/SimplePyScripts/blob/d3ee62b48dd1277aa68244f7ec2966183517b931/generate_qrcode/main.py import qrcode img = qrcode.make(command) extension = 'png' # In memory import io io_data = io.BytesIO() img.save(io_data, extension) attachment = common.FileAttachment(content=io_data.getvalue(), extension=extension) return self.generate_response( attachment=attachment, attachment_type=common.AttachmentType.IMAGE)
class GifServer(BaseServer): name = 'GifServer' guid = '0CBBE285D35D4BCCB28EF3554CD3D4DC' command_list = [ Command( # TODO: если не указан запрос, возвращать случайную гифку name='gif', uri='/execute', description= 'Возвращает по запросу гифку. Например: Бот, gif котята', ), ] # Путь к файлу сервера file_name = os.path.abspath(__file__) def _execute_body(self, rq: dict, **params: dict) -> typing.Union[dict, str]: command = rq['command'] command_name = rq['command_name'] if not command: raise Exception( "Неправильная команда '{}': нужно указать текст, например: котята" .format(command_name)) import giphy url = giphy.get_gif(command) if not url: return '<Не удалось найти гифку>' import requests rs = requests.get(url) import common attachment = common.FileAttachment(content=rs.content, extension='gif') attachment_type = common.AttachmentType.GIF return self.generate_response(attachment=attachment, attachment_type=attachment_type)
class CalcServer(BaseServer): name = 'CalcServer' guid = '9A306C919E0941C68D92F190E3F89C2B' command_list = [ Command( name='калькулятор', uri='/execute', description= 'Команда для рассчета математических выражений. Например: Бот, калькулятор 2 + 2 * 2. Еще ' 'примеры выражений: "10 ** 3", "sin(2 ** 10), "(0xFF + 255) / 0b1010", ' '"(0xFF + 255) // 0b1010"', priority=9, ), ] # Путь к файлу сервера file_name = os.path.abspath(__file__) def _execute_body(self, rq: dict, **params: dict) -> typing.Union[dict, str]: command = rq['command'] command_name = rq['command_name'] if not command: raise Exception( "Неправильная команда 'калькулятор': нужно указать выражение, например: 2 + 2 * 2" ) # Калькулятор из: https://github.com/gil9red/SimplePyScripts/blob/master/calculator/use_numexpr_module/main.py # TODO: не модет подсчитать 10**123 # TODO: использовать simpleeval: https://github.com/gil9red/SimplePyScripts/blob/38765c6ef304fb5da233b8d44a9294b52b86343d/simpleeval__examples__calc/hello_world.py import numexpr result = numexpr.evaluate(command) return result
class TextConverter(BaseServer): name = 'TextConverter' guid = '531B994DED974F1586BA370F0AE1BE14' command_list = [ Command( name='str2hex', uri='/execute?str2hex', description='Конвертация строки в HEX. Например: Бот, str2hex Привет!', priority=6, ), Command( name='hex2str', uri='/execute?hex2str', description='Конвертация из HEX в строку. Например: Бот, hex2str D09FD180D0B8D0B2D0B5D18220D0BCD0B8D18021', priority=6, ), Command( name='str2bin', uri='/execute?str2bin', description='Конвертация из текстовой строки в бинарную. Например: Бот, str2bin Привет!', priority=5, ), Command( name='bin2str', uri='/execute?bin2str', description='Конвертация из бинарной строки в текстовую. Например: Бот, bin2str 11001111 11110000 ' '11101000 11100010 11100101 11110010 00100001', priority=5, ), Command( name='str_2_base64', uri='/execute?str_2_base64', description='Конвертация из текстовой строки в base64. Например: Бот, str_2_base64 Привет! Hello!', priority=4, ), Command( name='base64_2_str', uri='/execute?base64_2_str', description='Конвертация из строки в base64 в текстовую. ' 'Например: Бот, base64_2_str 0J/RgNC40LLQtdGCISBIZWxsbyE=', priority=4, ), Command( name='str_2_base85', uri='/execute?str_2_base85', description='Конвертация из текстовой строки в base85. Например: Бот, str_2_base85 Привет! Hello!', priority=3, ), Command( name='base85_2_str', uri='/execute?base85_2_str', description='Конвертация из строки в base85 в текстовую. ' 'Например: Бот, base85_2_str (4WzO(74dD(6!NmAs|R)Y;12K', priority=3 , ), Command( name='str_2_ascii85', uri='/execute?str_2_ascii85', description='Конвертация из текстовой строки в ascii85. Например: ' 'Бот, str_2_ascii85 https://en.wikipedia.org/wiki/Ascii85', priority=2, ), Command( name='ascii85_2_str', uri='/execute?ascii85_2_str', description="Конвертация из строки в ascii85 в текстовую. " "Например: Бот, ascii85_2_str BQS?8F#ks-ASs,EBkqF%ARoL`/oPcC06_,GBeMbn@qfX:2#", priority=2 , ), ] # Путь к файлу сервера file_name = os.path.abspath(__file__) name_by_func = { 'hex2str': hex2str, 'str2hex': str2hex, 'bin2str': bin2str, 'str2bin': str2bin, 'str_2_base64': lambda text: base64.b64encode(text.encode()).decode(), 'base64_2_str': lambda text: base64.b64decode(text.encode()).decode(), 'str_2_base85': lambda text: base64.b85encode(text.encode()).decode(), 'base85_2_str': lambda text: base64.b85decode(text.encode()).decode(), 'str_2_ascii85': lambda text: base64.a85encode(text.encode()).decode(), 'ascii85_2_str': lambda text: base64.a85decode(text.encode()).decode(), } def _execute_body(self, rq: dict, **params: dict) -> typing.Union[dict, str]: command = rq['command'] command_name = rq['command_name'] if not command: raise Exception("Неправильная команда '{}': не указан текст".format(command_name)) function_list = list(params.keys()) if not function_list: raise Exception("Неправильная команда '{}': не указана вызываемая функция, " "например 'hex2str'".format(command_name)) func_name = function_list[0] if func_name not in self.name_by_func: raise Exception("Неправильная команда '{}': не найдена функция '{}', доступны следующие " "функции: {}".format(command_name, func_name, ', '.join(self.name_by_func.keys()))) try: # Вызов функции по ее имени result = self.name_by_func[func_name](command) except Exception as e: import traceback print('Error: {}\n\n{}'.format(e, traceback.format_exc())) raise Exception('При выполнении команды "{}" произошла ошибка. ' 'Проверь что данные правильные. Текст ошибки: "{}".'.format(command_name, e)) return result
class TestAttachmentServer(BaseServer): name = 'TestAttachmentServer' guid = 'D2EA28FA35D244E5A36EECC5FA3EA759' command_list = [ Command( name='тест картинку', uri='/execute?' + common.AttachmentType.IMAGE, description='Возвращает тестовую картинку. Например: Бот, тест картинку', ), Command( name='тест много картинок', uri='/execute?' + common.AttachmentType.LIST_IMAGE, description='Возвращает несколько тестовых картинок. Например: Бот, тест много картинок', ), Command( name='тест гифку', uri='/execute?' + common.AttachmentType.GIF, description='Возвращает тестовую гифку. Например: Бот, тест гифку', ), ] # Путь к файлу сервера file_name = os.path.abspath(__file__) def _execute_body(self, rq: dict, **params: dict) -> typing.Union[dict, str]: command = rq['command'] command_name = rq['command_name'] function_list = list(params.keys()) func_name = function_list[0] import pathlib current_dir = pathlib.Path(__file__).parent result = None attachment_type = common.AttachmentType(func_name) if attachment_type == common.AttachmentType.IMAGE: file = current_dir / 'Jimm Kerry.jpg' result = file.name # 'Jimm Kerry.jpg' extension = file.suffix[1:] content = file.read_bytes() attachment = common.FileAttachment(content=content, extension=extension) elif attachment_type == common.AttachmentType.GIF: file = current_dir / 'Jimm Kerry.gif' result = 'Jimm Kerry.gif' extension = file.suffix[1:] content = file.read_bytes() attachment = common.FileAttachment(content=content, extension=extension) elif attachment_type == common.AttachmentType.LIST_IMAGE: attachment = [] for file in current_dir.glob('images/*.jpg'): extension = file.suffix[1:] content = file.read_bytes() file_attachment = common.FileAttachment(content=content, extension=extension) attachment.append(file_attachment) else: message = "Неправильная команда '{}': не найдена функция '{}', доступны следующие функции: {}" message = message.format( command_name, attachment_type.value, ', '.join([common.AttachmentType.IMAGE.value, common.AttachmentType.GIF.value, common.AttachmentType.LIST_IMAGE.value]), ) raise Exception(message) return self.generate_response(result=result, attachment=attachment, attachment_type=attachment_type)
class DuckDuckGoServer(BaseServer): name = 'DuckDuckGoServer' guid = 'F7C3D7520D434C82A28B48967F5513B6' command_list = [ Command( name='ddg', uri='/execute', description= 'Команда для поиска информации, используя api duckduckgo. Например: Бот, ddg металлика', priority=7, ), ] # Путь к файлу сервера file_name = os.path.abspath(__file__) def _execute_body(self, rq: dict, **params: dict) -> typing.Union[dict, str]: command = rq['command'] command_name = rq['command_name'] if not command: raise Exception( "Неправильная команда '{}': нужно указать выражение, например: металлика" .format(command_name)) from commands.command__duckduckgo import duckduckgo result = duckduckgo.query(command, kad='ru_RU') print('"{}" -> {}'.format(command, result.json)) try: # Пытаемся достать текст ответа и если произошла ошибка либо нет # содержимого пытаемся получить результат из других полей text = result.abstract.text.strip() if not text: raise Exception('time duckduckgo.get_zci!') except: return duckduckgo.get_zci(command, on_no_results='<Нет результатов>', kad='ru_RU') attachment = None attachment_type = None img_url = result.image.url if img_url: try: import requests rs = requests.get(img_url) content = rs.content extension = pathlib.Path(img_url).suffix[1:] attachment = common.FileAttachment(content=content, extension=extension) attachment_type = common.AttachmentType.IMAGE except: pass url = result.abstract.url if url: from urllib.parse import unquote url = unquote(url) text += ' (' + url + ')' text = text.strip() return self.generate_response(result=text, attachment=attachment, attachment_type=attachment_type)
class CoordinatorServer(BaseServer): name = 'CoordinatorServer' guid = 'B57B73C8F8D442C48EDAFC951963D7A5' command_list = [ Command(name='команды', uri='/execute', description='Возвращает топ10 команд', priority=999), Command(name='все команды', uri='/execute', description='Возвращает все команды', priority=998), ] # Путь к файлу сервера file_name = os.path.abspath(__file__) @BaseServer.expose @BaseServer.json_out def get_commands(self, as_result=None, max_number=None) -> dict: print(self.request.params) all_command_name_by_description = db.get_all_command_name_by_description( ) if as_result is not None: items = list(all_command_name_by_description.items())[:max_number] result = '\n'.join('✓ {}: {}'.format(k, v) for k, v in items) return self.generate_response(result=result) return all_command_name_by_description @BaseServer.expose @BaseServer.json_out def get_top10_commands(self, as_result=None) -> dict: return self.get_commands(as_result, max_number=10) def _execute_body(self, rq: dict, **params: dict) -> typing.Union[dict, str]: command = rq['command'] command_name = rq['command_name'] attachment = rq['attachment'] # Если команды нет, показываем список команд if not command.strip(): return self.get_top10_commands(as_result=True) # Приведение в нижний регистр чтобы проверка команды была регистронезависимой execute_command = command.lower() print('execute_command: "{}"'.format(execute_command)) all_command_by_url = db.get_all_command_name_by_url() command_name_list = [x.lower() for x in all_command_by_url.keys()] # Если текущая команда не была найдена среди списка команд хотя бы по совпадению начальной строки, # пытаемся найти, учитывая, что в ней могут быть опечатки, иначе ругаемся на неизвестную команду if not any(execute_command.startswith(x) for x in command_name_list): fix_execute_command = None for command_name in command_name_list: word_list = execute_command.split() for i in range(1, len(word_list) + 1): part_command_name = ' '.join(word_list[:i]) # Если нашли команду if command_name == fix_command(part_command_name, command_name_list): # Составляем команду с текстом fix_execute_command = ' '.join([command_name] + word_list[i:]) # Если это была опечатка, обновляем запрос команды, исправив опечатку if fix_execute_command is not None and execute_command != fix_execute_command: print('fix execute command: "{}" -> "{}"'.format( execute_command, fix_execute_command)) execute_command = fix_execute_command # Если не удалось разобрать команду как опечатку if fix_execute_command is None: result = 'Получена неизвестная команда "{}".\n' \ 'Чтобы узнать доступные команды введи: Бот, команды'.format(command) return result # Обработка собственных команд if execute_command == 'команды': return self.get_top10_commands(as_result=True) elif execute_command == 'все команды': return self.get_commands(as_result=True) for command_name, url in all_command_by_url.items(): if execute_command.startswith(command_name.lower()): command_text = command[len(command_name):].strip() print( 'Found server: {}, command name: "{}", command text: "{}"'. format(url, command_name, command_text)) rq = generate_request(command_name, command_text, attachment) print('Generate request:', rq) import requests try: rs = requests.post(url, json=rq) print('Response:', rs.text) return rs.json() except requests.exceptions.ConnectionError: error = 'Сервер команды "{}" ({}) недоступен'.format( command_name, url) return self.generate_response(error=error) error = 'Что-то пошло не так: команда "{}" не была распознана'.format( command) return self.generate_response(error=error) def _before_run(self): def _thread_func(): # Немного дадим времени серверам перед проверкой (особенно http-серверу самого координатора) time.sleep(2) while True: pool = ThreadPool() rows = pool.map(update_availability_server, db.get_all_server()) # Сортировка по NAME rows.sort(key=lambda x: x[0]) table = tabulate(rows, headers=('NAME', 'AVAILABILITY'), tablefmt="grid") # TODO: лучше это логировать. Можно и в отдельный файл, т.к. логировать будет много и часто print(table + '\n\n') # Иначе может не вывести сразу в консоль sys.stdout.flush() time.sleep(25) from threading import Thread thread = Thread(target=_thread_func) thread.start()