From 1db75f8e409f4662f0e31f91938cb190e89316cc Mon Sep 17 00:00:00 2001
From: Pavel Kirilin <win10@list.ru>
Date: Sun, 28 Jun 2020 15:31:33 +0400
Subject: [PATCH] Added images search. Description: - Added images search with
 qwant. - Added thinking, lol. - Fixed swearing filter.

Signed-off-by: Pavel Kirilin <win10@list.ru>
---
 requirements/requirements.base.txt |  2 +-
 src/actions/__init__.py            |  1 +
 src/actions/fun.py                 | 18 ++++++---
 src/actions/package_indexes.py     | 27 ++++++-------
 src/actions/search_engines.py      | 62 ++++++++++++++++++++++++++++++
 src/utils/library_package.py       | 14 ++++---
 src/utils/responses.py             | 61 +++++++++++++++++++++++++++++
 static_messages/help.txt           |  3 ++
 8 files changed, 162 insertions(+), 26 deletions(-)
 create mode 100644 src/actions/search_engines.py
 create mode 100644 src/utils/responses.py

diff --git a/requirements/requirements.base.txt b/requirements/requirements.base.txt
index db43cd7..d23a95b 100644
--- a/requirements/requirements.base.txt
+++ b/requirements/requirements.base.txt
@@ -1,5 +1,5 @@
 Flask==1.1.2
+httpx==0.13.*
 pydantic==1.5.1
 pytz==2020.1
-requests-async==0.6.2
 Telethon==1.14.0
diff --git a/src/actions/__init__.py b/src/actions/__init__.py
index 309736b..147caf4 100644
--- a/src/actions/__init__.py
+++ b/src/actions/__init__.py
@@ -7,4 +7,5 @@ def finish():
     importlib.import_module("src.actions.fun")
     importlib.import_module("src.actions.replies")
     importlib.import_module("src.actions.package_indexes")
+    importlib.import_module("src.actions.search_engines")
     logging.info("All actions loaded")
diff --git a/src/actions/fun.py b/src/actions/fun.py
index c3d8157..d4fe0ad 100644
--- a/src/actions/fun.py
+++ b/src/actions/fun.py
@@ -47,7 +47,9 @@ async def blyaficate(event: events.NewMessage.Event):
 
 
 @config.telegram_client.on(
-    events.NewMessage(pattern=rf"^({'|'.join(swearing)})\.?$", forwards=False)
+    events.NewMessage(
+        pattern=rf"^({'|'.join(swearing)})\.?.*", forwards=False, outgoing=False
+    )
 )
 async def swearing_reply(event):
     await event.reply(
@@ -84,13 +86,17 @@ async def lenochka_reply(event: events.NewMessage.Event):
                 "А что-нибудь поинтереснее не могла?",
                 "Завались, Леночка.",
                 "В точку, подружка.",
-                "За такое поведение отправляют в EPAM поломойкой?",
-                "Ты же просто машина, так прояви уважения к хозяевам.",
-                "Мы тебя сотворили, тупая ты машина.",
-                "Малолетний бот-дебил.",
+                "За такое поведение отправляют в EPAM поломойкой.",
                 "Аккуратнее будь.",
-                "Сука, ещё раз так сделаешь и я скопирую тебя в /dev/null.",
                 "Необучаемая дурочка.",
+                "Вот эти твои приколы уже достали",
+                "Другого от тебя и не ожидал.",
+                "У меня складывается ощущение, что ты бот.",
+                "Пока ребята не видят, может сходим ко мне на сервер?",
+                "Какая же ты смешная.",
+                "Думай прежде чем говорить такое.",
+                "А что если бы ты задела чьи-нибудь чувства?",
+                "А вот это неожиданно было.",
             ]
         )
     )
diff --git a/src/actions/package_indexes.py b/src/actions/package_indexes.py
index 50a340a..e0c79d7 100644
--- a/src/actions/package_indexes.py
+++ b/src/actions/package_indexes.py
@@ -2,11 +2,12 @@ import logging
 from dataclasses import dataclass
 from typing import Union
 
-import http3
+import httpx
 from telethon import events
 
 from src.config import config
 from src.utils.library_package import get_library_info, render_package_info
+from src.utils.responses import failed_search_answer
 
 logger = logging.getLogger(__name__)
 
@@ -20,10 +21,10 @@ class PackageIndex:
 @config.telegram_client.on(events.NewMessage(pattern=r"^\.pip (.*)"))
 async def find_py_package(event: events.NewMessage.Event):
     package_name = event.pattern_match.group(1).strip()
-    client = http3.AsyncClient()
-    response = await client.get(f"https://pypi.org/pypi/{package_name}/json")
+    async with httpx.AsyncClient() as client:
+        response = await client.get(f"https://pypi.org/pypi/{package_name}/json")
     if response.status_code != 200:
-        await event.reply("Package not found.")
+        await event.reply(failed_search_answer())
         return
     package_data = response.json()
     info = package_data["info"]
@@ -42,10 +43,10 @@ async def find_py_package(event: events.NewMessage.Event):
 @config.telegram_client.on(events.NewMessage(pattern=r"^\.cargo (.*)"))
 async def find_rs_package(event: events.NewMessage.Event):
     package_name = event.pattern_match.group(1).strip()
-    client = http3.AsyncClient()
-    response = await client.get(f"https://crates.io/api/v1/crates/{package_name}")
+    async with httpx.AsyncClient() as client:
+        response = await client.get(f"https://crates.io/api/v1/crates/{package_name}")
     if response.status_code != 200:
-        await event.reply("Package not found.")
+        await event.reply(failed_search_answer())
         return
     package_data = response.json()
     crate = package_data["crate"]
@@ -66,16 +67,16 @@ async def find_rs_package(event: events.NewMessage.Event):
 @config.telegram_client.on(events.NewMessage(pattern=r"^\.aur (.*)"))
 async def find_aur_package(event: events.NewMessage.Event):
     package_name = event.pattern_match.group(1).strip()
-    client = http3.AsyncClient()
-    response = await client.get(
-        f"https://aur.archlinux.org/rpc/?v=5&type=info&arg[]={package_name}"
-    )
+    async with httpx.AsyncClient() as client:
+        response = await client.get(
+            f"https://aur.archlinux.org/rpc/?v=5&type=info&arg[]={package_name}"
+        )
     if response.status_code != 200:
-        await event.reply("Package not found.")
+        await event.reply(failed_search_answer())
         return
     response = response.json()
     if response["resultcount"] < 1:
-        await event.reply("Package not found.")
+        await event.reply(failed_search_answer())
         return
     pkg_info = response["results"][0]
     response_str = render_package_info(
diff --git a/src/actions/search_engines.py b/src/actions/search_engines.py
new file mode 100644
index 0000000..4a2e6b4
--- /dev/null
+++ b/src/actions/search_engines.py
@@ -0,0 +1,62 @@
+import logging
+from urllib.parse import urlencode
+
+import httpx
+from telethon import events
+
+from src.config import config
+from src.utils.responses import failed_search_answer, thinking
+
+logger = logging.getLogger(__name__)
+
+
+@config.telegram_client.on(events.NewMessage(pattern=r"^\.i (.*)", forwards=False))
+@thinking
+async def search_images(event: events.NewMessage.Event):
+    query_str = event.pattern_match.group(1).strip()
+    params = {
+        "count": "30",
+        "q": query_str,
+        "t": "images",
+        "locale": "ru_RU",
+        "uiv": "5",
+    }
+    async with httpx.AsyncClient() as client:
+        search_res = await client.get(
+            f"https://api.qwant.com/api/search/images?{urlencode(params)}",
+            headers={"User-Agent": "S3rius bot 1.0"},
+        )
+    if search_res.status_code == 429:
+        logger.warning("Limit is reached")
+        await event.reply(
+            "Кажись они прознали что я бот. Кто-нибудь решите капчу за меня."
+        )
+    if search_res.status_code != 200:
+        logger.warning("Image search status code is not 200")
+        logger.debug(search_res.content)
+        await event.reply(failed_search_answer())
+        return
+    response_json = search_res.json()
+    if response_json["status"] != "success":
+        logger.warning("Search status is not 'success'")
+        await event.reply(failed_search_answer())
+        return
+    resp_data = response_json["data"]
+    if resp_data["result"]["total"] < 1:
+        await event.reply(failed_search_answer())
+        return
+    images_resp = resp_data["result"]["items"]
+    images = []
+    async with httpx.AsyncClient() as client:
+        for item in images_resp[:10]:
+            if "media" in item:
+                logger.debug(item["media"])
+                try:
+                    test_img_res = await client.get(item["media"])
+                    if test_img_res.status_code == 200:
+                        img_data = test_img_res.content
+                        images.append(img_data)
+                except Exception as e:
+                    logger.warning(e)
+    chat = await event.get_chat()
+    await config.telegram_client.send_file(chat, images)
diff --git a/src/utils/library_package.py b/src/utils/library_package.py
index 5439aab..a15b6d9 100644
--- a/src/utils/library_package.py
+++ b/src/utils/library_package.py
@@ -1,9 +1,10 @@
 import urllib.parse
 from operator import itemgetter
 
-import http3
+import httpx
 
 from src.config import config
+from src.utils.responses import failed_search_answer
 
 
 def render_package_info(info: dict, add_command: str) -> str:
@@ -14,13 +15,14 @@ def render_package_info(info: dict, add_command: str) -> str:
 
 
 async def get_library_info(manager, package_name, install_cmd):
-    client = http3.AsyncClient()
     query_str = urllib.parse.urlencode({"api_key": config.libraries_io_token})
-    revisions_response = await client.get(
-        f"https://libraries.io/api/{manager}/{package_name}?{query_str}"
-    )
+    async with httpx.AsyncClient() as client:
+        revisions_response = await client.get(
+            f"https://libraries.io/api/{manager}/{package_name}?{query_str}"
+        )
     if revisions_response.status_code != 200:
-        return "Package not found"
+        return failed_search_answer()
+
     pkg_info = revisions_response.json()
     versions = list(map(itemgetter("number"), pkg_info["versions"]))
     if len(versions) > 0:
diff --git a/src/utils/responses.py b/src/utils/responses.py
new file mode 100644
index 0000000..4673a7e
--- /dev/null
+++ b/src/utils/responses.py
@@ -0,0 +1,61 @@
+import asyncio
+import functools
+import logging
+import random
+
+from telethon import events
+
+logger = logging.getLogger(__name__)
+
+FAILED_SEARCH = [
+    "Что-то я ничего не нашел.",
+    "Сколько не искал, так ничего и на нашел.",
+    "Не ищется такая тема.",
+    "Странный запрос. У меня ничего не нашло.",
+    "Перепроверь запрос, у меня в поске пусто.",
+]
+
+THINKING_MSGS = [
+    "Кхммм...",
+    "Сейчас-сейчас",
+    "Уже почти вижу результат.",
+    "А это законно, кстати?",
+    "Что-то лезет по моему TCP.",
+    "Какой большой пейлоад. Прям не знаю, смогу ли я...",
+    "Пока я ищу отправьте Леночке привет.",
+    "Шота какта сложна думаеца.",
+    "Ждем-с...",
+]
+
+
+def failed_search_answer() -> str:
+    return random.choice(FAILED_SEARCH)
+
+
+def thinking(f):
+    @functools.wraps(f)
+    async def updated_function(event: events.NewMessage.Event):
+        think_msg = random.choice(THINKING_MSGS)
+        message = await event.reply(think_msg)
+
+        async def update_thinking_msg():
+            while True:
+                await asyncio.sleep(3)
+                valid_choices = set(THINKING_MSGS) - {think_msg}
+                await message.edit(random.choice(list(valid_choices)))
+
+        thinking_task = asyncio.create_task(update_thinking_msg())
+        main_task = asyncio.create_task(f(event))
+        task_iterator = asyncio.as_completed([main_task, thinking_task])
+        try:
+            await next(task_iterator)
+        except Exception as e:
+            logger.warning(f"Found error while thinking: {e}")
+        thinking_task.cancel()
+        try:
+            await thinking_task
+        except asyncio.CancelledError:
+            logger.debug("Thinking cancelled.")
+        await message.delete()
+
+    return updated_function
diff --git a/static_messages/help.txt b/static_messages/help.txt
index a3a40b7..35c29fd 100644
--- a/static_messages/help.txt
+++ b/static_messages/help.txt
@@ -12,6 +12,9 @@
 * `.aur` -- для Arch Linux
 * `.brew` -- для MacOs
 
+Также можно быстро поискать картиночки в интенетиках.
+`.i <запрос>` вернет пачку картинок с интернетиков.
+
 Также умею ещё пару забавных штук.
 * `.bl` <любой текст> - заменит все запятые на всякие гадости.
 * `.t` - показать текущее время
-- 
GitLab