From c07cd997c9ad0d5c603a567b45320be24a9ffee5 Mon Sep 17 00:00:00 2001 From: Godnessli Date: Sun, 18 Oct 2026 15:44:55 +0300 Subject: [PATCH] added file --- .gitignore | 7 ++ answers.py | 75 ++++++++++++++++++ callbacks.py | 184 +++++++++++++++++++++++++++++++++++++++++++++ login.py | 112 +++++++++++++++++++++++++++ person.py | 84 +++++++++++++++++++++ pythontest | 1 - scp.py | 117 ++++++++++++++++++++++++++++ test.py | 52 +++++++++++++ unknown_unswers.py | 37 +++++++++ whether.py | 86 +++++++++++++++++++++ 10 files changed, 754 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 answers.py create mode 100644 callbacks.py create mode 100644 login.py create mode 100644 person.py delete mode 160000 pythontest create mode 100644 scp.py create mode 100644 test.py create mode 100644 unknown_unswers.py create mode 100644 whether.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f948989 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.json +*.txt +whether_token +*.sqlite +/crypto_store +/.vscode +/__pycache__ \ No newline at end of file diff --git a/answers.py b/answers.py new file mode 100644 index 0000000..6002cde --- /dev/null +++ b/answers.py @@ -0,0 +1,75 @@ +import psutil +import shutil +from whether import Whether +from scp import SCP +from random import randint +from unknown_unswers import ( + UNKNOWN_ANSWER, + UNKNOWN_PENTA, + UNKNOWN_TRIPLE +) + +unknown_count = 0 + +def weather_request() -> str: + global unknown_count + unknown_count = 0 + wh = Whether("Санкт-Петербург") + ret = wh.get_weather() + + return ret + +def system_request() -> str: + global unknown_count + unknown_count = 0 + ret = "Использование CPU: " + str(psutil.cpu_percent(interval=1)) + "%\n" + + ram = psutil.virtual_memory() + + ret += "Использование RAM: " + str(ram.percent) + "%\n" + ret += "Использовано RAM: " + str(round(ram.used / 1e9, 2)) + "ГБ\n" + + total, used, free = shutil.disk_usage("/") + + ret += "Использование диска:\n" + ret += "Всего: %d ГБ" % (total // (2**30)) + "\n" + ret += "Использовано: %d ГБ" % (used // (2**30)) + "\n" + ret += "Свободно: %d ГБ" % (free // (2**30)) + "\n" + + return ret + +def unknown_request() -> str: + global unknown_count + unknown_count += 1 + print(f"[DEBUG] UNKNOWN COUNT = {unknown_count}") + + if unknown_count < 3: + num = randint(0, len(UNKNOWN_ANSWER) - 1) + return UNKNOWN_ANSWER[num] + elif unknown_count < 5: + num = randint(0, len(UNKNOWN_TRIPLE) - 1) + return UNKNOWN_TRIPLE[num] + elif unknown_count >= 5: + num = randint(0, len(UNKNOWN_PENTA) - 1) + return UNKNOWN_PENTA[num] + +def help_request() -> str: + ret = "\n\nМеню:\n" \ + "1. Сервер — Данные об использовании сервера.\n" \ + "2. Погода в <город> — Погода в городе.=(тестово).\n" \ + "3. SCP — Выдача случайного объекта фонда для чтения.\n" \ + "4. SCP классы — Описание классов.\n\n" \ + "Можете писать слова(Пример: Сервер) или цифрой(Пример: 1)." + + return ret + +def scp_request() -> str: + global unknown_count + unknown_count = 0 + scp = SCP() + return scp.get_text() + +def scp_classed() -> str: + global unknown_count + unknown_count = 0 + return SCP.get_classified() \ No newline at end of file diff --git a/callbacks.py b/callbacks.py new file mode 100644 index 0000000..2f99764 --- /dev/null +++ b/callbacks.py @@ -0,0 +1,184 @@ +import login +import answers +import sys + +from nio import ( + MatrixRoom, + RoomMessageText, + InviteMemberEvent, + MegolmEvent, + KeyVerificationStart, + KeyVerificationKey, + KeyVerificationMac, + KeyVerificationCancel, + LocalProtocolError, + ToDeviceError +) + +WELCOME_MSG = "Добро пожаловать ко мне, УЕБОТУ! АХАХА\n" \ +"И так, вот что я умею:\n" \ +"1. Сервер — Данные об использовании сервера.\n" \ +"2. Погода в <город> — Погода в городе.=(тестово).\n" \ +"3. SCP — Выдача случайного объекта фонда для чтения.\n" \ +"4. SCP классы — Описание классов.\n" \ +"5. Помощь — Это меню.\n\n" \ +"Можете писать слова(Пример: Сервер) или цифрой(Пример: 1)." + +client = None + +async def message_callback(room: MatrixRoom, event: RoomMessageText): + print(f"[DEBUG] Открытое сообщение от {event.sender}.") + if event.sender == client.user_id: + return + + content = None + + if event.body.lower() == "сервер" or event.body == "1": + content = answers.system_request() + elif event.body.lower() == "погода" or event.body == "2": + content = answers.weather_request() + elif event.body.lower() == "scp" or event.body == "3": + content = answers.scp_request() + elif event.body.lower() == "scp классы" or event.body == "4": + content = answers.scp_classed() + else: + content = answers.unknown_request() + + content += answers.help_request() + + await send_message(room, content) + + +async def megolm_callback(room: MatrixRoom, event: MegolmEvent): + try: + decrypted = await client.decrypt_event(event) + except Exception as e: + print(f"[ERROR] Расшифровка не удалась: {e}") + return + + if isinstance(decrypted, RoomMessageText): + print(f"[DEBUG] Расшифровано: {decrypted.body}") + if decrypted.sender == client.user_id: + return + await send_message(room, decrypted.body) + else: + print(f"[DEBUG] Расшифровано, но не текст: {type(decrypted).__name__}") + + +async def invite_callback(room: MatrixRoom, event: InviteMemberEvent): + print(f"[INFO] Приглашение в комнату {room.room_id}") + await client.join(room.room_id) + await send_message(room, WELCOME_MSG) + + +async def trust_user_devices(user_id: str, device_ids=None): + """Доверие устройствам пользователя (локально, без интерактивной верификации).""" + if user_id == client.user_id: + return + if user_id not in client.device_store: + print(f"[WARN] Устройства {user_id} пока не известны.") + return + + devices = client.device_store[user_id] + trusted = 0 + for device_id, olm_device in devices.items(): + if device_ids and device_id not in device_ids: + continue + try: + client.verify_device(olm_device) + print(f"[OK] Доверено устройство {device_id} пользователя {user_id}.") + trusted += 1 + except Exception as e: + print(f"[ERROR] Не удалось доверить {device_id}: {e}") + if trusted == 0: + print(f"[WARN] Ни одно устройство {user_id} не было доверено.") + +async def send_message(room: MatrixRoom, text: str): + CHUNK = 5000 + if len(text) > CHUNK: + start = 0 + end = 0 + while start < len(text): + end = start + CHUNK + if end < len(text): + while text[end] != ".": + end -= 1 + + end += 1 + chunk = text[start:end] + await client.room_send( + room_id = room.room_id, + message_type = "m.room.message", + content = {"msgtype" : "m.text", "body" : chunk}, + ignore_unverified_devices = True + ) + + start = end + print(start) + else: + await client.room_send( + room_id = room.room_id, + message_type = "m.room.message", + content = {"msgtype" : "m.text", "body" : text}, + ignore_unverified_devices = True + ) + +async def after_first_sync(): + print("[INFO] Ожидание первого синка...") + await client.synced.wait() + print("[INFO] Первый синк выполнен. Доверяем устройствам...") + for user_id in login.TRUST_USERS: + await trust_user_devices(user_id) + +async def to_device_callback(event): + """Автоматически принимает и подтверждает SAS-верификацию от доверенных пользователей.""" + try: + if isinstance(event, KeyVerificationStart): + if event.sender not in login.TRUST_USERS: + print(f"[WARN] Верификация от недоверенного пользователя {event.sender}, игнорируем.") + return + if "emoji" not in event.short_authentication_string: + print(f"[WARN] Устройство {event.sender} не поддерживает эмодзи-верификацию.") + return + + print(f"[INFO] Принимаем запрос на верификацию от {event.sender}...") + resp = await client.accept_key_verification(event.transaction_id) + if isinstance(resp, ToDeviceError): + print(f"[ERROR] accept_key_verification: {resp}") + return + + sas = client.key_verifications[event.transaction_id] + todevice_msg = sas.share_key() + resp = await client.to_device(todevice_msg) + if isinstance(resp, ToDeviceError): + print(f"[ERROR] to_device (share_key): {resp}") + + elif isinstance(event, KeyVerificationKey): + sas = client.key_verifications[event.transaction_id] + print(f"[INFO] Эмодзи: {sas.get_emoji()}") + # Автоматически подтверждаем совпадение + resp = await client.confirm_short_auth_string(event.transaction_id) + if isinstance(resp, ToDeviceError): + print(f"[ERROR] confirm_short_auth_string: {resp}") + else: + print("[INFO] SAS подтверждён автоматически.") + + elif isinstance(event, KeyVerificationMac): + sas = client.key_verifications[event.transaction_id] + try: + todevice_msg = sas.get_mac() + except LocalProtocolError as e: + print(f"[WARN] Верификация прервана: {e}") + return + resp = await client.to_device(todevice_msg) + if isinstance(resp, ToDeviceError): + print(f"[ERROR] to_device (mac): {resp}") + else: + print(f"[OK] Верификация с {event.sender} завершена. " + f"Доверенные устройства: {sas.verified_devices}") + + elif isinstance(event, KeyVerificationCancel): + print(f"[INFO] Верификация отменена: {event.reason}") + + except Exception as e: + print(f"[ERROR] to_device_callback: {e}") diff --git a/login.py b/login.py new file mode 100644 index 0000000..b17a15a --- /dev/null +++ b/login.py @@ -0,0 +1,112 @@ +import getpass +import json +import os +import sys + +from nio import ( + AsyncClient, + AsyncClientConfig, + LoginResponse +) + +CONFIG_FILE = "./credentials.json" +STORE_PATH = "./crypto_store" +TRUST_USERS = ["@tema:godnessli.ru"] + + +async def login_from_credentials() -> AsyncClient: + """ + Аутентификация: + - если credentials.json есть — используем сохранённые данные; + - если нет — спрашиваем у пользователя и логинимся по паролю, + после чего сохраняем данные в credentials.json. + Логика взята из официального примера matrix-nio verify_with_emoji.py. + """ + client_config = AsyncClientConfig( + max_limit_exceeded=0, + max_timeouts=0, + store_sync_tokens=True, + encryption_enabled=True, + ) + + if not os.path.exists(STORE_PATH): + os.makedirs(STORE_PATH) + + # ===== Вариант 1: файла нет — логинимся по паролю ===== + if not os.path.exists(CONFIG_FILE): + print("Файл credentials.json не найден. Входим по паролю.") + + homeserver = input("Введите URL homeserver [https://matrix.example.org]: ").strip() + if not homeserver: + homeserver = "https://matrix.example.org" + if not (homeserver.startswith("https://") or homeserver.startswith("http://")): + homeserver = "https://" + homeserver + + user_id = input("Введите полный user ID [@user:example.org]: ").strip() + + device_name = input("Имя устройства [echo-bot]: ").strip() + if not device_name: + device_name = "echo-bot" + + password = getpass.getpass("Пароль: ") + + client = AsyncClient( + homeserver, + user_id, + store_path=STORE_PATH, + config=client_config, + ) + + resp = await client.login(password=password, device_name=device_name) + + if not isinstance(resp, LoginResponse): + print(f"[FATAL] Не удалось войти: {resp}") + sys.exit(1) + + # Сохраняем данные в credentials.json + with open(CONFIG_FILE, "w") as f: + json.dump( + { + "homeserver": homeserver, + "user_id": resp.user_id, + "device_id": resp.device_id, + "access_token": resp.access_token, + }, + f, + indent=2, + ) + print(f"[INFO] Вход выполнен. Данные сохранены в {CONFIG_FILE}.") + print(f"[INFO] user_id={resp.user_id}, device_id={resp.device_id}") + + # После login нужно вызвать restore_login, чтобы установить + # user_id/device_id/access_token и загрузить хранилище E2EE + client.restore_login( + user_id=resp.user_id, + device_id=resp.device_id, + access_token=resp.access_token, + ) + return client + + # ===== Вариант 2: файл есть — используем сохранённые данные ===== + with open(CONFIG_FILE, "r") as f: + config = json.load(f) + + client = AsyncClient( + config["homeserver"], + config["user_id"], + device_id=config["device_id"], + store_path=STORE_PATH, + config=client_config, + ) + + client.restore_login( + user_id=config["user_id"], + device_id=config["device_id"], + access_token=config["access_token"], + ) + + print( + f"[INFO] Вход по сохранённым данным: " + f"{config['user_id']} / {config['device_id']}" + ) + return client \ No newline at end of file diff --git a/person.py b/person.py new file mode 100644 index 0000000..5cd64aa --- /dev/null +++ b/person.py @@ -0,0 +1,84 @@ +import time +import json + +class Person: + __ACCOUNTS_FILE = "accounts.json" + __DOMAIN = "godnessli.ru" + + def __init__(self, account: str, admin = False): + if self.__check_person(account): + self.__read_person(account) + else: + self.__create_person(account, admin) + + def get_account(self) -> str: + return self.__account + + def get_unknown_answers(self) -> int: + return self.__unknown_count + + def clear_answers(self): + self.__unknown_count = 0 + + def incr_account(self): + self.__unknown_count += 1 + + def __check_person(self, account: str) -> bool: + accounts = None + try: + accounts = self.__get_accounts() + except FileNotFoundError: + return False + + for acc in accounts: + if acc == account: + return True + + return False + + def __read_person(self, account: str): + accounts = self.__get_accounts() + + acc = accounts[account] + + self.__nick_name = acc["nick"] + self.__admin = acc["admin"] + self.__unknown_counter = acc["u_count"] + self.__whether_counter = acc["w_count"] + self.__ideas = acc["ideas"] + + def __create_person(self, account: str, admin: bool): + accounts = None + try: + accounts = self.__get_accounts() + except FileNotFoundError: + pass + with open(self.__ACCOUNTS_FILE, 'w', encoding = 'utf-8') as p_file: + begin = account.find("@") + end = account.find(self.__DOMAIN) + + accounts[account] = { + "nick" : account[begin:end], + "admin" : admin, + "u_count" : 0, + "w_count" : 0, + "ideas" : [] + } + + p_file.write(accounts) + + acc = accounts[account] + self.__nick = acc["nick"] + self.__admin = acc["admin"] + self.__unknown_counter = acc["u_count"] + self.__whether_counter = acc["w_count"] + self.__ideas = acc["ideas"] + + def __get_accounts(self) -> dict: + with open(self.__ACCOUNTS_FILE, 'r', encoding = 'utf-8') as p_file: + accounts = json.load(p_file) + p_file.close() + + return accounts + +tema = Person("@tema:godnessli") diff --git a/pythontest b/pythontest deleted file mode 160000 index 5ec0246..0000000 --- a/pythontest +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5ec0246c955858bb0552b118a084d571bd76c025 diff --git a/scp.py b/scp.py new file mode 100644 index 0000000..3c9d04f --- /dev/null +++ b/scp.py @@ -0,0 +1,117 @@ +import os +import random +import subprocess + +from bs4 import BeautifulSoup + +class SCP: + __ANUBIS = os.path.expanduser("~/.nix-profile/bin/anubis-fetch") + + def __init__(self, num = None): + self.__scp = { + "title" : "SCP Foundation" + } + + self_num = False + + if num != None: + self_num = True + + while self.__scp["title"] == "SCP Foundation": + if num == None: + num = random.randint(1, 4000) + + url = "" + + if num == 1: + self.__scp = { + "title" : "SCP-001", + "text" : "ПРИВЕДЁННЫЕ НИЖЕ МАТЕРИАЛЫ ЗАСЕКРЕЧЕНЫ\nПРИКАЗОМ АДМИНИСТРАТОРА\nСОВЕРШЕННО СЕКРЕТНО" + } + return + + if num < 10: + url = f"https://scpfoundation.net/scp-00{num}" + elif num < 100: + url = f"https://scpfoundation.net/scp-0{num}" + else: + url = f"https://scpfoundation.net/scp-{num}" + + print(url) + + scp_html = self.__fetch_with_anubis(url) + self.__scp = self.__parse_html(scp_html) + + if self_num: + break + else: + num = None + + + def get_text(self) -> str: + return f"{self.__scp["title"]}\n{self.__scp["text"]}" + + @staticmethod + def get_classified() -> str: + return "Основные классы SCP:\n" \ + "«Безопасный» — это аномалии, которые проще всего содержать без последствий.\n" \ + "«Евклид» — относятся недостаточно изученные или изначально непредсказуемые аномалии, надёжное" \ + "содержание которых не всегда возможно или требует бо́льших затрат, чем на содержание «безопасных».\n" \ + "Вторичные классы:\n" \ + "«Кетер» — это аномалии, постоянное или надёжное содержание которых тяжело реализуемо и сопряжено со сложными процедурами сдерживания.\n" \ + "«Таумиэль» применяются Фондом исключительно для содержания других аномалий.\n" \ + "«Аполлион» являются аномалиями, которые или невозможно содержать, и они неминуемо нарушат" \ + "условия содержания, или предполагается развитие событий по схожему сценарию.\n" \ + "«Архонт» являются аномалиями, чье содержание теоретически возможно, но по каким-либо причинам их лучше не содержать.\n" \ + "«Тикондерога» невозможно содержать, но в этом и нет необходимости." \ + "Эти аномалии схожи с объектами класса «Архонт» в том, что их содержание не обязательно," \ + "но отличны тем, что на данный момент для их содержания у Фонда недостаточно ресурсов и " \ + "компетенции из-за повсеместной распространённости таких аномалий по Земле." \ + "При этом их существование или аномальная природа непостижимы для широких масс. " + + def __fetch_with_anubis(self, url: str) -> str: + try: + result = subprocess.run( + [self.__ANUBIS, url], + capture_output = True, text = True, check = True + ) + + return result.stdout + except subprocess.CalledProcessError as e: + print(f"anubis-fetch завершился с кодом {e.returncode}: {e.stderr}") + raise + except subprocess.TimeoutExpired: + print("anubis-fetch не успел завершиться за отведённое время") + raise + + def __parse_html(self, text: str) -> dict: + soup = BeautifulSoup(text, "lxml") + + for tag in soup(["script", "style", "nostyle"]): + tag.decompose() + + content = soup.find(id = "page-content") or soup.find("div", id = "content") + + parse_text = content.get_text("\n", strip = True) if content else None + + if parse_text: + marker = "Класс объекта:" + indx = parse_text.find(marker) + if indx != -1: + parse_text = parse_text[indx:] + else: + marker = "Объект №:" + indx = parse_text.find(marker) + if indx != -1: + parse_text = parse_text[indx:] + + marker = "«\n" + indx = parse_text.find(marker) + if indx != -1: + parse_text = parse_text[:indx] + + return { + "title" : soup.title.string.strip() if soup.title else None, + #"heading" : content.find("h1").get_text(strip = True) if content and content.find("h1") else None, + "text" : parse_text + } \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..f068540 --- /dev/null +++ b/test.py @@ -0,0 +1,52 @@ +import asyncio +import sys +import traceback +import login +import callbacks + +from nio import ( + RoomMessageText, + InviteMemberEvent, + MegolmEvent, + KeyVerificationEvent +) + +async def main(): + callbacks.client = await login.login_from_credentials() + + # Критично для E2EE: загружаем ключи бота на сервер, + # чтобы другие устройства могли с ним общаться + if callbacks.client.should_upload_keys: + await callbacks.client.keys_upload() + print("[INFO] Ключи E2EE загружены на сервер.") + + # Регистрируем обработчики + callbacks.client.add_event_callback(callbacks.message_callback, RoomMessageText) + callbacks.client.add_event_callback(callbacks.megolm_callback, MegolmEvent) + callbacks.client.add_event_callback(callbacks.invite_callback, InviteMemberEvent) + callbacks.client.add_to_device_callback(callbacks.to_device_callback, KeyVerificationEvent) + + print("[INFO] Эхо-бот запущен. Для выхода нажмите Ctrl+C.") + + after_sync_task = asyncio.ensure_future(callbacks.after_first_sync()) + sync_task = asyncio.ensure_future( + callbacks.client.sync_forever(timeout=30000, full_state=True) + ) + + try: + await asyncio.gather(after_sync_task, sync_task) + except asyncio.CancelledError: + pass + finally: + await callbacks.client.close() + print("[INFO] Клиент закрыт.") + + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + print("\n[INFO] Остановка бота.") + except Exception: + print(traceback.format_exc()) + sys.exit(1) \ No newline at end of file diff --git a/unknown_unswers.py b/unknown_unswers.py new file mode 100644 index 0000000..7dd8116 --- /dev/null +++ b/unknown_unswers.py @@ -0,0 +1,37 @@ +UNKNOWN_ANSWER = [ + "Я вас не понял. Неверный пункт меню.", + "Что это? Давай без импровизации...", + "ААААА ПАМАГИТЕ! Я НЕ ПОНИЛ! ВВАДИ ТО, ЧТО ЕСТЬ НИЖЕ", + "Я тоже умею писать что-то не понятное!\n1000100101111001100101011101", + "Да, я написан на Python. Оу, вы не это спрашивали, тогда...", + "Нет, все не так, убийца дворецкий. А, вы не этого хотели? Так и я не этого ждал...", + "Ваш запрос не имеет смысла! Давайте я помогу...", + "Стакан наполовину пуст... Кстати, я не понял, что вы хотели.", + "Стакан наполовину полон! :) И в честь этого...", + "Все что имеет начало, имеет и..." +] + +UNKNOWN_TRIPLE = [ + "Тебе заняться нечем?", + "Ну давай! Я подожду, пока ты соизволишь ткнуть туда, куда надо...", + "Ты издеваешься?", + "Я тебе что, шутка какая-то?", + "Слушай, опусти глаза, там меню. Ты справишься...", + "А ты упертый...", + "Пиши, пиши... я подожду тут...", + "Интересно, что будет?" +] + +UNKNOWN_PENTA = [ + "И так... Видимо ты очень хочешь эту кнопку...\n@tema:godnessli.ru\nЭто мой создатель, напиши ему...", + "Серьезно? Зачем ты столько раз промазал мимо меню? Это уже абсурд!", + "А я хочу, а я хочу опять... О! Ты соизволил взглянуть на текст, тогда опсти чуть глаза и нажми то, что есть в меню.", + "...ну это уже не смешно...", + "Раз ты так хочешь эту кнопку, то я скажу создателю, чтобы когда будет на нее кто-то нажимать, тебе будет присылаться ЗВУК ПУКА!", + "Я устал, босс... выбери что-то из меню...", + "АААААА, Я БЕШЕНЫЙ, БЕГИТЕ!!!", + "@tema:godnessli.ru\nС просьбой добавить кнопку к нему.", + "Я не хочу с тобой общаться...", + "Ты думаешь, это смешно?", + "Успокоился?" +] \ No newline at end of file diff --git a/whether.py b/whether.py new file mode 100644 index 0000000..0b0ecbb --- /dev/null +++ b/whether.py @@ -0,0 +1,86 @@ +from geopy import Nominatim +import requests +import json +import datetime + +class Whether: + def __init__(self, city: str): + gn = Nominatim(user_agent = "kzntsvartem@gmail.com") + self.__location = gn.geocode(city) + + def get_weather(self) -> str: + url = f'https://api.weather.yandex.ru/v2/forecast?lat={self.__location.latitude}&lon={self.__location.longitude}' + access_key = open("whether_token").read() + + header = { + 'X-Yandex-Weather-Key' : access_key + } + + #response = requests.get(url, headers = header) + #with open('data.json', 'w', encoding='utf-8') as f: + # json.dump(response.json(), f, ensure_ascii=False, indent=4) + #print(json.dumps(r, indent = 4)) + + with open('data.json') as json_data: + d = json.load(json_data) + json_data.close() + + fact = d["fact"] + + print(type(fact["cloudness"])) + + string = "Погода сейчас:\n" + string += self.__get_cloudness(fact["cloudness"]) + string += self.__get_temp(fact["temp"], fact["feels_like"]) + string += self.__get_humidity(fact["humidity"]) + string += self.__get_wind(fact["wind_dir"], fact["wind_speed"], fact["wind_gust"]) + string += self.__get_prec(fact["prec_type"], fact["prec_strength"]) + + return string + return "Что-то пошло не так :(" + + def __get_cloudness(self, cloudness: float) -> str: + text = "" + if cloudness >= 0.8: + text += "Пасмурно" + elif cloudness >= 0.4: + text += "Облачно с прояснениями" + elif cloudness >= 0.1: + text += "Малооблачно" + else: + text += "Ясно" + + text += "\n" + return text + + def __get_humidity(self, humidity: int) -> str: + text = f"Влажность: {humidity}% (" + + if humidity == 100: + text += "Кто проживает на дне океана?)" + elif humidity >= 90: + text += "Очень высокая" + elif humidity >= 70: + text += "Высокая" + elif humidity >= 50: + text += "Средняя" + elif humidity >= 30: + text += "Низкая" + else: + text += "Сушь галимая" + + text += ")\n" + + return text + + def __get_temp(self, temp: int, feels: int) -> str: + text = f"Температура: {temp}°C\n" + text += f"(Ощущается как: {feels}°C)\n" + + return text + + def __get_wind(self, dir: str, speed: float, gust: float) -> str: + return "" + + def __get_prec(self, type: int, strength: float) -> str: + return "" \ No newline at end of file