<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
	<id>http://digida.mgpu.ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=CherenkovIR</id>
	<title>Поле цифровой дидактики - Вклад [ru]</title>
	<link rel="self" type="application/atom+xml" href="http://digida.mgpu.ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=CherenkovIR"/>
	<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php/%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/CherenkovIR"/>
	<updated>2026-07-17T13:04:23Z</updated>
	<subtitle>Вклад</subtitle>
	<generator>MediaWiki 1.44.0</generator>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=Unsplash&amp;diff=45342</id>
		<title>Unsplash</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=Unsplash&amp;diff=45342"/>
		<updated>2026-03-26T14:41:43Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{DigitalTool&lt;br /&gt;
|Description=Unsplash — это популярный бесплатный фотосток и онлайн-платформа, предоставляющая доступ к миллионам высококачественных фотографий в высоком разрешении.&lt;br /&gt;
|Affordances=Скачивание фото для коммерческого и личного использования без авторских отчислений, интеграцию через API в приложения, использование плагинов, а также загрузку собственных работ для продвижения.&lt;br /&gt;
|Field_of_knowledge=Информатика, Медиа&lt;br /&gt;
|Область применения=Образование&lt;br /&gt;
|Website=https://unsplash.com&lt;br /&gt;
|End users=Учащиеся, Преподаватели, Исследователи, Организации, Разработчики&lt;br /&gt;
|distant_collab=Да&lt;br /&gt;
|Language_Ru_Eng=English&lt;br /&gt;
|AI=Да&lt;br /&gt;
}}&lt;br /&gt;
==== Пример работы с API фотостока ====&lt;br /&gt;
&lt;br /&gt;
Одним примером из работы с API является работа с фотостоками, в котором описан полноценный цикл работы с API и созданием бота в Telegram с интеграцией поиска фотографий с сайта-фотостока.&lt;br /&gt;
&lt;br /&gt;
Посмотреть статью можно ниже по ссылке:&lt;br /&gt;
&lt;br /&gt;
[[Телеграм-бот для поиска фотографий через API-ключ фотостока]]&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=Unsplash&amp;diff=45341</id>
		<title>Unsplash</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=Unsplash&amp;diff=45341"/>
		<updated>2026-03-26T14:40:30Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{DigitalTool&lt;br /&gt;
|Description=Unsplash — это популярный бесплатный фотосток и онлайн-платформа, предоставляющая доступ к миллионам высококачественных фотографий в высоком разрешении.&lt;br /&gt;
|Affordances=Скачивание фото для коммерческого и личного использования без авторских отчислений, интеграцию через API в приложения, использование плагинов, а также загрузку собственных работ для продвижения.&lt;br /&gt;
|Field_of_knowledge=Информатика, Медиа&lt;br /&gt;
|Область применения=Образование&lt;br /&gt;
|End users=Учащиеся, Преподаватели, Исследователи, Организации, Разработчики&lt;br /&gt;
|distant_collab=Да&lt;br /&gt;
|Language_Ru_Eng=English&lt;br /&gt;
|AI=Да&lt;br /&gt;
}}&lt;br /&gt;
==== Пример работы с API фотостока ====&lt;br /&gt;
&lt;br /&gt;
Одним примером из работы с API является работа с фотостоками, в котором описан полноценный цикл работы с API и созданием бота в Telegram с интеграцией поиска фотографий с сайта-фотостока.&lt;br /&gt;
&lt;br /&gt;
Посмотреть статью можно ниже по ссылке:&lt;br /&gt;
&lt;br /&gt;
[[Телеграм-бот для поиска фотографий через API-ключ фотостока]]&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=Unsplash&amp;diff=45339</id>
		<title>Unsplash</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=Unsplash&amp;diff=45339"/>
		<updated>2026-03-26T14:39:40Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: Новая страница: «{{DigitalTool |Description=Unsplash — это популярный бесплатный фотосток и онлайн-платформа, предоставляющая доступ к миллионам высококачественных фотографий в высоком разрешении. |Affordances=Скачивание фото для коммерческого и личного использования без авторских отчи...»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{DigitalTool&lt;br /&gt;
|Description=Unsplash — это популярный бесплатный фотосток и онлайн-платформа, предоставляющая доступ к миллионам высококачественных фотографий в высоком разрешении.&lt;br /&gt;
|Affordances=Скачивание фото для коммерческого и личного использования без авторских отчислений, интеграцию через API в приложения, использование плагинов, а также загрузку собственных работ для продвижения.&lt;br /&gt;
|Field_of_knowledge=Информатика&lt;br /&gt;
|Область применения=Образование&lt;br /&gt;
|End users=Учащиеся, Преподаватели&lt;br /&gt;
|Tool is made for=Генерация и использование контента&lt;br /&gt;
|distant_collab=Да&lt;br /&gt;
|Language_Ru_Eng=English&lt;br /&gt;
|AI=Да&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Пример работы с API фотостока ====&lt;br /&gt;
&lt;br /&gt;
Одним примером из работы с API является работа с фотостоками, в котором описан полноценный цикл работы с API и созданием бота в Telegram с интеграцией поиска фотографий с сайта-фотостока.&lt;br /&gt;
&lt;br /&gt;
Посмотреть статью можно ниже по ссылке:&lt;br /&gt;
&lt;br /&gt;
[[Телеграм-бот для поиска фотографий через API-ключ фотостока]]&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=45334</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=45334"/>
		<updated>2026-03-26T14:36:19Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: /* Методические материалы */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с [[API]] фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз [[Unsplash]] API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
pip install -r requirements.txt&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
В соответствии с ранее описанными функциональными требованиями диаграмма [[UML]] ниже&lt;br /&gt;
&lt;br /&gt;
&amp;lt;uml&amp;gt;&lt;br /&gt;
@startuml&lt;br /&gt;
actor Пользователь as User&lt;br /&gt;
participant &amp;quot;Telegram Bot&amp;quot; as TB&lt;br /&gt;
participant &amp;quot;Обработчик&amp;quot; as H&lt;br /&gt;
participant &amp;quot;Unsplash API&amp;quot; as API&lt;br /&gt;
participant &amp;quot;Хранилище&amp;quot; as DB&lt;br /&gt;
&lt;br /&gt;
== Поиск фото ==&lt;br /&gt;
User -&amp;gt; TB: /search природа&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET photos?query=природа&lt;br /&gt;
API --&amp;gt; H: 4 фото&lt;br /&gt;
H -&amp;gt; DB: Сохранить фото&lt;br /&gt;
loop 4 фото&lt;br /&gt;
    H -&amp;gt; TB: Фото + кнопка&lt;br /&gt;
    TB --&amp;gt; User: Показать&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Выбор фото ==&lt;br /&gt;
User -&amp;gt; TB: Нажимает кнопку&lt;br /&gt;
TB -&amp;gt; H: callback_data&lt;br /&gt;
H -&amp;gt; DB: Получить фото&lt;br /&gt;
DB --&amp;gt; H: Данные фото&lt;br /&gt;
H -&amp;gt; TB: Ссылки на фото&lt;br /&gt;
TB --&amp;gt; User: Оригинал\nАвтор\nUnsplash&lt;br /&gt;
&lt;br /&gt;
== Случайное фото ==&lt;br /&gt;
User -&amp;gt; TB: /random&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET random photo&lt;br /&gt;
API --&amp;gt; H: Случайное фото&lt;br /&gt;
H -&amp;gt; TB: Фото + ссылки&lt;br /&gt;
TB --&amp;gt; User: Показать&lt;br /&gt;
&lt;br /&gt;
alt Ошибка&lt;br /&gt;
    H -&amp;gt; TB: Ошибка...&lt;br /&gt;
    TB --&amp;gt; User: Сообщение об ошибке&lt;br /&gt;
end&lt;br /&gt;
@enduml&lt;br /&gt;
&amp;lt;/uml&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Непосредственная работа с кодом приложения ====&lt;br /&gt;
&lt;br /&gt;
===== Код перед созданием класса =====&lt;br /&gt;
&lt;br /&gt;
Импорт вышеописанных библиотек и подгрузка переменных из файла .env:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import logging&lt;br /&gt;
import os&lt;br /&gt;
import uuid&lt;br /&gt;
from dotenv import load_dotenv&lt;br /&gt;
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup&lt;br /&gt;
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes, CallbackQueryHandler&lt;br /&gt;
import requests&lt;br /&gt;
&lt;br /&gt;
load_dotenv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Настройка логирования&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    format=&#039;%(asctime)s - %(name)s - %(levelname)s - %(message)s&#039;,&lt;br /&gt;
    level=logging.INFO&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(__name__)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Работа с классом =====&lt;br /&gt;
&lt;br /&gt;
Далее, так как пользователей у бота может быть несколько - крайне важно создать класс, который поможет работать нескольким пользователям одновременно в соответствие с объектно-ориентированным программированием.&lt;br /&gt;
&lt;br /&gt;
Для этого я создал класс, в котором указал основные метаданные, а также функции:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
class UnsplashTelegramBot:&lt;br /&gt;
    def __init__(self):&lt;br /&gt;
        self.unsplash_key = os.getenv(&#039;UNSPLASH_ACCESS_KEY&#039;)&lt;br /&gt;
        self.telegram_token = os.getenv(&#039;TELEGRAM_BOT_TOKEN&#039;)&lt;br /&gt;
        self.unsplash_url = &amp;quot;https://api.unsplash.com&amp;quot;&lt;br /&gt;
        self.headers = {&lt;br /&gt;
            &#039;Authorization&#039;: f&#039;Client-ID {self.unsplash_key}&#039;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        self.search_results = {}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Программирование функций класса бота ======&lt;br /&gt;
&lt;br /&gt;
* /start - основная команда, которая будет запускать бота и создавать класс в нем. После отправки этой команды пользователь должен получать информацию о боте.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /start&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        welcome_message = (&lt;br /&gt;
            &amp;quot;Привет! Я бот для поиска фотографий на Unsplash.\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Команды:\n&amp;quot;&lt;br /&gt;
            &amp;quot;/search &amp;lt;запрос&amp;gt; - поиск и выбор из 4 фотографий\n&amp;quot;&lt;br /&gt;
            &amp;quot;/random - случайное фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/popular - популярные фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/help - помощь&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(welcome_message)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /help - дополнительная команда, которая будет отчасти повторять функционал команды /start, но описывать функционал более подробно.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /help&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        help_text = (&lt;br /&gt;
            &amp;quot;? Как пользоваться ботом:\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;1. Для поиска фото напиши: /search природа\n&amp;quot;&lt;br /&gt;
            &amp;quot;   Бот покажет 4 фото на выбор\n&amp;quot;&lt;br /&gt;
            &amp;quot;2. Нажми на кнопку под фото, чтобы получить ссылку на источник\n&amp;quot;&lt;br /&gt;
            &amp;quot;3. Для случайного фото: /random\n&amp;quot;&lt;br /&gt;
            &amp;quot;4. Для популярных фото: /popular\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Также можно просто отправить текстовый запрос для поиска!&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(help_text)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /search или сообщение без команды - основа всего бота, поиск фотографий и показ 4-х фотографий.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def search_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Поиск фотографий и показ 4 вариантов&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        if context.args:&lt;br /&gt;
            query = &#039; &#039;.join(context.args)&lt;br /&gt;
        else:&lt;br /&gt;
            # Команда без арг-тов, просто сообщение&lt;br /&gt;
            if update.message and update.message.text and not update.message.text.startswith(&#039;/&#039;):&lt;br /&gt;
                query = update.message.text&lt;br /&gt;
            else:&lt;br /&gt;
                await update.message.reply_text(&amp;quot;Пожалуйста, укажи запрос. Например: /search природа&amp;quot;)&lt;br /&gt;
                return&lt;br /&gt;
        &lt;br /&gt;
        # Отправляем сообщение о поиске&lt;br /&gt;
        status_message = await update.message.reply_text(f&amp;quot;Ищу фото по запросу: {query}...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            # Получаем 4 фотографии&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/search/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;query&#039;: query, &#039;per_page&#039;: 4, &#039;page&#039;: 1}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                data = response.json()&lt;br /&gt;
                if data[&#039;results&#039;]:&lt;br /&gt;
                    # Удаляем сообщение о поиске&lt;br /&gt;
                    await status_message.delete()&lt;br /&gt;
                    &lt;br /&gt;
                    # Уникальный ID&lt;br /&gt;
                    search_id = str(uuid.uuid4())[:8]&lt;br /&gt;
                    self.search_results[search_id] = data[&#039;results&#039;]&lt;br /&gt;
                    &lt;br /&gt;
                    for i, photo in enumerate(data[&#039;results&#039;], 1):&lt;br /&gt;
                        caption = f&amp;quot;Вариант {i} из 4\n👤 Автор: {photo[&#039;user&#039;][&#039;name&#039;]}&amp;quot;&lt;br /&gt;
                        &lt;br /&gt;
                        keyboard = [&lt;br /&gt;
                            [InlineKeyboardButton(&lt;br /&gt;
                                &amp;quot;Получить ссылку на источник&amp;quot;, &lt;br /&gt;
                                callback_data=f&amp;quot;get_link_{search_id}_{i-1}&amp;quot;&lt;br /&gt;
                            )]&lt;br /&gt;
                        ]&lt;br /&gt;
                        reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                        &lt;br /&gt;
                        await update.message.reply_photo(&lt;br /&gt;
                            photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                            caption=caption,&lt;br /&gt;
                            reply_markup=reply_markup&lt;br /&gt;
                        )&lt;br /&gt;
                    &lt;br /&gt;
                    # Отправляем сообщение с инструкцией&lt;br /&gt;
                    await update.message.reply_text(&lt;br /&gt;
                        f&amp;quot;✅ Найдено {data[&#039;total&#039;]} фото. Выбери понравившийся вариант, чтобы получить ссылку на источник!&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                else:&lt;br /&gt;
                    await status_message.edit_text(&amp;quot;Ничего не найдено. Попробуй другой запрос.&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                await status_message.edit_text(&amp;quot;❌ Ошибка при поиске фото. Попробуй позже.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_message.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* /random - случайное фото из Unsplash.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Случайное фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        await self.send_random_photo(update, context)&lt;br /&gt;
    &lt;br /&gt;
    async def send_random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Отправка случайного фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # Определяем, откуда пришел запрос&lt;br /&gt;
        if update.callback_query:&lt;br /&gt;
            message = update.callback_query.message&lt;br /&gt;
            await update.callback_query.answer()&lt;br /&gt;
        else:&lt;br /&gt;
            message = update.message&lt;br /&gt;
        &lt;br /&gt;
        query = &#039; &#039;.join(context.args) if context.args and hasattr(context, &#039;args&#039;) else None&lt;br /&gt;
        &lt;br /&gt;
        status_msg = await message.reply_text(&amp;quot;🎲 Получаю случайное фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            params = {}&lt;br /&gt;
            if query:&lt;br /&gt;
                params[&#039;query&#039;] = query&lt;br /&gt;
            &lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos/random&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params=params&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photo = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                caption = (&lt;br /&gt;
                    f&amp;quot;**Случайное фото**\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;📝 **Описание:** {photo.get(&#039;description&#039;, &#039;Нет описания&#039;)}\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Создаем кнопки для действий с фото&lt;br /&gt;
                keyboard = [&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔗 Получить все ссылки&amp;quot;, callback_data=f&amp;quot;get_link_random_{photo[&#039;id&#039;]}&amp;quot;)],&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔄 Еще случайное&amp;quot;, callback_data=&amp;quot;random_photo&amp;quot;)]&lt;br /&gt;
                ]&lt;br /&gt;
                reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                &lt;br /&gt;
                await message.reply_photo(&lt;br /&gt;
                    photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                    caption=caption,&lt;br /&gt;
                    parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                    reply_markup=reply_markup&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Сохраняем фото для возможного запроса ссылок&lt;br /&gt;
                if not hasattr(self, &#039;random_photos&#039;):&lt;br /&gt;
                    self.random_photos = {}&lt;br /&gt;
                self.random_photos[photo[&#039;id&#039;]] = photo&lt;br /&gt;
                &lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при получении случайного фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /popular - случайное фото из подборки &amp;quot;Популярное&amp;quot; из Unsplash. делается наподобие команды /random.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def popular_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Популярные фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        status_msg = await update.message.reply_text(&amp;quot;🔥 Загружаю популярные фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;per_page&#039;: 5, &#039;order_by&#039;: &#039;popular&#039;}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photos = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                for i, photo in enumerate(photos, 1):&lt;br /&gt;
                    caption = (&lt;br /&gt;
                        f&amp;quot;🔥 **Популярное фото #{i}**\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;❤️ **Лайков:** {photo[&#039;likes&#039;]}\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Кнопка для получения ссылок&lt;br /&gt;
                    keyboard = [[InlineKeyboardButton(&amp;quot;🔗 Получить ссылки&amp;quot;, callback_data=f&amp;quot;get_link_popular_{photo[&#039;id&#039;]}&amp;quot;)]]&lt;br /&gt;
                    reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                    &lt;br /&gt;
                    await update.message.reply_photo(&lt;br /&gt;
                        photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                        caption=caption,&lt;br /&gt;
                        parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                        reply_markup=reply_markup&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Сохраняем фото&lt;br /&gt;
                    if not hasattr(self, &#039;popular_photos_cache&#039;):&lt;br /&gt;
                        self.popular_photos_cache = {}&lt;br /&gt;
                    self.popular_photos_cache[photo[&#039;id&#039;]] = photo&lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при загрузке популярных фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Написание обработчика на запуск бота =====&lt;br /&gt;
&lt;br /&gt;
Функция на запуск бота, включает в себя создание приложения, обработчики команд и логирование всех команд в терминале.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    def run(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Запуск бота&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        application = Application.builder().token(self.telegram_token).build()&lt;br /&gt;
        &lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;start&amp;quot;, self.start))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;help&amp;quot;, self.help_command))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;search&amp;quot;, self.search_photos))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;random&amp;quot;, self.random_photo))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;popular&amp;quot;, self.popular_photos))&lt;br /&gt;
        application.add_handler(MessageHandler(filters.TEXT &amp;amp; ~filters.COMMAND, self.handle_message))&lt;br /&gt;
        application.add_handler(CallbackQueryHandler(self.handle_callback))&lt;br /&gt;
        &lt;br /&gt;
        print(&amp;quot;/start&amp;quot;)&lt;br /&gt;
        application.run_polling(allowed_updates=Update.ALL_TYPES)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Вне класса также создаем класс и запускаем бота.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    bot = UnsplashTelegramBot()&lt;br /&gt;
    bot.run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Запуск бота и его тестирование ====&lt;br /&gt;
&lt;br /&gt;
Производим запуск бота через запуск исполняемого python файла:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233335238.png|400px]]&lt;br /&gt;
&lt;br /&gt;
После этого действия можем начинать работать и тестировать бота.&lt;br /&gt;
&lt;br /&gt;
Переходим в телеграм, пишем команду /start&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233503508.png|400px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, запрос прошел успешно, бот ответил то, что я зашил в код программы.&lt;br /&gt;
&lt;br /&gt;
Попробуем получить случайное фото командой /random:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233610248.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233641178.png|200px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь попробуем найти что-то конкретное и посмотреть как это будет выглядеть. Для этого сформируем команду /search. Искать будем собаку с породой самоед:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233810808.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233832396.png|200px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, бот отправил нам 4 варианта фотографий, к каждой из которой добавил ссылку на оригинал.&lt;br /&gt;
&lt;br /&gt;
Притом каждый раз в терминале (консоли) мы можем заметить, как бот поддерживает подключение с Unsplash и логирует отправку сообщений.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 234038505.png|700px]]&lt;br /&gt;
&lt;br /&gt;
=== Методические материалы ===&lt;br /&gt;
&lt;br /&gt;
{{#ask: [[API]] OR [[Python]]  OR [[UML]] OR [[Telegram]] OR [[Unsplash]] | ?Description }}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Участник:CherenkovIR|CherenkovIR]] ([[Обсуждение участника:CherenkovIR|обсуждение]])&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=45333</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=45333"/>
		<updated>2026-03-26T14:36:09Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: /* Получение API и создание внутреннего конфигурационного API файла */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с [[API]] фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз [[Unsplash]] API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
pip install -r requirements.txt&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
В соответствии с ранее описанными функциональными требованиями диаграмма [[UML]] ниже&lt;br /&gt;
&lt;br /&gt;
&amp;lt;uml&amp;gt;&lt;br /&gt;
@startuml&lt;br /&gt;
actor Пользователь as User&lt;br /&gt;
participant &amp;quot;Telegram Bot&amp;quot; as TB&lt;br /&gt;
participant &amp;quot;Обработчик&amp;quot; as H&lt;br /&gt;
participant &amp;quot;Unsplash API&amp;quot; as API&lt;br /&gt;
participant &amp;quot;Хранилище&amp;quot; as DB&lt;br /&gt;
&lt;br /&gt;
== Поиск фото ==&lt;br /&gt;
User -&amp;gt; TB: /search природа&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET photos?query=природа&lt;br /&gt;
API --&amp;gt; H: 4 фото&lt;br /&gt;
H -&amp;gt; DB: Сохранить фото&lt;br /&gt;
loop 4 фото&lt;br /&gt;
    H -&amp;gt; TB: Фото + кнопка&lt;br /&gt;
    TB --&amp;gt; User: Показать&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Выбор фото ==&lt;br /&gt;
User -&amp;gt; TB: Нажимает кнопку&lt;br /&gt;
TB -&amp;gt; H: callback_data&lt;br /&gt;
H -&amp;gt; DB: Получить фото&lt;br /&gt;
DB --&amp;gt; H: Данные фото&lt;br /&gt;
H -&amp;gt; TB: Ссылки на фото&lt;br /&gt;
TB --&amp;gt; User: Оригинал\nАвтор\nUnsplash&lt;br /&gt;
&lt;br /&gt;
== Случайное фото ==&lt;br /&gt;
User -&amp;gt; TB: /random&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET random photo&lt;br /&gt;
API --&amp;gt; H: Случайное фото&lt;br /&gt;
H -&amp;gt; TB: Фото + ссылки&lt;br /&gt;
TB --&amp;gt; User: Показать&lt;br /&gt;
&lt;br /&gt;
alt Ошибка&lt;br /&gt;
    H -&amp;gt; TB: Ошибка...&lt;br /&gt;
    TB --&amp;gt; User: Сообщение об ошибке&lt;br /&gt;
end&lt;br /&gt;
@enduml&lt;br /&gt;
&amp;lt;/uml&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Непосредственная работа с кодом приложения ====&lt;br /&gt;
&lt;br /&gt;
===== Код перед созданием класса =====&lt;br /&gt;
&lt;br /&gt;
Импорт вышеописанных библиотек и подгрузка переменных из файла .env:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import logging&lt;br /&gt;
import os&lt;br /&gt;
import uuid&lt;br /&gt;
from dotenv import load_dotenv&lt;br /&gt;
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup&lt;br /&gt;
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes, CallbackQueryHandler&lt;br /&gt;
import requests&lt;br /&gt;
&lt;br /&gt;
load_dotenv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Настройка логирования&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    format=&#039;%(asctime)s - %(name)s - %(levelname)s - %(message)s&#039;,&lt;br /&gt;
    level=logging.INFO&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(__name__)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Работа с классом =====&lt;br /&gt;
&lt;br /&gt;
Далее, так как пользователей у бота может быть несколько - крайне важно создать класс, который поможет работать нескольким пользователям одновременно в соответствие с объектно-ориентированным программированием.&lt;br /&gt;
&lt;br /&gt;
Для этого я создал класс, в котором указал основные метаданные, а также функции:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
class UnsplashTelegramBot:&lt;br /&gt;
    def __init__(self):&lt;br /&gt;
        self.unsplash_key = os.getenv(&#039;UNSPLASH_ACCESS_KEY&#039;)&lt;br /&gt;
        self.telegram_token = os.getenv(&#039;TELEGRAM_BOT_TOKEN&#039;)&lt;br /&gt;
        self.unsplash_url = &amp;quot;https://api.unsplash.com&amp;quot;&lt;br /&gt;
        self.headers = {&lt;br /&gt;
            &#039;Authorization&#039;: f&#039;Client-ID {self.unsplash_key}&#039;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        self.search_results = {}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Программирование функций класса бота ======&lt;br /&gt;
&lt;br /&gt;
* /start - основная команда, которая будет запускать бота и создавать класс в нем. После отправки этой команды пользователь должен получать информацию о боте.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /start&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        welcome_message = (&lt;br /&gt;
            &amp;quot;Привет! Я бот для поиска фотографий на Unsplash.\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Команды:\n&amp;quot;&lt;br /&gt;
            &amp;quot;/search &amp;lt;запрос&amp;gt; - поиск и выбор из 4 фотографий\n&amp;quot;&lt;br /&gt;
            &amp;quot;/random - случайное фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/popular - популярные фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/help - помощь&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(welcome_message)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /help - дополнительная команда, которая будет отчасти повторять функционал команды /start, но описывать функционал более подробно.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /help&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        help_text = (&lt;br /&gt;
            &amp;quot;? Как пользоваться ботом:\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;1. Для поиска фото напиши: /search природа\n&amp;quot;&lt;br /&gt;
            &amp;quot;   Бот покажет 4 фото на выбор\n&amp;quot;&lt;br /&gt;
            &amp;quot;2. Нажми на кнопку под фото, чтобы получить ссылку на источник\n&amp;quot;&lt;br /&gt;
            &amp;quot;3. Для случайного фото: /random\n&amp;quot;&lt;br /&gt;
            &amp;quot;4. Для популярных фото: /popular\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Также можно просто отправить текстовый запрос для поиска!&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(help_text)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /search или сообщение без команды - основа всего бота, поиск фотографий и показ 4-х фотографий.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def search_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Поиск фотографий и показ 4 вариантов&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        if context.args:&lt;br /&gt;
            query = &#039; &#039;.join(context.args)&lt;br /&gt;
        else:&lt;br /&gt;
            # Команда без арг-тов, просто сообщение&lt;br /&gt;
            if update.message and update.message.text and not update.message.text.startswith(&#039;/&#039;):&lt;br /&gt;
                query = update.message.text&lt;br /&gt;
            else:&lt;br /&gt;
                await update.message.reply_text(&amp;quot;Пожалуйста, укажи запрос. Например: /search природа&amp;quot;)&lt;br /&gt;
                return&lt;br /&gt;
        &lt;br /&gt;
        # Отправляем сообщение о поиске&lt;br /&gt;
        status_message = await update.message.reply_text(f&amp;quot;Ищу фото по запросу: {query}...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            # Получаем 4 фотографии&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/search/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;query&#039;: query, &#039;per_page&#039;: 4, &#039;page&#039;: 1}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                data = response.json()&lt;br /&gt;
                if data[&#039;results&#039;]:&lt;br /&gt;
                    # Удаляем сообщение о поиске&lt;br /&gt;
                    await status_message.delete()&lt;br /&gt;
                    &lt;br /&gt;
                    # Уникальный ID&lt;br /&gt;
                    search_id = str(uuid.uuid4())[:8]&lt;br /&gt;
                    self.search_results[search_id] = data[&#039;results&#039;]&lt;br /&gt;
                    &lt;br /&gt;
                    for i, photo in enumerate(data[&#039;results&#039;], 1):&lt;br /&gt;
                        caption = f&amp;quot;Вариант {i} из 4\n👤 Автор: {photo[&#039;user&#039;][&#039;name&#039;]}&amp;quot;&lt;br /&gt;
                        &lt;br /&gt;
                        keyboard = [&lt;br /&gt;
                            [InlineKeyboardButton(&lt;br /&gt;
                                &amp;quot;Получить ссылку на источник&amp;quot;, &lt;br /&gt;
                                callback_data=f&amp;quot;get_link_{search_id}_{i-1}&amp;quot;&lt;br /&gt;
                            )]&lt;br /&gt;
                        ]&lt;br /&gt;
                        reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                        &lt;br /&gt;
                        await update.message.reply_photo(&lt;br /&gt;
                            photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                            caption=caption,&lt;br /&gt;
                            reply_markup=reply_markup&lt;br /&gt;
                        )&lt;br /&gt;
                    &lt;br /&gt;
                    # Отправляем сообщение с инструкцией&lt;br /&gt;
                    await update.message.reply_text(&lt;br /&gt;
                        f&amp;quot;✅ Найдено {data[&#039;total&#039;]} фото. Выбери понравившийся вариант, чтобы получить ссылку на источник!&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                else:&lt;br /&gt;
                    await status_message.edit_text(&amp;quot;Ничего не найдено. Попробуй другой запрос.&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                await status_message.edit_text(&amp;quot;❌ Ошибка при поиске фото. Попробуй позже.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_message.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* /random - случайное фото из Unsplash.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Случайное фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        await self.send_random_photo(update, context)&lt;br /&gt;
    &lt;br /&gt;
    async def send_random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Отправка случайного фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # Определяем, откуда пришел запрос&lt;br /&gt;
        if update.callback_query:&lt;br /&gt;
            message = update.callback_query.message&lt;br /&gt;
            await update.callback_query.answer()&lt;br /&gt;
        else:&lt;br /&gt;
            message = update.message&lt;br /&gt;
        &lt;br /&gt;
        query = &#039; &#039;.join(context.args) if context.args and hasattr(context, &#039;args&#039;) else None&lt;br /&gt;
        &lt;br /&gt;
        status_msg = await message.reply_text(&amp;quot;🎲 Получаю случайное фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            params = {}&lt;br /&gt;
            if query:&lt;br /&gt;
                params[&#039;query&#039;] = query&lt;br /&gt;
            &lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos/random&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params=params&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photo = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                caption = (&lt;br /&gt;
                    f&amp;quot;**Случайное фото**\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;📝 **Описание:** {photo.get(&#039;description&#039;, &#039;Нет описания&#039;)}\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Создаем кнопки для действий с фото&lt;br /&gt;
                keyboard = [&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔗 Получить все ссылки&amp;quot;, callback_data=f&amp;quot;get_link_random_{photo[&#039;id&#039;]}&amp;quot;)],&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔄 Еще случайное&amp;quot;, callback_data=&amp;quot;random_photo&amp;quot;)]&lt;br /&gt;
                ]&lt;br /&gt;
                reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                &lt;br /&gt;
                await message.reply_photo(&lt;br /&gt;
                    photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                    caption=caption,&lt;br /&gt;
                    parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                    reply_markup=reply_markup&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Сохраняем фото для возможного запроса ссылок&lt;br /&gt;
                if not hasattr(self, &#039;random_photos&#039;):&lt;br /&gt;
                    self.random_photos = {}&lt;br /&gt;
                self.random_photos[photo[&#039;id&#039;]] = photo&lt;br /&gt;
                &lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при получении случайного фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /popular - случайное фото из подборки &amp;quot;Популярное&amp;quot; из Unsplash. делается наподобие команды /random.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def popular_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Популярные фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        status_msg = await update.message.reply_text(&amp;quot;🔥 Загружаю популярные фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;per_page&#039;: 5, &#039;order_by&#039;: &#039;popular&#039;}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photos = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                for i, photo in enumerate(photos, 1):&lt;br /&gt;
                    caption = (&lt;br /&gt;
                        f&amp;quot;🔥 **Популярное фото #{i}**\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;❤️ **Лайков:** {photo[&#039;likes&#039;]}\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Кнопка для получения ссылок&lt;br /&gt;
                    keyboard = [[InlineKeyboardButton(&amp;quot;🔗 Получить ссылки&amp;quot;, callback_data=f&amp;quot;get_link_popular_{photo[&#039;id&#039;]}&amp;quot;)]]&lt;br /&gt;
                    reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                    &lt;br /&gt;
                    await update.message.reply_photo(&lt;br /&gt;
                        photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                        caption=caption,&lt;br /&gt;
                        parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                        reply_markup=reply_markup&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Сохраняем фото&lt;br /&gt;
                    if not hasattr(self, &#039;popular_photos_cache&#039;):&lt;br /&gt;
                        self.popular_photos_cache = {}&lt;br /&gt;
                    self.popular_photos_cache[photo[&#039;id&#039;]] = photo&lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при загрузке популярных фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Написание обработчика на запуск бота =====&lt;br /&gt;
&lt;br /&gt;
Функция на запуск бота, включает в себя создание приложения, обработчики команд и логирование всех команд в терминале.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    def run(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Запуск бота&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        application = Application.builder().token(self.telegram_token).build()&lt;br /&gt;
        &lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;start&amp;quot;, self.start))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;help&amp;quot;, self.help_command))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;search&amp;quot;, self.search_photos))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;random&amp;quot;, self.random_photo))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;popular&amp;quot;, self.popular_photos))&lt;br /&gt;
        application.add_handler(MessageHandler(filters.TEXT &amp;amp; ~filters.COMMAND, self.handle_message))&lt;br /&gt;
        application.add_handler(CallbackQueryHandler(self.handle_callback))&lt;br /&gt;
        &lt;br /&gt;
        print(&amp;quot;/start&amp;quot;)&lt;br /&gt;
        application.run_polling(allowed_updates=Update.ALL_TYPES)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Вне класса также создаем класс и запускаем бота.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    bot = UnsplashTelegramBot()&lt;br /&gt;
    bot.run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Запуск бота и его тестирование ====&lt;br /&gt;
&lt;br /&gt;
Производим запуск бота через запуск исполняемого python файла:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233335238.png|400px]]&lt;br /&gt;
&lt;br /&gt;
После этого действия можем начинать работать и тестировать бота.&lt;br /&gt;
&lt;br /&gt;
Переходим в телеграм, пишем команду /start&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233503508.png|400px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, запрос прошел успешно, бот ответил то, что я зашил в код программы.&lt;br /&gt;
&lt;br /&gt;
Попробуем получить случайное фото командой /random:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233610248.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233641178.png|200px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь попробуем найти что-то конкретное и посмотреть как это будет выглядеть. Для этого сформируем команду /search. Искать будем собаку с породой самоед:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233810808.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233832396.png|200px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, бот отправил нам 4 варианта фотографий, к каждой из которой добавил ссылку на оригинал.&lt;br /&gt;
&lt;br /&gt;
Притом каждый раз в терминале (консоли) мы можем заметить, как бот поддерживает подключение с Unsplash и логирует отправку сообщений.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 234038505.png|700px]]&lt;br /&gt;
&lt;br /&gt;
=== Методические материалы ===&lt;br /&gt;
&lt;br /&gt;
{{#ask: [[API]] OR [[Python]]  OR [[UML]] OR [[Telegram]] | ?Description }}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Участник:CherenkovIR|CherenkovIR]] ([[Обсуждение участника:CherenkovIR|обсуждение]])&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=45332</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=45332"/>
		<updated>2026-03-26T14:35:56Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: /* Получение API и создание внутреннего конфигурационного API файла */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с [[API]] фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
pip install -r requirements.txt&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
В соответствии с ранее описанными функциональными требованиями диаграмма [[UML]] ниже&lt;br /&gt;
&lt;br /&gt;
&amp;lt;uml&amp;gt;&lt;br /&gt;
@startuml&lt;br /&gt;
actor Пользователь as User&lt;br /&gt;
participant &amp;quot;Telegram Bot&amp;quot; as TB&lt;br /&gt;
participant &amp;quot;Обработчик&amp;quot; as H&lt;br /&gt;
participant &amp;quot;Unsplash API&amp;quot; as API&lt;br /&gt;
participant &amp;quot;Хранилище&amp;quot; as DB&lt;br /&gt;
&lt;br /&gt;
== Поиск фото ==&lt;br /&gt;
User -&amp;gt; TB: /search природа&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET photos?query=природа&lt;br /&gt;
API --&amp;gt; H: 4 фото&lt;br /&gt;
H -&amp;gt; DB: Сохранить фото&lt;br /&gt;
loop 4 фото&lt;br /&gt;
    H -&amp;gt; TB: Фото + кнопка&lt;br /&gt;
    TB --&amp;gt; User: Показать&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Выбор фото ==&lt;br /&gt;
User -&amp;gt; TB: Нажимает кнопку&lt;br /&gt;
TB -&amp;gt; H: callback_data&lt;br /&gt;
H -&amp;gt; DB: Получить фото&lt;br /&gt;
DB --&amp;gt; H: Данные фото&lt;br /&gt;
H -&amp;gt; TB: Ссылки на фото&lt;br /&gt;
TB --&amp;gt; User: Оригинал\nАвтор\nUnsplash&lt;br /&gt;
&lt;br /&gt;
== Случайное фото ==&lt;br /&gt;
User -&amp;gt; TB: /random&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET random photo&lt;br /&gt;
API --&amp;gt; H: Случайное фото&lt;br /&gt;
H -&amp;gt; TB: Фото + ссылки&lt;br /&gt;
TB --&amp;gt; User: Показать&lt;br /&gt;
&lt;br /&gt;
alt Ошибка&lt;br /&gt;
    H -&amp;gt; TB: Ошибка...&lt;br /&gt;
    TB --&amp;gt; User: Сообщение об ошибке&lt;br /&gt;
end&lt;br /&gt;
@enduml&lt;br /&gt;
&amp;lt;/uml&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Непосредственная работа с кодом приложения ====&lt;br /&gt;
&lt;br /&gt;
===== Код перед созданием класса =====&lt;br /&gt;
&lt;br /&gt;
Импорт вышеописанных библиотек и подгрузка переменных из файла .env:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import logging&lt;br /&gt;
import os&lt;br /&gt;
import uuid&lt;br /&gt;
from dotenv import load_dotenv&lt;br /&gt;
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup&lt;br /&gt;
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes, CallbackQueryHandler&lt;br /&gt;
import requests&lt;br /&gt;
&lt;br /&gt;
load_dotenv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Настройка логирования&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    format=&#039;%(asctime)s - %(name)s - %(levelname)s - %(message)s&#039;,&lt;br /&gt;
    level=logging.INFO&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(__name__)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Работа с классом =====&lt;br /&gt;
&lt;br /&gt;
Далее, так как пользователей у бота может быть несколько - крайне важно создать класс, который поможет работать нескольким пользователям одновременно в соответствие с объектно-ориентированным программированием.&lt;br /&gt;
&lt;br /&gt;
Для этого я создал класс, в котором указал основные метаданные, а также функции:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
class UnsplashTelegramBot:&lt;br /&gt;
    def __init__(self):&lt;br /&gt;
        self.unsplash_key = os.getenv(&#039;UNSPLASH_ACCESS_KEY&#039;)&lt;br /&gt;
        self.telegram_token = os.getenv(&#039;TELEGRAM_BOT_TOKEN&#039;)&lt;br /&gt;
        self.unsplash_url = &amp;quot;https://api.unsplash.com&amp;quot;&lt;br /&gt;
        self.headers = {&lt;br /&gt;
            &#039;Authorization&#039;: f&#039;Client-ID {self.unsplash_key}&#039;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        self.search_results = {}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Программирование функций класса бота ======&lt;br /&gt;
&lt;br /&gt;
* /start - основная команда, которая будет запускать бота и создавать класс в нем. После отправки этой команды пользователь должен получать информацию о боте.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /start&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        welcome_message = (&lt;br /&gt;
            &amp;quot;Привет! Я бот для поиска фотографий на Unsplash.\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Команды:\n&amp;quot;&lt;br /&gt;
            &amp;quot;/search &amp;lt;запрос&amp;gt; - поиск и выбор из 4 фотографий\n&amp;quot;&lt;br /&gt;
            &amp;quot;/random - случайное фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/popular - популярные фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/help - помощь&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(welcome_message)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /help - дополнительная команда, которая будет отчасти повторять функционал команды /start, но описывать функционал более подробно.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /help&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        help_text = (&lt;br /&gt;
            &amp;quot;? Как пользоваться ботом:\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;1. Для поиска фото напиши: /search природа\n&amp;quot;&lt;br /&gt;
            &amp;quot;   Бот покажет 4 фото на выбор\n&amp;quot;&lt;br /&gt;
            &amp;quot;2. Нажми на кнопку под фото, чтобы получить ссылку на источник\n&amp;quot;&lt;br /&gt;
            &amp;quot;3. Для случайного фото: /random\n&amp;quot;&lt;br /&gt;
            &amp;quot;4. Для популярных фото: /popular\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Также можно просто отправить текстовый запрос для поиска!&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(help_text)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /search или сообщение без команды - основа всего бота, поиск фотографий и показ 4-х фотографий.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def search_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Поиск фотографий и показ 4 вариантов&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        if context.args:&lt;br /&gt;
            query = &#039; &#039;.join(context.args)&lt;br /&gt;
        else:&lt;br /&gt;
            # Команда без арг-тов, просто сообщение&lt;br /&gt;
            if update.message and update.message.text and not update.message.text.startswith(&#039;/&#039;):&lt;br /&gt;
                query = update.message.text&lt;br /&gt;
            else:&lt;br /&gt;
                await update.message.reply_text(&amp;quot;Пожалуйста, укажи запрос. Например: /search природа&amp;quot;)&lt;br /&gt;
                return&lt;br /&gt;
        &lt;br /&gt;
        # Отправляем сообщение о поиске&lt;br /&gt;
        status_message = await update.message.reply_text(f&amp;quot;Ищу фото по запросу: {query}...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            # Получаем 4 фотографии&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/search/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;query&#039;: query, &#039;per_page&#039;: 4, &#039;page&#039;: 1}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                data = response.json()&lt;br /&gt;
                if data[&#039;results&#039;]:&lt;br /&gt;
                    # Удаляем сообщение о поиске&lt;br /&gt;
                    await status_message.delete()&lt;br /&gt;
                    &lt;br /&gt;
                    # Уникальный ID&lt;br /&gt;
                    search_id = str(uuid.uuid4())[:8]&lt;br /&gt;
                    self.search_results[search_id] = data[&#039;results&#039;]&lt;br /&gt;
                    &lt;br /&gt;
                    for i, photo in enumerate(data[&#039;results&#039;], 1):&lt;br /&gt;
                        caption = f&amp;quot;Вариант {i} из 4\n👤 Автор: {photo[&#039;user&#039;][&#039;name&#039;]}&amp;quot;&lt;br /&gt;
                        &lt;br /&gt;
                        keyboard = [&lt;br /&gt;
                            [InlineKeyboardButton(&lt;br /&gt;
                                &amp;quot;Получить ссылку на источник&amp;quot;, &lt;br /&gt;
                                callback_data=f&amp;quot;get_link_{search_id}_{i-1}&amp;quot;&lt;br /&gt;
                            )]&lt;br /&gt;
                        ]&lt;br /&gt;
                        reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                        &lt;br /&gt;
                        await update.message.reply_photo(&lt;br /&gt;
                            photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                            caption=caption,&lt;br /&gt;
                            reply_markup=reply_markup&lt;br /&gt;
                        )&lt;br /&gt;
                    &lt;br /&gt;
                    # Отправляем сообщение с инструкцией&lt;br /&gt;
                    await update.message.reply_text(&lt;br /&gt;
                        f&amp;quot;✅ Найдено {data[&#039;total&#039;]} фото. Выбери понравившийся вариант, чтобы получить ссылку на источник!&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                else:&lt;br /&gt;
                    await status_message.edit_text(&amp;quot;Ничего не найдено. Попробуй другой запрос.&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                await status_message.edit_text(&amp;quot;❌ Ошибка при поиске фото. Попробуй позже.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_message.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* /random - случайное фото из Unsplash.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Случайное фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        await self.send_random_photo(update, context)&lt;br /&gt;
    &lt;br /&gt;
    async def send_random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Отправка случайного фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # Определяем, откуда пришел запрос&lt;br /&gt;
        if update.callback_query:&lt;br /&gt;
            message = update.callback_query.message&lt;br /&gt;
            await update.callback_query.answer()&lt;br /&gt;
        else:&lt;br /&gt;
            message = update.message&lt;br /&gt;
        &lt;br /&gt;
        query = &#039; &#039;.join(context.args) if context.args and hasattr(context, &#039;args&#039;) else None&lt;br /&gt;
        &lt;br /&gt;
        status_msg = await message.reply_text(&amp;quot;🎲 Получаю случайное фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            params = {}&lt;br /&gt;
            if query:&lt;br /&gt;
                params[&#039;query&#039;] = query&lt;br /&gt;
            &lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos/random&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params=params&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photo = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                caption = (&lt;br /&gt;
                    f&amp;quot;**Случайное фото**\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;📝 **Описание:** {photo.get(&#039;description&#039;, &#039;Нет описания&#039;)}\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Создаем кнопки для действий с фото&lt;br /&gt;
                keyboard = [&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔗 Получить все ссылки&amp;quot;, callback_data=f&amp;quot;get_link_random_{photo[&#039;id&#039;]}&amp;quot;)],&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔄 Еще случайное&amp;quot;, callback_data=&amp;quot;random_photo&amp;quot;)]&lt;br /&gt;
                ]&lt;br /&gt;
                reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                &lt;br /&gt;
                await message.reply_photo(&lt;br /&gt;
                    photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                    caption=caption,&lt;br /&gt;
                    parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                    reply_markup=reply_markup&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Сохраняем фото для возможного запроса ссылок&lt;br /&gt;
                if not hasattr(self, &#039;random_photos&#039;):&lt;br /&gt;
                    self.random_photos = {}&lt;br /&gt;
                self.random_photos[photo[&#039;id&#039;]] = photo&lt;br /&gt;
                &lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при получении случайного фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /popular - случайное фото из подборки &amp;quot;Популярное&amp;quot; из Unsplash. делается наподобие команды /random.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def popular_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Популярные фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        status_msg = await update.message.reply_text(&amp;quot;🔥 Загружаю популярные фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;per_page&#039;: 5, &#039;order_by&#039;: &#039;popular&#039;}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photos = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                for i, photo in enumerate(photos, 1):&lt;br /&gt;
                    caption = (&lt;br /&gt;
                        f&amp;quot;🔥 **Популярное фото #{i}**\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;❤️ **Лайков:** {photo[&#039;likes&#039;]}\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Кнопка для получения ссылок&lt;br /&gt;
                    keyboard = [[InlineKeyboardButton(&amp;quot;🔗 Получить ссылки&amp;quot;, callback_data=f&amp;quot;get_link_popular_{photo[&#039;id&#039;]}&amp;quot;)]]&lt;br /&gt;
                    reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                    &lt;br /&gt;
                    await update.message.reply_photo(&lt;br /&gt;
                        photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                        caption=caption,&lt;br /&gt;
                        parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                        reply_markup=reply_markup&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Сохраняем фото&lt;br /&gt;
                    if not hasattr(self, &#039;popular_photos_cache&#039;):&lt;br /&gt;
                        self.popular_photos_cache = {}&lt;br /&gt;
                    self.popular_photos_cache[photo[&#039;id&#039;]] = photo&lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при загрузке популярных фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Написание обработчика на запуск бота =====&lt;br /&gt;
&lt;br /&gt;
Функция на запуск бота, включает в себя создание приложения, обработчики команд и логирование всех команд в терминале.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    def run(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Запуск бота&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        application = Application.builder().token(self.telegram_token).build()&lt;br /&gt;
        &lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;start&amp;quot;, self.start))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;help&amp;quot;, self.help_command))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;search&amp;quot;, self.search_photos))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;random&amp;quot;, self.random_photo))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;popular&amp;quot;, self.popular_photos))&lt;br /&gt;
        application.add_handler(MessageHandler(filters.TEXT &amp;amp; ~filters.COMMAND, self.handle_message))&lt;br /&gt;
        application.add_handler(CallbackQueryHandler(self.handle_callback))&lt;br /&gt;
        &lt;br /&gt;
        print(&amp;quot;/start&amp;quot;)&lt;br /&gt;
        application.run_polling(allowed_updates=Update.ALL_TYPES)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Вне класса также создаем класс и запускаем бота.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    bot = UnsplashTelegramBot()&lt;br /&gt;
    bot.run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Запуск бота и его тестирование ====&lt;br /&gt;
&lt;br /&gt;
Производим запуск бота через запуск исполняемого python файла:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233335238.png|400px]]&lt;br /&gt;
&lt;br /&gt;
После этого действия можем начинать работать и тестировать бота.&lt;br /&gt;
&lt;br /&gt;
Переходим в телеграм, пишем команду /start&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233503508.png|400px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, запрос прошел успешно, бот ответил то, что я зашил в код программы.&lt;br /&gt;
&lt;br /&gt;
Попробуем получить случайное фото командой /random:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233610248.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233641178.png|200px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь попробуем найти что-то конкретное и посмотреть как это будет выглядеть. Для этого сформируем команду /search. Искать будем собаку с породой самоед:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233810808.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233832396.png|200px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, бот отправил нам 4 варианта фотографий, к каждой из которой добавил ссылку на оригинал.&lt;br /&gt;
&lt;br /&gt;
Притом каждый раз в терминале (консоли) мы можем заметить, как бот поддерживает подключение с Unsplash и логирует отправку сообщений.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 234038505.png|700px]]&lt;br /&gt;
&lt;br /&gt;
=== Методические материалы ===&lt;br /&gt;
&lt;br /&gt;
{{#ask: [[API]] OR [[Python]]  OR [[UML]] OR [[Telegram]] | ?Description }}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Участник:CherenkovIR|CherenkovIR]] ([[Обсуждение участника:CherenkovIR|обсуждение]])&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=Telegram&amp;diff=45330</id>
		<title>Telegram</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=Telegram&amp;diff=45330"/>
		<updated>2026-03-26T14:35:11Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: /* Пример работы с API */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{DigitalTool&lt;br /&gt;
|Description=Telegram — это мессенджер и платформа для сообществ, где можно создавать групповые чаты, каналы и ботов для координации командной работы и учебных проектов.&lt;br /&gt;
|Affordances=Telegram позволяет организовывать большие группы и каналы, поддерживает обмен файлами, голосовые и видеозвонки, опросы и реакции, а также подключение ботов для автоматизации рутинных действий (сбор заданий, напоминания, мини‑опросы). Благодаря этому он подходит как для оперативной коммуникации команды, так и для распределённой проектной работы и смешанного обучения.&lt;br /&gt;
|Field_of_knowledge=Информатика&lt;br /&gt;
|Область применения=Образование&lt;br /&gt;
|End users=Учащиеся, Преподаватели, Исследователи&lt;br /&gt;
|Tool is made for=генерация контента&lt;br /&gt;
|distant_collab=Нет&lt;br /&gt;
|Language_Ru_Eng=Russian&lt;br /&gt;
|AI=Да&lt;br /&gt;
}}&lt;br /&gt;
В Telegram легко формируются учебные и профессиональные сообщества: студенческие группы курса, проектные команды, профессиональные каналы с обсуждениями и поддержкой новичков. Исследования показывают, что работа в таких чат‑группах усиливает взаимодействие, совместное обсуждение решений и вовлечённость участников, что делает платформу удобной для изучения координации, распределения ролей, аргументации и других аспектов командной деятельности&lt;br /&gt;
&lt;br /&gt;
=== Симуляция поведения группы в [[ТГ]] ===&lt;br /&gt;
&amp;lt;netlogo model=&amp;quot;tg_group_v2&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Работа с API [[Telegram]] для работы с ботами ===&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота, необходимо его сгенерировать после создания бота. Сделать это можно через одноименного бота @BotFather в самом телеграме.&lt;br /&gt;
&lt;br /&gt;
После запуска бота откройте интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создайте своего бота.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Пример работы с API ====&lt;br /&gt;
&lt;br /&gt;
Одним примером из работы с API является работа с фотостоками, в котором описан полноценный цикл работы с API и созданием бота в Telegram с интеграцией поиска фотографий с сайта-фотостока.&lt;br /&gt;
&lt;br /&gt;
Посмотреть статью можно ниже по ссылке:&lt;br /&gt;
&lt;br /&gt;
[[Телеграм-бот для поиска фотографий через API-ключ фотостока]]&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=Telegram&amp;diff=45328</id>
		<title>Telegram</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=Telegram&amp;diff=45328"/>
		<updated>2026-03-26T14:34:52Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: /* Работа с API Telegram для работы с ботами */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{DigitalTool&lt;br /&gt;
|Description=Telegram — это мессенджер и платформа для сообществ, где можно создавать групповые чаты, каналы и ботов для координации командной работы и учебных проектов.&lt;br /&gt;
|Affordances=Telegram позволяет организовывать большие группы и каналы, поддерживает обмен файлами, голосовые и видеозвонки, опросы и реакции, а также подключение ботов для автоматизации рутинных действий (сбор заданий, напоминания, мини‑опросы). Благодаря этому он подходит как для оперативной коммуникации команды, так и для распределённой проектной работы и смешанного обучения.&lt;br /&gt;
|Field_of_knowledge=Информатика&lt;br /&gt;
|Область применения=Образование&lt;br /&gt;
|End users=Учащиеся, Преподаватели, Исследователи&lt;br /&gt;
|Tool is made for=генерация контента&lt;br /&gt;
|distant_collab=Нет&lt;br /&gt;
|Language_Ru_Eng=Russian&lt;br /&gt;
|AI=Да&lt;br /&gt;
}}&lt;br /&gt;
В Telegram легко формируются учебные и профессиональные сообщества: студенческие группы курса, проектные команды, профессиональные каналы с обсуждениями и поддержкой новичков. Исследования показывают, что работа в таких чат‑группах усиливает взаимодействие, совместное обсуждение решений и вовлечённость участников, что делает платформу удобной для изучения координации, распределения ролей, аргументации и других аспектов командной деятельности&lt;br /&gt;
&lt;br /&gt;
=== Симуляция поведения группы в [[ТГ]] ===&lt;br /&gt;
&amp;lt;netlogo model=&amp;quot;tg_group_v2&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Работа с API [[Telegram]] для работы с ботами ===&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота, необходимо его сгенерировать после создания бота. Сделать это можно через одноименного бота @BotFather в самом телеграме.&lt;br /&gt;
&lt;br /&gt;
После запуска бота откройте интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создайте своего бота.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Пример работы с API ====&lt;br /&gt;
&lt;br /&gt;
Одним примером из работы с API является работа с фотостоками, в котором описан полноценный цикл работы с API и созданием бота в Telegram с интеграцией поиска фотографий с сайта-фотостока.&lt;br /&gt;
&lt;br /&gt;
[[Телеграм-бот для поиска фотографий через API-ключ фотостока]]&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=Telegram&amp;diff=45323</id>
		<title>Telegram</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=Telegram&amp;diff=45323"/>
		<updated>2026-03-26T14:31:42Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{DigitalTool&lt;br /&gt;
|Description=Telegram — это мессенджер и платформа для сообществ, где можно создавать групповые чаты, каналы и ботов для координации командной работы и учебных проектов.&lt;br /&gt;
|Affordances=Telegram позволяет организовывать большие группы и каналы, поддерживает обмен файлами, голосовые и видеозвонки, опросы и реакции, а также подключение ботов для автоматизации рутинных действий (сбор заданий, напоминания, мини‑опросы). Благодаря этому он подходит как для оперативной коммуникации команды, так и для распределённой проектной работы и смешанного обучения.&lt;br /&gt;
|Field_of_knowledge=Информатика&lt;br /&gt;
|Область применения=Образование&lt;br /&gt;
|End users=Учащиеся, Преподаватели, Исследователи&lt;br /&gt;
|Tool is made for=генерация контента&lt;br /&gt;
|distant_collab=Нет&lt;br /&gt;
|Language_Ru_Eng=Russian&lt;br /&gt;
|AI=Да&lt;br /&gt;
}}&lt;br /&gt;
В Telegram легко формируются учебные и профессиональные сообщества: студенческие группы курса, проектные команды, профессиональные каналы с обсуждениями и поддержкой новичков. Исследования показывают, что работа в таких чат‑группах усиливает взаимодействие, совместное обсуждение решений и вовлечённость участников, что делает платформу удобной для изучения координации, распределения ролей, аргументации и других аспектов командной деятельности&lt;br /&gt;
&lt;br /&gt;
=== Симуляция поведения группы в [[ТГ]] ===&lt;br /&gt;
&amp;lt;netlogo model=&amp;quot;tg_group_v2&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Работа с API [[Telegram]] для работы с ботами ===&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=45322</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=45322"/>
		<updated>2026-03-26T14:30:08Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: /* Методические материалы */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с [[API]] фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
pip install -r requirements.txt&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
В соответствии с ранее описанными функциональными требованиями диаграмма [[UML]] ниже&lt;br /&gt;
&lt;br /&gt;
&amp;lt;uml&amp;gt;&lt;br /&gt;
@startuml&lt;br /&gt;
actor Пользователь as User&lt;br /&gt;
participant &amp;quot;Telegram Bot&amp;quot; as TB&lt;br /&gt;
participant &amp;quot;Обработчик&amp;quot; as H&lt;br /&gt;
participant &amp;quot;Unsplash API&amp;quot; as API&lt;br /&gt;
participant &amp;quot;Хранилище&amp;quot; as DB&lt;br /&gt;
&lt;br /&gt;
== Поиск фото ==&lt;br /&gt;
User -&amp;gt; TB: /search природа&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET photos?query=природа&lt;br /&gt;
API --&amp;gt; H: 4 фото&lt;br /&gt;
H -&amp;gt; DB: Сохранить фото&lt;br /&gt;
loop 4 фото&lt;br /&gt;
    H -&amp;gt; TB: Фото + кнопка&lt;br /&gt;
    TB --&amp;gt; User: Показать&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Выбор фото ==&lt;br /&gt;
User -&amp;gt; TB: Нажимает кнопку&lt;br /&gt;
TB -&amp;gt; H: callback_data&lt;br /&gt;
H -&amp;gt; DB: Получить фото&lt;br /&gt;
DB --&amp;gt; H: Данные фото&lt;br /&gt;
H -&amp;gt; TB: Ссылки на фото&lt;br /&gt;
TB --&amp;gt; User: Оригинал\nАвтор\nUnsplash&lt;br /&gt;
&lt;br /&gt;
== Случайное фото ==&lt;br /&gt;
User -&amp;gt; TB: /random&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET random photo&lt;br /&gt;
API --&amp;gt; H: Случайное фото&lt;br /&gt;
H -&amp;gt; TB: Фото + ссылки&lt;br /&gt;
TB --&amp;gt; User: Показать&lt;br /&gt;
&lt;br /&gt;
alt Ошибка&lt;br /&gt;
    H -&amp;gt; TB: Ошибка...&lt;br /&gt;
    TB --&amp;gt; User: Сообщение об ошибке&lt;br /&gt;
end&lt;br /&gt;
@enduml&lt;br /&gt;
&amp;lt;/uml&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Непосредственная работа с кодом приложения ====&lt;br /&gt;
&lt;br /&gt;
===== Код перед созданием класса =====&lt;br /&gt;
&lt;br /&gt;
Импорт вышеописанных библиотек и подгрузка переменных из файла .env:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import logging&lt;br /&gt;
import os&lt;br /&gt;
import uuid&lt;br /&gt;
from dotenv import load_dotenv&lt;br /&gt;
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup&lt;br /&gt;
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes, CallbackQueryHandler&lt;br /&gt;
import requests&lt;br /&gt;
&lt;br /&gt;
load_dotenv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Настройка логирования&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    format=&#039;%(asctime)s - %(name)s - %(levelname)s - %(message)s&#039;,&lt;br /&gt;
    level=logging.INFO&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(__name__)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Работа с классом =====&lt;br /&gt;
&lt;br /&gt;
Далее, так как пользователей у бота может быть несколько - крайне важно создать класс, который поможет работать нескольким пользователям одновременно в соответствие с объектно-ориентированным программированием.&lt;br /&gt;
&lt;br /&gt;
Для этого я создал класс, в котором указал основные метаданные, а также функции:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
class UnsplashTelegramBot:&lt;br /&gt;
    def __init__(self):&lt;br /&gt;
        self.unsplash_key = os.getenv(&#039;UNSPLASH_ACCESS_KEY&#039;)&lt;br /&gt;
        self.telegram_token = os.getenv(&#039;TELEGRAM_BOT_TOKEN&#039;)&lt;br /&gt;
        self.unsplash_url = &amp;quot;https://api.unsplash.com&amp;quot;&lt;br /&gt;
        self.headers = {&lt;br /&gt;
            &#039;Authorization&#039;: f&#039;Client-ID {self.unsplash_key}&#039;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        self.search_results = {}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Программирование функций класса бота ======&lt;br /&gt;
&lt;br /&gt;
* /start - основная команда, которая будет запускать бота и создавать класс в нем. После отправки этой команды пользователь должен получать информацию о боте.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /start&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        welcome_message = (&lt;br /&gt;
            &amp;quot;Привет! Я бот для поиска фотографий на Unsplash.\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Команды:\n&amp;quot;&lt;br /&gt;
            &amp;quot;/search &amp;lt;запрос&amp;gt; - поиск и выбор из 4 фотографий\n&amp;quot;&lt;br /&gt;
            &amp;quot;/random - случайное фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/popular - популярные фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/help - помощь&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(welcome_message)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /help - дополнительная команда, которая будет отчасти повторять функционал команды /start, но описывать функционал более подробно.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /help&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        help_text = (&lt;br /&gt;
            &amp;quot;? Как пользоваться ботом:\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;1. Для поиска фото напиши: /search природа\n&amp;quot;&lt;br /&gt;
            &amp;quot;   Бот покажет 4 фото на выбор\n&amp;quot;&lt;br /&gt;
            &amp;quot;2. Нажми на кнопку под фото, чтобы получить ссылку на источник\n&amp;quot;&lt;br /&gt;
            &amp;quot;3. Для случайного фото: /random\n&amp;quot;&lt;br /&gt;
            &amp;quot;4. Для популярных фото: /popular\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Также можно просто отправить текстовый запрос для поиска!&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(help_text)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /search или сообщение без команды - основа всего бота, поиск фотографий и показ 4-х фотографий.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def search_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Поиск фотографий и показ 4 вариантов&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        if context.args:&lt;br /&gt;
            query = &#039; &#039;.join(context.args)&lt;br /&gt;
        else:&lt;br /&gt;
            # Команда без арг-тов, просто сообщение&lt;br /&gt;
            if update.message and update.message.text and not update.message.text.startswith(&#039;/&#039;):&lt;br /&gt;
                query = update.message.text&lt;br /&gt;
            else:&lt;br /&gt;
                await update.message.reply_text(&amp;quot;Пожалуйста, укажи запрос. Например: /search природа&amp;quot;)&lt;br /&gt;
                return&lt;br /&gt;
        &lt;br /&gt;
        # Отправляем сообщение о поиске&lt;br /&gt;
        status_message = await update.message.reply_text(f&amp;quot;Ищу фото по запросу: {query}...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            # Получаем 4 фотографии&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/search/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;query&#039;: query, &#039;per_page&#039;: 4, &#039;page&#039;: 1}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                data = response.json()&lt;br /&gt;
                if data[&#039;results&#039;]:&lt;br /&gt;
                    # Удаляем сообщение о поиске&lt;br /&gt;
                    await status_message.delete()&lt;br /&gt;
                    &lt;br /&gt;
                    # Уникальный ID&lt;br /&gt;
                    search_id = str(uuid.uuid4())[:8]&lt;br /&gt;
                    self.search_results[search_id] = data[&#039;results&#039;]&lt;br /&gt;
                    &lt;br /&gt;
                    for i, photo in enumerate(data[&#039;results&#039;], 1):&lt;br /&gt;
                        caption = f&amp;quot;Вариант {i} из 4\n👤 Автор: {photo[&#039;user&#039;][&#039;name&#039;]}&amp;quot;&lt;br /&gt;
                        &lt;br /&gt;
                        keyboard = [&lt;br /&gt;
                            [InlineKeyboardButton(&lt;br /&gt;
                                &amp;quot;Получить ссылку на источник&amp;quot;, &lt;br /&gt;
                                callback_data=f&amp;quot;get_link_{search_id}_{i-1}&amp;quot;&lt;br /&gt;
                            )]&lt;br /&gt;
                        ]&lt;br /&gt;
                        reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                        &lt;br /&gt;
                        await update.message.reply_photo(&lt;br /&gt;
                            photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                            caption=caption,&lt;br /&gt;
                            reply_markup=reply_markup&lt;br /&gt;
                        )&lt;br /&gt;
                    &lt;br /&gt;
                    # Отправляем сообщение с инструкцией&lt;br /&gt;
                    await update.message.reply_text(&lt;br /&gt;
                        f&amp;quot;✅ Найдено {data[&#039;total&#039;]} фото. Выбери понравившийся вариант, чтобы получить ссылку на источник!&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                else:&lt;br /&gt;
                    await status_message.edit_text(&amp;quot;Ничего не найдено. Попробуй другой запрос.&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                await status_message.edit_text(&amp;quot;❌ Ошибка при поиске фото. Попробуй позже.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_message.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* /random - случайное фото из Unsplash.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Случайное фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        await self.send_random_photo(update, context)&lt;br /&gt;
    &lt;br /&gt;
    async def send_random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Отправка случайного фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # Определяем, откуда пришел запрос&lt;br /&gt;
        if update.callback_query:&lt;br /&gt;
            message = update.callback_query.message&lt;br /&gt;
            await update.callback_query.answer()&lt;br /&gt;
        else:&lt;br /&gt;
            message = update.message&lt;br /&gt;
        &lt;br /&gt;
        query = &#039; &#039;.join(context.args) if context.args and hasattr(context, &#039;args&#039;) else None&lt;br /&gt;
        &lt;br /&gt;
        status_msg = await message.reply_text(&amp;quot;🎲 Получаю случайное фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            params = {}&lt;br /&gt;
            if query:&lt;br /&gt;
                params[&#039;query&#039;] = query&lt;br /&gt;
            &lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos/random&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params=params&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photo = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                caption = (&lt;br /&gt;
                    f&amp;quot;**Случайное фото**\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;📝 **Описание:** {photo.get(&#039;description&#039;, &#039;Нет описания&#039;)}\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Создаем кнопки для действий с фото&lt;br /&gt;
                keyboard = [&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔗 Получить все ссылки&amp;quot;, callback_data=f&amp;quot;get_link_random_{photo[&#039;id&#039;]}&amp;quot;)],&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔄 Еще случайное&amp;quot;, callback_data=&amp;quot;random_photo&amp;quot;)]&lt;br /&gt;
                ]&lt;br /&gt;
                reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                &lt;br /&gt;
                await message.reply_photo(&lt;br /&gt;
                    photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                    caption=caption,&lt;br /&gt;
                    parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                    reply_markup=reply_markup&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Сохраняем фото для возможного запроса ссылок&lt;br /&gt;
                if not hasattr(self, &#039;random_photos&#039;):&lt;br /&gt;
                    self.random_photos = {}&lt;br /&gt;
                self.random_photos[photo[&#039;id&#039;]] = photo&lt;br /&gt;
                &lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при получении случайного фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /popular - случайное фото из подборки &amp;quot;Популярное&amp;quot; из Unsplash. делается наподобие команды /random.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def popular_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Популярные фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        status_msg = await update.message.reply_text(&amp;quot;🔥 Загружаю популярные фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;per_page&#039;: 5, &#039;order_by&#039;: &#039;popular&#039;}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photos = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                for i, photo in enumerate(photos, 1):&lt;br /&gt;
                    caption = (&lt;br /&gt;
                        f&amp;quot;🔥 **Популярное фото #{i}**\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;❤️ **Лайков:** {photo[&#039;likes&#039;]}\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Кнопка для получения ссылок&lt;br /&gt;
                    keyboard = [[InlineKeyboardButton(&amp;quot;🔗 Получить ссылки&amp;quot;, callback_data=f&amp;quot;get_link_popular_{photo[&#039;id&#039;]}&amp;quot;)]]&lt;br /&gt;
                    reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                    &lt;br /&gt;
                    await update.message.reply_photo(&lt;br /&gt;
                        photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                        caption=caption,&lt;br /&gt;
                        parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                        reply_markup=reply_markup&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Сохраняем фото&lt;br /&gt;
                    if not hasattr(self, &#039;popular_photos_cache&#039;):&lt;br /&gt;
                        self.popular_photos_cache = {}&lt;br /&gt;
                    self.popular_photos_cache[photo[&#039;id&#039;]] = photo&lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при загрузке популярных фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Написание обработчика на запуск бота =====&lt;br /&gt;
&lt;br /&gt;
Функция на запуск бота, включает в себя создание приложения, обработчики команд и логирование всех команд в терминале.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    def run(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Запуск бота&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        application = Application.builder().token(self.telegram_token).build()&lt;br /&gt;
        &lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;start&amp;quot;, self.start))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;help&amp;quot;, self.help_command))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;search&amp;quot;, self.search_photos))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;random&amp;quot;, self.random_photo))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;popular&amp;quot;, self.popular_photos))&lt;br /&gt;
        application.add_handler(MessageHandler(filters.TEXT &amp;amp; ~filters.COMMAND, self.handle_message))&lt;br /&gt;
        application.add_handler(CallbackQueryHandler(self.handle_callback))&lt;br /&gt;
        &lt;br /&gt;
        print(&amp;quot;/start&amp;quot;)&lt;br /&gt;
        application.run_polling(allowed_updates=Update.ALL_TYPES)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Вне класса также создаем класс и запускаем бота.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    bot = UnsplashTelegramBot()&lt;br /&gt;
    bot.run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Запуск бота и его тестирование ====&lt;br /&gt;
&lt;br /&gt;
Производим запуск бота через запуск исполняемого python файла:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233335238.png|400px]]&lt;br /&gt;
&lt;br /&gt;
После этого действия можем начинать работать и тестировать бота.&lt;br /&gt;
&lt;br /&gt;
Переходим в телеграм, пишем команду /start&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233503508.png|400px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, запрос прошел успешно, бот ответил то, что я зашил в код программы.&lt;br /&gt;
&lt;br /&gt;
Попробуем получить случайное фото командой /random:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233610248.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233641178.png|200px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь попробуем найти что-то конкретное и посмотреть как это будет выглядеть. Для этого сформируем команду /search. Искать будем собаку с породой самоед:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233810808.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233832396.png|200px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, бот отправил нам 4 варианта фотографий, к каждой из которой добавил ссылку на оригинал.&lt;br /&gt;
&lt;br /&gt;
Притом каждый раз в терминале (консоли) мы можем заметить, как бот поддерживает подключение с Unsplash и логирует отправку сообщений.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 234038505.png|700px]]&lt;br /&gt;
&lt;br /&gt;
=== Методические материалы ===&lt;br /&gt;
&lt;br /&gt;
{{#ask: [[API]] OR [[Python]]  OR [[UML]] OR [[Telegram]] | ?Description }}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Участник:CherenkovIR|CherenkovIR]] ([[Обсуждение участника:CherenkovIR|обсуждение]])&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=45321</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=45321"/>
		<updated>2026-03-26T14:29:26Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: /* Код перед созданием класса = */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с [[API]] фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
pip install -r requirements.txt&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
В соответствии с ранее описанными функциональными требованиями диаграмма [[UML]] ниже&lt;br /&gt;
&lt;br /&gt;
&amp;lt;uml&amp;gt;&lt;br /&gt;
@startuml&lt;br /&gt;
actor Пользователь as User&lt;br /&gt;
participant &amp;quot;Telegram Bot&amp;quot; as TB&lt;br /&gt;
participant &amp;quot;Обработчик&amp;quot; as H&lt;br /&gt;
participant &amp;quot;Unsplash API&amp;quot; as API&lt;br /&gt;
participant &amp;quot;Хранилище&amp;quot; as DB&lt;br /&gt;
&lt;br /&gt;
== Поиск фото ==&lt;br /&gt;
User -&amp;gt; TB: /search природа&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET photos?query=природа&lt;br /&gt;
API --&amp;gt; H: 4 фото&lt;br /&gt;
H -&amp;gt; DB: Сохранить фото&lt;br /&gt;
loop 4 фото&lt;br /&gt;
    H -&amp;gt; TB: Фото + кнопка&lt;br /&gt;
    TB --&amp;gt; User: Показать&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Выбор фото ==&lt;br /&gt;
User -&amp;gt; TB: Нажимает кнопку&lt;br /&gt;
TB -&amp;gt; H: callback_data&lt;br /&gt;
H -&amp;gt; DB: Получить фото&lt;br /&gt;
DB --&amp;gt; H: Данные фото&lt;br /&gt;
H -&amp;gt; TB: Ссылки на фото&lt;br /&gt;
TB --&amp;gt; User: Оригинал\nАвтор\nUnsplash&lt;br /&gt;
&lt;br /&gt;
== Случайное фото ==&lt;br /&gt;
User -&amp;gt; TB: /random&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET random photo&lt;br /&gt;
API --&amp;gt; H: Случайное фото&lt;br /&gt;
H -&amp;gt; TB: Фото + ссылки&lt;br /&gt;
TB --&amp;gt; User: Показать&lt;br /&gt;
&lt;br /&gt;
alt Ошибка&lt;br /&gt;
    H -&amp;gt; TB: Ошибка...&lt;br /&gt;
    TB --&amp;gt; User: Сообщение об ошибке&lt;br /&gt;
end&lt;br /&gt;
@enduml&lt;br /&gt;
&amp;lt;/uml&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Непосредственная работа с кодом приложения ====&lt;br /&gt;
&lt;br /&gt;
===== Код перед созданием класса =====&lt;br /&gt;
&lt;br /&gt;
Импорт вышеописанных библиотек и подгрузка переменных из файла .env:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import logging&lt;br /&gt;
import os&lt;br /&gt;
import uuid&lt;br /&gt;
from dotenv import load_dotenv&lt;br /&gt;
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup&lt;br /&gt;
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes, CallbackQueryHandler&lt;br /&gt;
import requests&lt;br /&gt;
&lt;br /&gt;
load_dotenv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Настройка логирования&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    format=&#039;%(asctime)s - %(name)s - %(levelname)s - %(message)s&#039;,&lt;br /&gt;
    level=logging.INFO&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(__name__)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Работа с классом =====&lt;br /&gt;
&lt;br /&gt;
Далее, так как пользователей у бота может быть несколько - крайне важно создать класс, который поможет работать нескольким пользователям одновременно в соответствие с объектно-ориентированным программированием.&lt;br /&gt;
&lt;br /&gt;
Для этого я создал класс, в котором указал основные метаданные, а также функции:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
class UnsplashTelegramBot:&lt;br /&gt;
    def __init__(self):&lt;br /&gt;
        self.unsplash_key = os.getenv(&#039;UNSPLASH_ACCESS_KEY&#039;)&lt;br /&gt;
        self.telegram_token = os.getenv(&#039;TELEGRAM_BOT_TOKEN&#039;)&lt;br /&gt;
        self.unsplash_url = &amp;quot;https://api.unsplash.com&amp;quot;&lt;br /&gt;
        self.headers = {&lt;br /&gt;
            &#039;Authorization&#039;: f&#039;Client-ID {self.unsplash_key}&#039;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        self.search_results = {}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Программирование функций класса бота ======&lt;br /&gt;
&lt;br /&gt;
* /start - основная команда, которая будет запускать бота и создавать класс в нем. После отправки этой команды пользователь должен получать информацию о боте.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /start&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        welcome_message = (&lt;br /&gt;
            &amp;quot;Привет! Я бот для поиска фотографий на Unsplash.\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Команды:\n&amp;quot;&lt;br /&gt;
            &amp;quot;/search &amp;lt;запрос&amp;gt; - поиск и выбор из 4 фотографий\n&amp;quot;&lt;br /&gt;
            &amp;quot;/random - случайное фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/popular - популярные фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/help - помощь&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(welcome_message)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /help - дополнительная команда, которая будет отчасти повторять функционал команды /start, но описывать функционал более подробно.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /help&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        help_text = (&lt;br /&gt;
            &amp;quot;? Как пользоваться ботом:\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;1. Для поиска фото напиши: /search природа\n&amp;quot;&lt;br /&gt;
            &amp;quot;   Бот покажет 4 фото на выбор\n&amp;quot;&lt;br /&gt;
            &amp;quot;2. Нажми на кнопку под фото, чтобы получить ссылку на источник\n&amp;quot;&lt;br /&gt;
            &amp;quot;3. Для случайного фото: /random\n&amp;quot;&lt;br /&gt;
            &amp;quot;4. Для популярных фото: /popular\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Также можно просто отправить текстовый запрос для поиска!&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(help_text)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /search или сообщение без команды - основа всего бота, поиск фотографий и показ 4-х фотографий.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def search_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Поиск фотографий и показ 4 вариантов&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        if context.args:&lt;br /&gt;
            query = &#039; &#039;.join(context.args)&lt;br /&gt;
        else:&lt;br /&gt;
            # Команда без арг-тов, просто сообщение&lt;br /&gt;
            if update.message and update.message.text and not update.message.text.startswith(&#039;/&#039;):&lt;br /&gt;
                query = update.message.text&lt;br /&gt;
            else:&lt;br /&gt;
                await update.message.reply_text(&amp;quot;Пожалуйста, укажи запрос. Например: /search природа&amp;quot;)&lt;br /&gt;
                return&lt;br /&gt;
        &lt;br /&gt;
        # Отправляем сообщение о поиске&lt;br /&gt;
        status_message = await update.message.reply_text(f&amp;quot;Ищу фото по запросу: {query}...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            # Получаем 4 фотографии&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/search/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;query&#039;: query, &#039;per_page&#039;: 4, &#039;page&#039;: 1}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                data = response.json()&lt;br /&gt;
                if data[&#039;results&#039;]:&lt;br /&gt;
                    # Удаляем сообщение о поиске&lt;br /&gt;
                    await status_message.delete()&lt;br /&gt;
                    &lt;br /&gt;
                    # Уникальный ID&lt;br /&gt;
                    search_id = str(uuid.uuid4())[:8]&lt;br /&gt;
                    self.search_results[search_id] = data[&#039;results&#039;]&lt;br /&gt;
                    &lt;br /&gt;
                    for i, photo in enumerate(data[&#039;results&#039;], 1):&lt;br /&gt;
                        caption = f&amp;quot;Вариант {i} из 4\n👤 Автор: {photo[&#039;user&#039;][&#039;name&#039;]}&amp;quot;&lt;br /&gt;
                        &lt;br /&gt;
                        keyboard = [&lt;br /&gt;
                            [InlineKeyboardButton(&lt;br /&gt;
                                &amp;quot;Получить ссылку на источник&amp;quot;, &lt;br /&gt;
                                callback_data=f&amp;quot;get_link_{search_id}_{i-1}&amp;quot;&lt;br /&gt;
                            )]&lt;br /&gt;
                        ]&lt;br /&gt;
                        reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                        &lt;br /&gt;
                        await update.message.reply_photo(&lt;br /&gt;
                            photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                            caption=caption,&lt;br /&gt;
                            reply_markup=reply_markup&lt;br /&gt;
                        )&lt;br /&gt;
                    &lt;br /&gt;
                    # Отправляем сообщение с инструкцией&lt;br /&gt;
                    await update.message.reply_text(&lt;br /&gt;
                        f&amp;quot;✅ Найдено {data[&#039;total&#039;]} фото. Выбери понравившийся вариант, чтобы получить ссылку на источник!&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                else:&lt;br /&gt;
                    await status_message.edit_text(&amp;quot;Ничего не найдено. Попробуй другой запрос.&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                await status_message.edit_text(&amp;quot;❌ Ошибка при поиске фото. Попробуй позже.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_message.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* /random - случайное фото из Unsplash.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Случайное фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        await self.send_random_photo(update, context)&lt;br /&gt;
    &lt;br /&gt;
    async def send_random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Отправка случайного фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # Определяем, откуда пришел запрос&lt;br /&gt;
        if update.callback_query:&lt;br /&gt;
            message = update.callback_query.message&lt;br /&gt;
            await update.callback_query.answer()&lt;br /&gt;
        else:&lt;br /&gt;
            message = update.message&lt;br /&gt;
        &lt;br /&gt;
        query = &#039; &#039;.join(context.args) if context.args and hasattr(context, &#039;args&#039;) else None&lt;br /&gt;
        &lt;br /&gt;
        status_msg = await message.reply_text(&amp;quot;🎲 Получаю случайное фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            params = {}&lt;br /&gt;
            if query:&lt;br /&gt;
                params[&#039;query&#039;] = query&lt;br /&gt;
            &lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos/random&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params=params&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photo = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                caption = (&lt;br /&gt;
                    f&amp;quot;**Случайное фото**\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;📝 **Описание:** {photo.get(&#039;description&#039;, &#039;Нет описания&#039;)}\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Создаем кнопки для действий с фото&lt;br /&gt;
                keyboard = [&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔗 Получить все ссылки&amp;quot;, callback_data=f&amp;quot;get_link_random_{photo[&#039;id&#039;]}&amp;quot;)],&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔄 Еще случайное&amp;quot;, callback_data=&amp;quot;random_photo&amp;quot;)]&lt;br /&gt;
                ]&lt;br /&gt;
                reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                &lt;br /&gt;
                await message.reply_photo(&lt;br /&gt;
                    photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                    caption=caption,&lt;br /&gt;
                    parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                    reply_markup=reply_markup&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Сохраняем фото для возможного запроса ссылок&lt;br /&gt;
                if not hasattr(self, &#039;random_photos&#039;):&lt;br /&gt;
                    self.random_photos = {}&lt;br /&gt;
                self.random_photos[photo[&#039;id&#039;]] = photo&lt;br /&gt;
                &lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при получении случайного фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /popular - случайное фото из подборки &amp;quot;Популярное&amp;quot; из Unsplash. делается наподобие команды /random.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def popular_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Популярные фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        status_msg = await update.message.reply_text(&amp;quot;🔥 Загружаю популярные фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;per_page&#039;: 5, &#039;order_by&#039;: &#039;popular&#039;}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photos = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                for i, photo in enumerate(photos, 1):&lt;br /&gt;
                    caption = (&lt;br /&gt;
                        f&amp;quot;🔥 **Популярное фото #{i}**\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;❤️ **Лайков:** {photo[&#039;likes&#039;]}\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Кнопка для получения ссылок&lt;br /&gt;
                    keyboard = [[InlineKeyboardButton(&amp;quot;🔗 Получить ссылки&amp;quot;, callback_data=f&amp;quot;get_link_popular_{photo[&#039;id&#039;]}&amp;quot;)]]&lt;br /&gt;
                    reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                    &lt;br /&gt;
                    await update.message.reply_photo(&lt;br /&gt;
                        photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                        caption=caption,&lt;br /&gt;
                        parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                        reply_markup=reply_markup&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Сохраняем фото&lt;br /&gt;
                    if not hasattr(self, &#039;popular_photos_cache&#039;):&lt;br /&gt;
                        self.popular_photos_cache = {}&lt;br /&gt;
                    self.popular_photos_cache[photo[&#039;id&#039;]] = photo&lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при загрузке популярных фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Написание обработчика на запуск бота =====&lt;br /&gt;
&lt;br /&gt;
Функция на запуск бота, включает в себя создание приложения, обработчики команд и логирование всех команд в терминале.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    def run(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Запуск бота&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        application = Application.builder().token(self.telegram_token).build()&lt;br /&gt;
        &lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;start&amp;quot;, self.start))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;help&amp;quot;, self.help_command))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;search&amp;quot;, self.search_photos))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;random&amp;quot;, self.random_photo))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;popular&amp;quot;, self.popular_photos))&lt;br /&gt;
        application.add_handler(MessageHandler(filters.TEXT &amp;amp; ~filters.COMMAND, self.handle_message))&lt;br /&gt;
        application.add_handler(CallbackQueryHandler(self.handle_callback))&lt;br /&gt;
        &lt;br /&gt;
        print(&amp;quot;/start&amp;quot;)&lt;br /&gt;
        application.run_polling(allowed_updates=Update.ALL_TYPES)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Вне класса также создаем класс и запускаем бота.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    bot = UnsplashTelegramBot()&lt;br /&gt;
    bot.run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Запуск бота и его тестирование ====&lt;br /&gt;
&lt;br /&gt;
Производим запуск бота через запуск исполняемого python файла:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233335238.png|400px]]&lt;br /&gt;
&lt;br /&gt;
После этого действия можем начинать работать и тестировать бота.&lt;br /&gt;
&lt;br /&gt;
Переходим в телеграм, пишем команду /start&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233503508.png|400px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, запрос прошел успешно, бот ответил то, что я зашил в код программы.&lt;br /&gt;
&lt;br /&gt;
Попробуем получить случайное фото командой /random:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233610248.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233641178.png|200px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь попробуем найти что-то конкретное и посмотреть как это будет выглядеть. Для этого сформируем команду /search. Искать будем собаку с породой самоед:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233810808.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233832396.png|200px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, бот отправил нам 4 варианта фотографий, к каждой из которой добавил ссылку на оригинал.&lt;br /&gt;
&lt;br /&gt;
Притом каждый раз в терминале (консоли) мы можем заметить, как бот поддерживает подключение с Unsplash и логирует отправку сообщений.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 234038505.png|700px]]&lt;br /&gt;
&lt;br /&gt;
=== Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;br /&gt;
* [[UML]]&lt;br /&gt;
{{#ask: [[UML]] | ?Description }}&lt;br /&gt;
* [[Telegram]]&lt;br /&gt;
{{#ask: [[Telegram]] | ?Description }}&lt;br /&gt;
* [[API]]&lt;br /&gt;
{{#ask: [[API]] | ?Description }}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{#ask: [[API]] OR [[Python]]  OR [[UML]] | ?Description }}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Участник:CherenkovIR|CherenkovIR]] ([[Обсуждение участника:CherenkovIR|обсуждение]])&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44809</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44809"/>
		<updated>2026-03-05T20:41:07Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с API фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
pip install -r requirements.txt&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
В соответствии с ранее описанными функциональными требованиями диаграмма [[UML]] ниже&lt;br /&gt;
&lt;br /&gt;
&amp;lt;uml&amp;gt;&lt;br /&gt;
@startuml&lt;br /&gt;
actor Пользователь as User&lt;br /&gt;
participant &amp;quot;Telegram Bot&amp;quot; as TB&lt;br /&gt;
participant &amp;quot;Обработчик&amp;quot; as H&lt;br /&gt;
participant &amp;quot;Unsplash API&amp;quot; as API&lt;br /&gt;
participant &amp;quot;Хранилище&amp;quot; as DB&lt;br /&gt;
&lt;br /&gt;
== Поиск фото ==&lt;br /&gt;
User -&amp;gt; TB: /search природа&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET photos?query=природа&lt;br /&gt;
API --&amp;gt; H: 4 фото&lt;br /&gt;
H -&amp;gt; DB: Сохранить фото&lt;br /&gt;
loop 4 фото&lt;br /&gt;
    H -&amp;gt; TB: Фото + кнопка&lt;br /&gt;
    TB --&amp;gt; User: Показать&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Выбор фото ==&lt;br /&gt;
User -&amp;gt; TB: Нажимает кнопку&lt;br /&gt;
TB -&amp;gt; H: callback_data&lt;br /&gt;
H -&amp;gt; DB: Получить фото&lt;br /&gt;
DB --&amp;gt; H: Данные фото&lt;br /&gt;
H -&amp;gt; TB: Ссылки на фото&lt;br /&gt;
TB --&amp;gt; User: Оригинал\nАвтор\nUnsplash&lt;br /&gt;
&lt;br /&gt;
== Случайное фото ==&lt;br /&gt;
User -&amp;gt; TB: /random&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET random photo&lt;br /&gt;
API --&amp;gt; H: Случайное фото&lt;br /&gt;
H -&amp;gt; TB: Фото + ссылки&lt;br /&gt;
TB --&amp;gt; User: Показать&lt;br /&gt;
&lt;br /&gt;
alt Ошибка&lt;br /&gt;
    H -&amp;gt; TB: Ошибка...&lt;br /&gt;
    TB --&amp;gt; User: Сообщение об ошибке&lt;br /&gt;
end&lt;br /&gt;
@enduml&lt;br /&gt;
&amp;lt;/uml&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Непосредственная работа с кодом приложения ====&lt;br /&gt;
&lt;br /&gt;
===== Код перед созданием класса ======&lt;br /&gt;
&lt;br /&gt;
Импорт вышеописанных библиотек и подгрузка переменных из файла .env:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import logging&lt;br /&gt;
import os&lt;br /&gt;
import uuid&lt;br /&gt;
from dotenv import load_dotenv&lt;br /&gt;
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup&lt;br /&gt;
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes, CallbackQueryHandler&lt;br /&gt;
import requests&lt;br /&gt;
&lt;br /&gt;
load_dotenv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Настройка логирования&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    format=&#039;%(asctime)s - %(name)s - %(levelname)s - %(message)s&#039;,&lt;br /&gt;
    level=logging.INFO&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(__name__)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Работа с классом =====&lt;br /&gt;
&lt;br /&gt;
Далее, так как пользователей у бота может быть несколько - крайне важно создать класс, который поможет работать нескольким пользователям одновременно в соответствие с объектно-ориентированным программированием.&lt;br /&gt;
&lt;br /&gt;
Для этого я создал класс, в котором указал основные метаданные, а также функции:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
class UnsplashTelegramBot:&lt;br /&gt;
    def __init__(self):&lt;br /&gt;
        self.unsplash_key = os.getenv(&#039;UNSPLASH_ACCESS_KEY&#039;)&lt;br /&gt;
        self.telegram_token = os.getenv(&#039;TELEGRAM_BOT_TOKEN&#039;)&lt;br /&gt;
        self.unsplash_url = &amp;quot;https://api.unsplash.com&amp;quot;&lt;br /&gt;
        self.headers = {&lt;br /&gt;
            &#039;Authorization&#039;: f&#039;Client-ID {self.unsplash_key}&#039;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        self.search_results = {}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Программирование функций класса бота ======&lt;br /&gt;
&lt;br /&gt;
* /start - основная команда, которая будет запускать бота и создавать класс в нем. После отправки этой команды пользователь должен получать информацию о боте.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /start&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        welcome_message = (&lt;br /&gt;
            &amp;quot;Привет! Я бот для поиска фотографий на Unsplash.\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Команды:\n&amp;quot;&lt;br /&gt;
            &amp;quot;/search &amp;lt;запрос&amp;gt; - поиск и выбор из 4 фотографий\n&amp;quot;&lt;br /&gt;
            &amp;quot;/random - случайное фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/popular - популярные фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/help - помощь&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(welcome_message)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /help - дополнительная команда, которая будет отчасти повторять функционал команды /start, но описывать функционал более подробно.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /help&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        help_text = (&lt;br /&gt;
            &amp;quot;? Как пользоваться ботом:\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;1. Для поиска фото напиши: /search природа\n&amp;quot;&lt;br /&gt;
            &amp;quot;   Бот покажет 4 фото на выбор\n&amp;quot;&lt;br /&gt;
            &amp;quot;2. Нажми на кнопку под фото, чтобы получить ссылку на источник\n&amp;quot;&lt;br /&gt;
            &amp;quot;3. Для случайного фото: /random\n&amp;quot;&lt;br /&gt;
            &amp;quot;4. Для популярных фото: /popular\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Также можно просто отправить текстовый запрос для поиска!&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(help_text)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /search или сообщение без команды - основа всего бота, поиск фотографий и показ 4-х фотографий.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def search_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Поиск фотографий и показ 4 вариантов&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        if context.args:&lt;br /&gt;
            query = &#039; &#039;.join(context.args)&lt;br /&gt;
        else:&lt;br /&gt;
            # Команда без арг-тов, просто сообщение&lt;br /&gt;
            if update.message and update.message.text and not update.message.text.startswith(&#039;/&#039;):&lt;br /&gt;
                query = update.message.text&lt;br /&gt;
            else:&lt;br /&gt;
                await update.message.reply_text(&amp;quot;Пожалуйста, укажи запрос. Например: /search природа&amp;quot;)&lt;br /&gt;
                return&lt;br /&gt;
        &lt;br /&gt;
        # Отправляем сообщение о поиске&lt;br /&gt;
        status_message = await update.message.reply_text(f&amp;quot;Ищу фото по запросу: {query}...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            # Получаем 4 фотографии&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/search/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;query&#039;: query, &#039;per_page&#039;: 4, &#039;page&#039;: 1}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                data = response.json()&lt;br /&gt;
                if data[&#039;results&#039;]:&lt;br /&gt;
                    # Удаляем сообщение о поиске&lt;br /&gt;
                    await status_message.delete()&lt;br /&gt;
                    &lt;br /&gt;
                    # Уникальный ID&lt;br /&gt;
                    search_id = str(uuid.uuid4())[:8]&lt;br /&gt;
                    self.search_results[search_id] = data[&#039;results&#039;]&lt;br /&gt;
                    &lt;br /&gt;
                    for i, photo in enumerate(data[&#039;results&#039;], 1):&lt;br /&gt;
                        caption = f&amp;quot;Вариант {i} из 4\n👤 Автор: {photo[&#039;user&#039;][&#039;name&#039;]}&amp;quot;&lt;br /&gt;
                        &lt;br /&gt;
                        keyboard = [&lt;br /&gt;
                            [InlineKeyboardButton(&lt;br /&gt;
                                &amp;quot;Получить ссылку на источник&amp;quot;, &lt;br /&gt;
                                callback_data=f&amp;quot;get_link_{search_id}_{i-1}&amp;quot;&lt;br /&gt;
                            )]&lt;br /&gt;
                        ]&lt;br /&gt;
                        reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                        &lt;br /&gt;
                        await update.message.reply_photo(&lt;br /&gt;
                            photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                            caption=caption,&lt;br /&gt;
                            reply_markup=reply_markup&lt;br /&gt;
                        )&lt;br /&gt;
                    &lt;br /&gt;
                    # Отправляем сообщение с инструкцией&lt;br /&gt;
                    await update.message.reply_text(&lt;br /&gt;
                        f&amp;quot;✅ Найдено {data[&#039;total&#039;]} фото. Выбери понравившийся вариант, чтобы получить ссылку на источник!&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                else:&lt;br /&gt;
                    await status_message.edit_text(&amp;quot;Ничего не найдено. Попробуй другой запрос.&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                await status_message.edit_text(&amp;quot;❌ Ошибка при поиске фото. Попробуй позже.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_message.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* /random - случайное фото из Unsplash.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Случайное фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        await self.send_random_photo(update, context)&lt;br /&gt;
    &lt;br /&gt;
    async def send_random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Отправка случайного фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # Определяем, откуда пришел запрос&lt;br /&gt;
        if update.callback_query:&lt;br /&gt;
            message = update.callback_query.message&lt;br /&gt;
            await update.callback_query.answer()&lt;br /&gt;
        else:&lt;br /&gt;
            message = update.message&lt;br /&gt;
        &lt;br /&gt;
        query = &#039; &#039;.join(context.args) if context.args and hasattr(context, &#039;args&#039;) else None&lt;br /&gt;
        &lt;br /&gt;
        status_msg = await message.reply_text(&amp;quot;🎲 Получаю случайное фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            params = {}&lt;br /&gt;
            if query:&lt;br /&gt;
                params[&#039;query&#039;] = query&lt;br /&gt;
            &lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos/random&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params=params&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photo = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                caption = (&lt;br /&gt;
                    f&amp;quot;**Случайное фото**\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;📝 **Описание:** {photo.get(&#039;description&#039;, &#039;Нет описания&#039;)}\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Создаем кнопки для действий с фото&lt;br /&gt;
                keyboard = [&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔗 Получить все ссылки&amp;quot;, callback_data=f&amp;quot;get_link_random_{photo[&#039;id&#039;]}&amp;quot;)],&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔄 Еще случайное&amp;quot;, callback_data=&amp;quot;random_photo&amp;quot;)]&lt;br /&gt;
                ]&lt;br /&gt;
                reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                &lt;br /&gt;
                await message.reply_photo(&lt;br /&gt;
                    photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                    caption=caption,&lt;br /&gt;
                    parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                    reply_markup=reply_markup&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Сохраняем фото для возможного запроса ссылок&lt;br /&gt;
                if not hasattr(self, &#039;random_photos&#039;):&lt;br /&gt;
                    self.random_photos = {}&lt;br /&gt;
                self.random_photos[photo[&#039;id&#039;]] = photo&lt;br /&gt;
                &lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при получении случайного фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /popular - случайное фото из подборки &amp;quot;Популярное&amp;quot; из Unsplash. делается наподобие команды /random.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def popular_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Популярные фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        status_msg = await update.message.reply_text(&amp;quot;🔥 Загружаю популярные фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;per_page&#039;: 5, &#039;order_by&#039;: &#039;popular&#039;}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photos = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                for i, photo in enumerate(photos, 1):&lt;br /&gt;
                    caption = (&lt;br /&gt;
                        f&amp;quot;🔥 **Популярное фото #{i}**\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;❤️ **Лайков:** {photo[&#039;likes&#039;]}\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Кнопка для получения ссылок&lt;br /&gt;
                    keyboard = [[InlineKeyboardButton(&amp;quot;🔗 Получить ссылки&amp;quot;, callback_data=f&amp;quot;get_link_popular_{photo[&#039;id&#039;]}&amp;quot;)]]&lt;br /&gt;
                    reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                    &lt;br /&gt;
                    await update.message.reply_photo(&lt;br /&gt;
                        photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                        caption=caption,&lt;br /&gt;
                        parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                        reply_markup=reply_markup&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Сохраняем фото&lt;br /&gt;
                    if not hasattr(self, &#039;popular_photos_cache&#039;):&lt;br /&gt;
                        self.popular_photos_cache = {}&lt;br /&gt;
                    self.popular_photos_cache[photo[&#039;id&#039;]] = photo&lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при загрузке популярных фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Написание обработчика на запуск бота =====&lt;br /&gt;
&lt;br /&gt;
Функция на запуск бота, включает в себя создание приложения, обработчики команд и логирование всех команд в терминале.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    def run(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Запуск бота&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        application = Application.builder().token(self.telegram_token).build()&lt;br /&gt;
        &lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;start&amp;quot;, self.start))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;help&amp;quot;, self.help_command))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;search&amp;quot;, self.search_photos))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;random&amp;quot;, self.random_photo))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;popular&amp;quot;, self.popular_photos))&lt;br /&gt;
        application.add_handler(MessageHandler(filters.TEXT &amp;amp; ~filters.COMMAND, self.handle_message))&lt;br /&gt;
        application.add_handler(CallbackQueryHandler(self.handle_callback))&lt;br /&gt;
        &lt;br /&gt;
        print(&amp;quot;/start&amp;quot;)&lt;br /&gt;
        application.run_polling(allowed_updates=Update.ALL_TYPES)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Вне класса также создаем класс и запускаем бота.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    bot = UnsplashTelegramBot()&lt;br /&gt;
    bot.run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Запуск бота и его тестирование ====&lt;br /&gt;
&lt;br /&gt;
Производим запуск бота через запуск исполняемого python файла:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233335238.png|400px]]&lt;br /&gt;
&lt;br /&gt;
После этого действия можем начинать работать и тестировать бота.&lt;br /&gt;
&lt;br /&gt;
Переходим в телеграм, пишем команду /start&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233503508.png|400px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, запрос прошел успешно, бот ответил то, что я зашил в код программы.&lt;br /&gt;
&lt;br /&gt;
Попробуем получить случайное фото командой /random:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233610248.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233641178.png|200px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь попробуем найти что-то конкретное и посмотреть как это будет выглядеть. Для этого сформируем команду /search. Искать будем собаку с породой самоед:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233810808.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233832396.png|200px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, бот отправил нам 4 варианта фотографий, к каждой из которой добавил ссылку на оригинал.&lt;br /&gt;
&lt;br /&gt;
Притом каждый раз в терминале (консоли) мы можем заметить, как бот поддерживает подключение с Unsplash и логирует отправку сообщений.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 234038505.png|700px]]&lt;br /&gt;
&lt;br /&gt;
=== Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;br /&gt;
* [[UML]]&lt;br /&gt;
{{#ask: [[UML]] | ?Description }}&lt;br /&gt;
* [[Telegram]]&lt;br /&gt;
{{#ask: [[Telegram]] | ?Description }}&lt;br /&gt;
* [[API]]&lt;br /&gt;
{{#ask: [[API]] | ?Description }}&lt;br /&gt;
&lt;br /&gt;
[[Участник:CherenkovIR|CherenkovIR]] ([[Обсуждение участника:CherenkovIR|обсуждение]])&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44808</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44808"/>
		<updated>2026-03-05T20:40:53Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с API фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
pip install -r requirements.txt&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
В соответствии с ранее описанными функциональными требованиями диаграмма [[UML]] ниже&lt;br /&gt;
&lt;br /&gt;
&amp;lt;uml&amp;gt;&lt;br /&gt;
@startuml&lt;br /&gt;
actor Пользователь as User&lt;br /&gt;
participant &amp;quot;Telegram Bot&amp;quot; as TB&lt;br /&gt;
participant &amp;quot;Обработчик&amp;quot; as H&lt;br /&gt;
participant &amp;quot;Unsplash API&amp;quot; as API&lt;br /&gt;
participant &amp;quot;Хранилище&amp;quot; as DB&lt;br /&gt;
&lt;br /&gt;
== Поиск фото ==&lt;br /&gt;
User -&amp;gt; TB: /search природа&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET photos?query=природа&lt;br /&gt;
API --&amp;gt; H: 4 фото&lt;br /&gt;
H -&amp;gt; DB: Сохранить фото&lt;br /&gt;
loop 4 фото&lt;br /&gt;
    H -&amp;gt; TB: Фото + кнопка&lt;br /&gt;
    TB --&amp;gt; User: Показать&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Выбор фото ==&lt;br /&gt;
User -&amp;gt; TB: Нажимает кнопку&lt;br /&gt;
TB -&amp;gt; H: callback_data&lt;br /&gt;
H -&amp;gt; DB: Получить фото&lt;br /&gt;
DB --&amp;gt; H: Данные фото&lt;br /&gt;
H -&amp;gt; TB: Ссылки на фото&lt;br /&gt;
TB --&amp;gt; User: Оригинал\nАвтор\nUnsplash&lt;br /&gt;
&lt;br /&gt;
== Случайное фото ==&lt;br /&gt;
User -&amp;gt; TB: /random&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET random photo&lt;br /&gt;
API --&amp;gt; H: Случайное фото&lt;br /&gt;
H -&amp;gt; TB: Фото + ссылки&lt;br /&gt;
TB --&amp;gt; User: Показать&lt;br /&gt;
&lt;br /&gt;
alt Ошибка&lt;br /&gt;
    H -&amp;gt; TB: Ошибка...&lt;br /&gt;
    TB --&amp;gt; User: Сообщение об ошибке&lt;br /&gt;
end&lt;br /&gt;
@enduml&lt;br /&gt;
&amp;lt;/uml&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Непосредственная работа с кодом приложения ====&lt;br /&gt;
&lt;br /&gt;
===== Код перед созданием класса ======&lt;br /&gt;
&lt;br /&gt;
Импорт вышеописанных библиотек и подгрузка переменных из файла .env:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import logging&lt;br /&gt;
import os&lt;br /&gt;
import uuid&lt;br /&gt;
from dotenv import load_dotenv&lt;br /&gt;
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup&lt;br /&gt;
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes, CallbackQueryHandler&lt;br /&gt;
import requests&lt;br /&gt;
&lt;br /&gt;
load_dotenv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Настройка логирования&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    format=&#039;%(asctime)s - %(name)s - %(levelname)s - %(message)s&#039;,&lt;br /&gt;
    level=logging.INFO&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(__name__)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Работа с классом =====&lt;br /&gt;
&lt;br /&gt;
Далее, так как пользователей у бота может быть несколько - крайне важно создать класс, который поможет работать нескольким пользователям одновременно в соответствие с объектно-ориентированным программированием.&lt;br /&gt;
&lt;br /&gt;
Для этого я создал класс, в котором указал основные метаданные, а также функции:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
class UnsplashTelegramBot:&lt;br /&gt;
    def __init__(self):&lt;br /&gt;
        self.unsplash_key = os.getenv(&#039;UNSPLASH_ACCESS_KEY&#039;)&lt;br /&gt;
        self.telegram_token = os.getenv(&#039;TELEGRAM_BOT_TOKEN&#039;)&lt;br /&gt;
        self.unsplash_url = &amp;quot;https://api.unsplash.com&amp;quot;&lt;br /&gt;
        self.headers = {&lt;br /&gt;
            &#039;Authorization&#039;: f&#039;Client-ID {self.unsplash_key}&#039;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        self.search_results = {}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Программирование функций класса бота ======&lt;br /&gt;
&lt;br /&gt;
* /start - основная команда, которая будет запускать бота и создавать класс в нем. После отправки этой команды пользователь должен получать информацию о боте.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /start&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        welcome_message = (&lt;br /&gt;
            &amp;quot;Привет! Я бот для поиска фотографий на Unsplash.\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Команды:\n&amp;quot;&lt;br /&gt;
            &amp;quot;/search &amp;lt;запрос&amp;gt; - поиск и выбор из 4 фотографий\n&amp;quot;&lt;br /&gt;
            &amp;quot;/random - случайное фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/popular - популярные фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/help - помощь&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(welcome_message)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /help - дополнительная команда, которая будет отчасти повторять функционал команды /start, но описывать функционал более подробно.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /help&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        help_text = (&lt;br /&gt;
            &amp;quot;? Как пользоваться ботом:\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;1. Для поиска фото напиши: /search природа\n&amp;quot;&lt;br /&gt;
            &amp;quot;   Бот покажет 4 фото на выбор\n&amp;quot;&lt;br /&gt;
            &amp;quot;2. Нажми на кнопку под фото, чтобы получить ссылку на источник\n&amp;quot;&lt;br /&gt;
            &amp;quot;3. Для случайного фото: /random\n&amp;quot;&lt;br /&gt;
            &amp;quot;4. Для популярных фото: /popular\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Также можно просто отправить текстовый запрос для поиска!&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(help_text)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /search или сообщение без команды - основа всего бота, поиск фотографий и показ 4-х фотографий.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def search_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Поиск фотографий и показ 4 вариантов&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        if context.args:&lt;br /&gt;
            query = &#039; &#039;.join(context.args)&lt;br /&gt;
        else:&lt;br /&gt;
            # Команда без арг-тов, просто сообщение&lt;br /&gt;
            if update.message and update.message.text and not update.message.text.startswith(&#039;/&#039;):&lt;br /&gt;
                query = update.message.text&lt;br /&gt;
            else:&lt;br /&gt;
                await update.message.reply_text(&amp;quot;Пожалуйста, укажи запрос. Например: /search природа&amp;quot;)&lt;br /&gt;
                return&lt;br /&gt;
        &lt;br /&gt;
        # Отправляем сообщение о поиске&lt;br /&gt;
        status_message = await update.message.reply_text(f&amp;quot;Ищу фото по запросу: {query}...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            # Получаем 4 фотографии&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/search/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;query&#039;: query, &#039;per_page&#039;: 4, &#039;page&#039;: 1}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                data = response.json()&lt;br /&gt;
                if data[&#039;results&#039;]:&lt;br /&gt;
                    # Удаляем сообщение о поиске&lt;br /&gt;
                    await status_message.delete()&lt;br /&gt;
                    &lt;br /&gt;
                    # Уникальный ID&lt;br /&gt;
                    search_id = str(uuid.uuid4())[:8]&lt;br /&gt;
                    self.search_results[search_id] = data[&#039;results&#039;]&lt;br /&gt;
                    &lt;br /&gt;
                    for i, photo in enumerate(data[&#039;results&#039;], 1):&lt;br /&gt;
                        caption = f&amp;quot;Вариант {i} из 4\n👤 Автор: {photo[&#039;user&#039;][&#039;name&#039;]}&amp;quot;&lt;br /&gt;
                        &lt;br /&gt;
                        keyboard = [&lt;br /&gt;
                            [InlineKeyboardButton(&lt;br /&gt;
                                &amp;quot;Получить ссылку на источник&amp;quot;, &lt;br /&gt;
                                callback_data=f&amp;quot;get_link_{search_id}_{i-1}&amp;quot;&lt;br /&gt;
                            )]&lt;br /&gt;
                        ]&lt;br /&gt;
                        reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                        &lt;br /&gt;
                        await update.message.reply_photo(&lt;br /&gt;
                            photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                            caption=caption,&lt;br /&gt;
                            reply_markup=reply_markup&lt;br /&gt;
                        )&lt;br /&gt;
                    &lt;br /&gt;
                    # Отправляем сообщение с инструкцией&lt;br /&gt;
                    await update.message.reply_text(&lt;br /&gt;
                        f&amp;quot;✅ Найдено {data[&#039;total&#039;]} фото. Выбери понравившийся вариант, чтобы получить ссылку на источник!&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                else:&lt;br /&gt;
                    await status_message.edit_text(&amp;quot;Ничего не найдено. Попробуй другой запрос.&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                await status_message.edit_text(&amp;quot;❌ Ошибка при поиске фото. Попробуй позже.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_message.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* /random - случайное фото из Unsplash.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Случайное фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        await self.send_random_photo(update, context)&lt;br /&gt;
    &lt;br /&gt;
    async def send_random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Отправка случайного фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # Определяем, откуда пришел запрос&lt;br /&gt;
        if update.callback_query:&lt;br /&gt;
            message = update.callback_query.message&lt;br /&gt;
            await update.callback_query.answer()&lt;br /&gt;
        else:&lt;br /&gt;
            message = update.message&lt;br /&gt;
        &lt;br /&gt;
        query = &#039; &#039;.join(context.args) if context.args and hasattr(context, &#039;args&#039;) else None&lt;br /&gt;
        &lt;br /&gt;
        status_msg = await message.reply_text(&amp;quot;🎲 Получаю случайное фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            params = {}&lt;br /&gt;
            if query:&lt;br /&gt;
                params[&#039;query&#039;] = query&lt;br /&gt;
            &lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos/random&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params=params&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photo = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                caption = (&lt;br /&gt;
                    f&amp;quot;**Случайное фото**\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;📝 **Описание:** {photo.get(&#039;description&#039;, &#039;Нет описания&#039;)}\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Создаем кнопки для действий с фото&lt;br /&gt;
                keyboard = [&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔗 Получить все ссылки&amp;quot;, callback_data=f&amp;quot;get_link_random_{photo[&#039;id&#039;]}&amp;quot;)],&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔄 Еще случайное&amp;quot;, callback_data=&amp;quot;random_photo&amp;quot;)]&lt;br /&gt;
                ]&lt;br /&gt;
                reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                &lt;br /&gt;
                await message.reply_photo(&lt;br /&gt;
                    photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                    caption=caption,&lt;br /&gt;
                    parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                    reply_markup=reply_markup&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Сохраняем фото для возможного запроса ссылок&lt;br /&gt;
                if not hasattr(self, &#039;random_photos&#039;):&lt;br /&gt;
                    self.random_photos = {}&lt;br /&gt;
                self.random_photos[photo[&#039;id&#039;]] = photo&lt;br /&gt;
                &lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при получении случайного фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /popular - случайное фото из подборки &amp;quot;Популярное&amp;quot; из Unsplash. делается наподобие команды /random.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def popular_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Популярные фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        status_msg = await update.message.reply_text(&amp;quot;🔥 Загружаю популярные фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;per_page&#039;: 5, &#039;order_by&#039;: &#039;popular&#039;}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photos = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                for i, photo in enumerate(photos, 1):&lt;br /&gt;
                    caption = (&lt;br /&gt;
                        f&amp;quot;🔥 **Популярное фото #{i}**\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;❤️ **Лайков:** {photo[&#039;likes&#039;]}\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Кнопка для получения ссылок&lt;br /&gt;
                    keyboard = [[InlineKeyboardButton(&amp;quot;🔗 Получить ссылки&amp;quot;, callback_data=f&amp;quot;get_link_popular_{photo[&#039;id&#039;]}&amp;quot;)]]&lt;br /&gt;
                    reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                    &lt;br /&gt;
                    await update.message.reply_photo(&lt;br /&gt;
                        photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                        caption=caption,&lt;br /&gt;
                        parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                        reply_markup=reply_markup&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Сохраняем фото&lt;br /&gt;
                    if not hasattr(self, &#039;popular_photos_cache&#039;):&lt;br /&gt;
                        self.popular_photos_cache = {}&lt;br /&gt;
                    self.popular_photos_cache[photo[&#039;id&#039;]] = photo&lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при загрузке популярных фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Написание обработчика на запуск бота =====&lt;br /&gt;
&lt;br /&gt;
Функция на запуск бота, включает в себя создание приложения, обработчики команд и логирование всех команд в терминале.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    def run(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Запуск бота&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        application = Application.builder().token(self.telegram_token).build()&lt;br /&gt;
        &lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;start&amp;quot;, self.start))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;help&amp;quot;, self.help_command))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;search&amp;quot;, self.search_photos))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;random&amp;quot;, self.random_photo))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;popular&amp;quot;, self.popular_photos))&lt;br /&gt;
        application.add_handler(MessageHandler(filters.TEXT &amp;amp; ~filters.COMMAND, self.handle_message))&lt;br /&gt;
        application.add_handler(CallbackQueryHandler(self.handle_callback))&lt;br /&gt;
        &lt;br /&gt;
        print(&amp;quot;/start&amp;quot;)&lt;br /&gt;
        application.run_polling(allowed_updates=Update.ALL_TYPES)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Вне класса также создаем класс и запускаем бота.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    bot = UnsplashTelegramBot()&lt;br /&gt;
    bot.run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Запуск бота и его тестирование ====&lt;br /&gt;
&lt;br /&gt;
Производим запуск бота через запуск исполняемого python файла:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233335238.png|400px]]&lt;br /&gt;
&lt;br /&gt;
После этого действия можем начинать работать и тестировать бота.&lt;br /&gt;
&lt;br /&gt;
Переходим в телеграм, пишем команду /start&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233503508.png|400px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, запрос прошел успешно, бот ответил то, что я зашил в код программы.&lt;br /&gt;
&lt;br /&gt;
Попробуем получить случайное фото командой /random:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233610248.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233641178.png|200px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь попробуем найти что-то конкретное и посмотреть как это будет выглядеть. Для этого сформируем команду /search. Искать будем собаку с породой самоед:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233810808.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233832396.png|200px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, бот отправил нам 4 варианта фотографий, к каждой из которой добавил ссылку на оригинал.&lt;br /&gt;
&lt;br /&gt;
Притом каждый раз в терминале (консоли) мы можем заметить, как бот поддерживает подключение с Unsplash и логирует отправку сообщений.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 234038505.png|400px]]&lt;br /&gt;
&lt;br /&gt;
=== Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;br /&gt;
* [[UML]]&lt;br /&gt;
{{#ask: [[UML]] | ?Description }}&lt;br /&gt;
* [[Telegram]]&lt;br /&gt;
{{#ask: [[Telegram]] | ?Description }}&lt;br /&gt;
* [[API]]&lt;br /&gt;
{{#ask: [[API]] | ?Description }}&lt;br /&gt;
&lt;br /&gt;
[[Участник:CherenkovIR|CherenkovIR]] ([[Обсуждение участника:CherenkovIR|обсуждение]])&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_234038505.png&amp;diff=44807</id>
		<title>Файл:Изображение 2026-03-05 234038505.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_234038505.png&amp;diff=44807"/>
		<updated>2026-03-05T20:40:40Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44806</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44806"/>
		<updated>2026-03-05T20:39:18Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с API фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
pip install -r requirements.txt&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
В соответствии с ранее описанными функциональными требованиями диаграмма [[UML]] ниже&lt;br /&gt;
&lt;br /&gt;
&amp;lt;uml&amp;gt;&lt;br /&gt;
@startuml&lt;br /&gt;
actor Пользователь as User&lt;br /&gt;
participant &amp;quot;Telegram Bot&amp;quot; as TB&lt;br /&gt;
participant &amp;quot;Обработчик&amp;quot; as H&lt;br /&gt;
participant &amp;quot;Unsplash API&amp;quot; as API&lt;br /&gt;
participant &amp;quot;Хранилище&amp;quot; as DB&lt;br /&gt;
&lt;br /&gt;
== Поиск фото ==&lt;br /&gt;
User -&amp;gt; TB: /search природа&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET photos?query=природа&lt;br /&gt;
API --&amp;gt; H: 4 фото&lt;br /&gt;
H -&amp;gt; DB: Сохранить фото&lt;br /&gt;
loop 4 фото&lt;br /&gt;
    H -&amp;gt; TB: Фото + кнопка&lt;br /&gt;
    TB --&amp;gt; User: Показать&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Выбор фото ==&lt;br /&gt;
User -&amp;gt; TB: Нажимает кнопку&lt;br /&gt;
TB -&amp;gt; H: callback_data&lt;br /&gt;
H -&amp;gt; DB: Получить фото&lt;br /&gt;
DB --&amp;gt; H: Данные фото&lt;br /&gt;
H -&amp;gt; TB: Ссылки на фото&lt;br /&gt;
TB --&amp;gt; User: Оригинал\nАвтор\nUnsplash&lt;br /&gt;
&lt;br /&gt;
== Случайное фото ==&lt;br /&gt;
User -&amp;gt; TB: /random&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET random photo&lt;br /&gt;
API --&amp;gt; H: Случайное фото&lt;br /&gt;
H -&amp;gt; TB: Фото + ссылки&lt;br /&gt;
TB --&amp;gt; User: Показать&lt;br /&gt;
&lt;br /&gt;
alt Ошибка&lt;br /&gt;
    H -&amp;gt; TB: Ошибка...&lt;br /&gt;
    TB --&amp;gt; User: Сообщение об ошибке&lt;br /&gt;
end&lt;br /&gt;
@enduml&lt;br /&gt;
&amp;lt;/uml&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Непосредственная работа с кодом приложения ====&lt;br /&gt;
&lt;br /&gt;
===== Код перед созданием класса ======&lt;br /&gt;
&lt;br /&gt;
Импорт вышеописанных библиотек и подгрузка переменных из файла .env:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import logging&lt;br /&gt;
import os&lt;br /&gt;
import uuid&lt;br /&gt;
from dotenv import load_dotenv&lt;br /&gt;
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup&lt;br /&gt;
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes, CallbackQueryHandler&lt;br /&gt;
import requests&lt;br /&gt;
&lt;br /&gt;
load_dotenv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Настройка логирования&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    format=&#039;%(asctime)s - %(name)s - %(levelname)s - %(message)s&#039;,&lt;br /&gt;
    level=logging.INFO&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(__name__)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Работа с классом =====&lt;br /&gt;
&lt;br /&gt;
Далее, так как пользователей у бота может быть несколько - крайне важно создать класс, который поможет работать нескольким пользователям одновременно в соответствие с объектно-ориентированным программированием.&lt;br /&gt;
&lt;br /&gt;
Для этого я создал класс, в котором указал основные метаданные, а также функции:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
class UnsplashTelegramBot:&lt;br /&gt;
    def __init__(self):&lt;br /&gt;
        self.unsplash_key = os.getenv(&#039;UNSPLASH_ACCESS_KEY&#039;)&lt;br /&gt;
        self.telegram_token = os.getenv(&#039;TELEGRAM_BOT_TOKEN&#039;)&lt;br /&gt;
        self.unsplash_url = &amp;quot;https://api.unsplash.com&amp;quot;&lt;br /&gt;
        self.headers = {&lt;br /&gt;
            &#039;Authorization&#039;: f&#039;Client-ID {self.unsplash_key}&#039;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        self.search_results = {}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Программирование функций класса бота ======&lt;br /&gt;
&lt;br /&gt;
* /start - основная команда, которая будет запускать бота и создавать класс в нем. После отправки этой команды пользователь должен получать информацию о боте.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /start&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        welcome_message = (&lt;br /&gt;
            &amp;quot;Привет! Я бот для поиска фотографий на Unsplash.\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Команды:\n&amp;quot;&lt;br /&gt;
            &amp;quot;/search &amp;lt;запрос&amp;gt; - поиск и выбор из 4 фотографий\n&amp;quot;&lt;br /&gt;
            &amp;quot;/random - случайное фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/popular - популярные фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/help - помощь&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(welcome_message)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /help - дополнительная команда, которая будет отчасти повторять функционал команды /start, но описывать функционал более подробно.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /help&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        help_text = (&lt;br /&gt;
            &amp;quot;? Как пользоваться ботом:\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;1. Для поиска фото напиши: /search природа\n&amp;quot;&lt;br /&gt;
            &amp;quot;   Бот покажет 4 фото на выбор\n&amp;quot;&lt;br /&gt;
            &amp;quot;2. Нажми на кнопку под фото, чтобы получить ссылку на источник\n&amp;quot;&lt;br /&gt;
            &amp;quot;3. Для случайного фото: /random\n&amp;quot;&lt;br /&gt;
            &amp;quot;4. Для популярных фото: /popular\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Также можно просто отправить текстовый запрос для поиска!&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(help_text)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /search или сообщение без команды - основа всего бота, поиск фотографий и показ 4-х фотографий.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def search_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Поиск фотографий и показ 4 вариантов&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        if context.args:&lt;br /&gt;
            query = &#039; &#039;.join(context.args)&lt;br /&gt;
        else:&lt;br /&gt;
            # Команда без арг-тов, просто сообщение&lt;br /&gt;
            if update.message and update.message.text and not update.message.text.startswith(&#039;/&#039;):&lt;br /&gt;
                query = update.message.text&lt;br /&gt;
            else:&lt;br /&gt;
                await update.message.reply_text(&amp;quot;Пожалуйста, укажи запрос. Например: /search природа&amp;quot;)&lt;br /&gt;
                return&lt;br /&gt;
        &lt;br /&gt;
        # Отправляем сообщение о поиске&lt;br /&gt;
        status_message = await update.message.reply_text(f&amp;quot;Ищу фото по запросу: {query}...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            # Получаем 4 фотографии&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/search/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;query&#039;: query, &#039;per_page&#039;: 4, &#039;page&#039;: 1}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                data = response.json()&lt;br /&gt;
                if data[&#039;results&#039;]:&lt;br /&gt;
                    # Удаляем сообщение о поиске&lt;br /&gt;
                    await status_message.delete()&lt;br /&gt;
                    &lt;br /&gt;
                    # Уникальный ID&lt;br /&gt;
                    search_id = str(uuid.uuid4())[:8]&lt;br /&gt;
                    self.search_results[search_id] = data[&#039;results&#039;]&lt;br /&gt;
                    &lt;br /&gt;
                    for i, photo in enumerate(data[&#039;results&#039;], 1):&lt;br /&gt;
                        caption = f&amp;quot;Вариант {i} из 4\n👤 Автор: {photo[&#039;user&#039;][&#039;name&#039;]}&amp;quot;&lt;br /&gt;
                        &lt;br /&gt;
                        keyboard = [&lt;br /&gt;
                            [InlineKeyboardButton(&lt;br /&gt;
                                &amp;quot;Получить ссылку на источник&amp;quot;, &lt;br /&gt;
                                callback_data=f&amp;quot;get_link_{search_id}_{i-1}&amp;quot;&lt;br /&gt;
                            )]&lt;br /&gt;
                        ]&lt;br /&gt;
                        reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                        &lt;br /&gt;
                        await update.message.reply_photo(&lt;br /&gt;
                            photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                            caption=caption,&lt;br /&gt;
                            reply_markup=reply_markup&lt;br /&gt;
                        )&lt;br /&gt;
                    &lt;br /&gt;
                    # Отправляем сообщение с инструкцией&lt;br /&gt;
                    await update.message.reply_text(&lt;br /&gt;
                        f&amp;quot;✅ Найдено {data[&#039;total&#039;]} фото. Выбери понравившийся вариант, чтобы получить ссылку на источник!&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                else:&lt;br /&gt;
                    await status_message.edit_text(&amp;quot;Ничего не найдено. Попробуй другой запрос.&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                await status_message.edit_text(&amp;quot;❌ Ошибка при поиске фото. Попробуй позже.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_message.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* /random - случайное фото из Unsplash.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Случайное фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        await self.send_random_photo(update, context)&lt;br /&gt;
    &lt;br /&gt;
    async def send_random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Отправка случайного фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # Определяем, откуда пришел запрос&lt;br /&gt;
        if update.callback_query:&lt;br /&gt;
            message = update.callback_query.message&lt;br /&gt;
            await update.callback_query.answer()&lt;br /&gt;
        else:&lt;br /&gt;
            message = update.message&lt;br /&gt;
        &lt;br /&gt;
        query = &#039; &#039;.join(context.args) if context.args and hasattr(context, &#039;args&#039;) else None&lt;br /&gt;
        &lt;br /&gt;
        status_msg = await message.reply_text(&amp;quot;🎲 Получаю случайное фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            params = {}&lt;br /&gt;
            if query:&lt;br /&gt;
                params[&#039;query&#039;] = query&lt;br /&gt;
            &lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos/random&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params=params&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photo = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                caption = (&lt;br /&gt;
                    f&amp;quot;**Случайное фото**\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;📝 **Описание:** {photo.get(&#039;description&#039;, &#039;Нет описания&#039;)}\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Создаем кнопки для действий с фото&lt;br /&gt;
                keyboard = [&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔗 Получить все ссылки&amp;quot;, callback_data=f&amp;quot;get_link_random_{photo[&#039;id&#039;]}&amp;quot;)],&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔄 Еще случайное&amp;quot;, callback_data=&amp;quot;random_photo&amp;quot;)]&lt;br /&gt;
                ]&lt;br /&gt;
                reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                &lt;br /&gt;
                await message.reply_photo(&lt;br /&gt;
                    photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                    caption=caption,&lt;br /&gt;
                    parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                    reply_markup=reply_markup&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Сохраняем фото для возможного запроса ссылок&lt;br /&gt;
                if not hasattr(self, &#039;random_photos&#039;):&lt;br /&gt;
                    self.random_photos = {}&lt;br /&gt;
                self.random_photos[photo[&#039;id&#039;]] = photo&lt;br /&gt;
                &lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при получении случайного фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /popular - случайное фото из подборки &amp;quot;Популярное&amp;quot; из Unsplash. делается наподобие команды /random.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def popular_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Популярные фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        status_msg = await update.message.reply_text(&amp;quot;🔥 Загружаю популярные фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;per_page&#039;: 5, &#039;order_by&#039;: &#039;popular&#039;}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photos = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                for i, photo in enumerate(photos, 1):&lt;br /&gt;
                    caption = (&lt;br /&gt;
                        f&amp;quot;🔥 **Популярное фото #{i}**\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;❤️ **Лайков:** {photo[&#039;likes&#039;]}\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Кнопка для получения ссылок&lt;br /&gt;
                    keyboard = [[InlineKeyboardButton(&amp;quot;🔗 Получить ссылки&amp;quot;, callback_data=f&amp;quot;get_link_popular_{photo[&#039;id&#039;]}&amp;quot;)]]&lt;br /&gt;
                    reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                    &lt;br /&gt;
                    await update.message.reply_photo(&lt;br /&gt;
                        photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                        caption=caption,&lt;br /&gt;
                        parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                        reply_markup=reply_markup&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Сохраняем фото&lt;br /&gt;
                    if not hasattr(self, &#039;popular_photos_cache&#039;):&lt;br /&gt;
                        self.popular_photos_cache = {}&lt;br /&gt;
                    self.popular_photos_cache[photo[&#039;id&#039;]] = photo&lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при загрузке популярных фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Написание обработчика на запуск бота =====&lt;br /&gt;
&lt;br /&gt;
Функция на запуск бота, включает в себя создание приложения, обработчики команд и логирование всех команд в терминале.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    def run(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Запуск бота&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        application = Application.builder().token(self.telegram_token).build()&lt;br /&gt;
        &lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;start&amp;quot;, self.start))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;help&amp;quot;, self.help_command))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;search&amp;quot;, self.search_photos))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;random&amp;quot;, self.random_photo))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;popular&amp;quot;, self.popular_photos))&lt;br /&gt;
        application.add_handler(MessageHandler(filters.TEXT &amp;amp; ~filters.COMMAND, self.handle_message))&lt;br /&gt;
        application.add_handler(CallbackQueryHandler(self.handle_callback))&lt;br /&gt;
        &lt;br /&gt;
        print(&amp;quot;/start&amp;quot;)&lt;br /&gt;
        application.run_polling(allowed_updates=Update.ALL_TYPES)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Вне класса также создаем класс и запускаем бота.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    bot = UnsplashTelegramBot()&lt;br /&gt;
    bot.run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Запуск бота и его тестирование ====&lt;br /&gt;
&lt;br /&gt;
Производим запуск бота через запуск исполняемого python файла:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233335238.png|400px]]&lt;br /&gt;
&lt;br /&gt;
После этого действия можем начинать работать и тестировать бота.&lt;br /&gt;
&lt;br /&gt;
Переходим в телеграм, пишем команду /start&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233503508.png|400px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, запрос прошел успешно, бот ответил то, что я зашил в код программы.&lt;br /&gt;
&lt;br /&gt;
Попробуем получить случайное фото командой /random:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233610248.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233641178.png|200px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь попробуем найти что-то конкретное и посмотреть как это будет выглядеть. Для этого сформируем команду /search. Искать будем собаку с породой самоед:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233810808.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233832396.png|200px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, бот отправил нам 4 варианта фотографий, к каждой из которой добавил ссылку на оригинал.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;br /&gt;
* [[UML]]&lt;br /&gt;
{{#ask: [[UML]] | ?Description }}&lt;br /&gt;
* [[Telegram]]&lt;br /&gt;
{{#ask: [[Telegram]] | ?Description }}&lt;br /&gt;
* [[API]]&lt;br /&gt;
{{#ask: [[API]] | ?Description }}&lt;br /&gt;
&lt;br /&gt;
[[Участник:CherenkovIR|CherenkovIR]] ([[Обсуждение участника:CherenkovIR|обсуждение]])&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44805</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44805"/>
		<updated>2026-03-05T20:39:08Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с API фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
pip install -r requirements.txt&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
В соответствии с ранее описанными функциональными требованиями диаграмма [[UML]] ниже&lt;br /&gt;
&lt;br /&gt;
&amp;lt;uml&amp;gt;&lt;br /&gt;
@startuml&lt;br /&gt;
actor Пользователь as User&lt;br /&gt;
participant &amp;quot;Telegram Bot&amp;quot; as TB&lt;br /&gt;
participant &amp;quot;Обработчик&amp;quot; as H&lt;br /&gt;
participant &amp;quot;Unsplash API&amp;quot; as API&lt;br /&gt;
participant &amp;quot;Хранилище&amp;quot; as DB&lt;br /&gt;
&lt;br /&gt;
== Поиск фото ==&lt;br /&gt;
User -&amp;gt; TB: /search природа&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET photos?query=природа&lt;br /&gt;
API --&amp;gt; H: 4 фото&lt;br /&gt;
H -&amp;gt; DB: Сохранить фото&lt;br /&gt;
loop 4 фото&lt;br /&gt;
    H -&amp;gt; TB: Фото + кнопка&lt;br /&gt;
    TB --&amp;gt; User: Показать&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Выбор фото ==&lt;br /&gt;
User -&amp;gt; TB: Нажимает кнопку&lt;br /&gt;
TB -&amp;gt; H: callback_data&lt;br /&gt;
H -&amp;gt; DB: Получить фото&lt;br /&gt;
DB --&amp;gt; H: Данные фото&lt;br /&gt;
H -&amp;gt; TB: Ссылки на фото&lt;br /&gt;
TB --&amp;gt; User: Оригинал\nАвтор\nUnsplash&lt;br /&gt;
&lt;br /&gt;
== Случайное фото ==&lt;br /&gt;
User -&amp;gt; TB: /random&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET random photo&lt;br /&gt;
API --&amp;gt; H: Случайное фото&lt;br /&gt;
H -&amp;gt; TB: Фото + ссылки&lt;br /&gt;
TB --&amp;gt; User: Показать&lt;br /&gt;
&lt;br /&gt;
alt Ошибка&lt;br /&gt;
    H -&amp;gt; TB: Ошибка...&lt;br /&gt;
    TB --&amp;gt; User: Сообщение об ошибке&lt;br /&gt;
end&lt;br /&gt;
@enduml&lt;br /&gt;
&amp;lt;/uml&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Непосредственная работа с кодом приложения ====&lt;br /&gt;
&lt;br /&gt;
===== Код перед созданием класса ======&lt;br /&gt;
&lt;br /&gt;
Импорт вышеописанных библиотек и подгрузка переменных из файла .env:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import logging&lt;br /&gt;
import os&lt;br /&gt;
import uuid&lt;br /&gt;
from dotenv import load_dotenv&lt;br /&gt;
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup&lt;br /&gt;
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes, CallbackQueryHandler&lt;br /&gt;
import requests&lt;br /&gt;
&lt;br /&gt;
load_dotenv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Настройка логирования&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    format=&#039;%(asctime)s - %(name)s - %(levelname)s - %(message)s&#039;,&lt;br /&gt;
    level=logging.INFO&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(__name__)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Работа с классом =====&lt;br /&gt;
&lt;br /&gt;
Далее, так как пользователей у бота может быть несколько - крайне важно создать класс, который поможет работать нескольким пользователям одновременно в соответствие с объектно-ориентированным программированием.&lt;br /&gt;
&lt;br /&gt;
Для этого я создал класс, в котором указал основные метаданные, а также функции:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
class UnsplashTelegramBot:&lt;br /&gt;
    def __init__(self):&lt;br /&gt;
        self.unsplash_key = os.getenv(&#039;UNSPLASH_ACCESS_KEY&#039;)&lt;br /&gt;
        self.telegram_token = os.getenv(&#039;TELEGRAM_BOT_TOKEN&#039;)&lt;br /&gt;
        self.unsplash_url = &amp;quot;https://api.unsplash.com&amp;quot;&lt;br /&gt;
        self.headers = {&lt;br /&gt;
            &#039;Authorization&#039;: f&#039;Client-ID {self.unsplash_key}&#039;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        self.search_results = {}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Программирование функций класса бота ======&lt;br /&gt;
&lt;br /&gt;
* /start - основная команда, которая будет запускать бота и создавать класс в нем. После отправки этой команды пользователь должен получать информацию о боте.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /start&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        welcome_message = (&lt;br /&gt;
            &amp;quot;Привет! Я бот для поиска фотографий на Unsplash.\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Команды:\n&amp;quot;&lt;br /&gt;
            &amp;quot;/search &amp;lt;запрос&amp;gt; - поиск и выбор из 4 фотографий\n&amp;quot;&lt;br /&gt;
            &amp;quot;/random - случайное фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/popular - популярные фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/help - помощь&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(welcome_message)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /help - дополнительная команда, которая будет отчасти повторять функционал команды /start, но описывать функционал более подробно.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /help&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        help_text = (&lt;br /&gt;
            &amp;quot;? Как пользоваться ботом:\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;1. Для поиска фото напиши: /search природа\n&amp;quot;&lt;br /&gt;
            &amp;quot;   Бот покажет 4 фото на выбор\n&amp;quot;&lt;br /&gt;
            &amp;quot;2. Нажми на кнопку под фото, чтобы получить ссылку на источник\n&amp;quot;&lt;br /&gt;
            &amp;quot;3. Для случайного фото: /random\n&amp;quot;&lt;br /&gt;
            &amp;quot;4. Для популярных фото: /popular\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Также можно просто отправить текстовый запрос для поиска!&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(help_text)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /search или сообщение без команды - основа всего бота, поиск фотографий и показ 4-х фотографий.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def search_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Поиск фотографий и показ 4 вариантов&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        if context.args:&lt;br /&gt;
            query = &#039; &#039;.join(context.args)&lt;br /&gt;
        else:&lt;br /&gt;
            # Команда без арг-тов, просто сообщение&lt;br /&gt;
            if update.message and update.message.text and not update.message.text.startswith(&#039;/&#039;):&lt;br /&gt;
                query = update.message.text&lt;br /&gt;
            else:&lt;br /&gt;
                await update.message.reply_text(&amp;quot;Пожалуйста, укажи запрос. Например: /search природа&amp;quot;)&lt;br /&gt;
                return&lt;br /&gt;
        &lt;br /&gt;
        # Отправляем сообщение о поиске&lt;br /&gt;
        status_message = await update.message.reply_text(f&amp;quot;Ищу фото по запросу: {query}...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            # Получаем 4 фотографии&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/search/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;query&#039;: query, &#039;per_page&#039;: 4, &#039;page&#039;: 1}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                data = response.json()&lt;br /&gt;
                if data[&#039;results&#039;]:&lt;br /&gt;
                    # Удаляем сообщение о поиске&lt;br /&gt;
                    await status_message.delete()&lt;br /&gt;
                    &lt;br /&gt;
                    # Уникальный ID&lt;br /&gt;
                    search_id = str(uuid.uuid4())[:8]&lt;br /&gt;
                    self.search_results[search_id] = data[&#039;results&#039;]&lt;br /&gt;
                    &lt;br /&gt;
                    for i, photo in enumerate(data[&#039;results&#039;], 1):&lt;br /&gt;
                        caption = f&amp;quot;Вариант {i} из 4\n👤 Автор: {photo[&#039;user&#039;][&#039;name&#039;]}&amp;quot;&lt;br /&gt;
                        &lt;br /&gt;
                        keyboard = [&lt;br /&gt;
                            [InlineKeyboardButton(&lt;br /&gt;
                                &amp;quot;Получить ссылку на источник&amp;quot;, &lt;br /&gt;
                                callback_data=f&amp;quot;get_link_{search_id}_{i-1}&amp;quot;&lt;br /&gt;
                            )]&lt;br /&gt;
                        ]&lt;br /&gt;
                        reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                        &lt;br /&gt;
                        await update.message.reply_photo(&lt;br /&gt;
                            photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                            caption=caption,&lt;br /&gt;
                            reply_markup=reply_markup&lt;br /&gt;
                        )&lt;br /&gt;
                    &lt;br /&gt;
                    # Отправляем сообщение с инструкцией&lt;br /&gt;
                    await update.message.reply_text(&lt;br /&gt;
                        f&amp;quot;✅ Найдено {data[&#039;total&#039;]} фото. Выбери понравившийся вариант, чтобы получить ссылку на источник!&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                else:&lt;br /&gt;
                    await status_message.edit_text(&amp;quot;Ничего не найдено. Попробуй другой запрос.&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                await status_message.edit_text(&amp;quot;❌ Ошибка при поиске фото. Попробуй позже.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_message.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* /random - случайное фото из Unsplash.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Случайное фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        await self.send_random_photo(update, context)&lt;br /&gt;
    &lt;br /&gt;
    async def send_random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Отправка случайного фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # Определяем, откуда пришел запрос&lt;br /&gt;
        if update.callback_query:&lt;br /&gt;
            message = update.callback_query.message&lt;br /&gt;
            await update.callback_query.answer()&lt;br /&gt;
        else:&lt;br /&gt;
            message = update.message&lt;br /&gt;
        &lt;br /&gt;
        query = &#039; &#039;.join(context.args) if context.args and hasattr(context, &#039;args&#039;) else None&lt;br /&gt;
        &lt;br /&gt;
        status_msg = await message.reply_text(&amp;quot;🎲 Получаю случайное фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            params = {}&lt;br /&gt;
            if query:&lt;br /&gt;
                params[&#039;query&#039;] = query&lt;br /&gt;
            &lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos/random&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params=params&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photo = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                caption = (&lt;br /&gt;
                    f&amp;quot;**Случайное фото**\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;📝 **Описание:** {photo.get(&#039;description&#039;, &#039;Нет описания&#039;)}\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Создаем кнопки для действий с фото&lt;br /&gt;
                keyboard = [&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔗 Получить все ссылки&amp;quot;, callback_data=f&amp;quot;get_link_random_{photo[&#039;id&#039;]}&amp;quot;)],&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔄 Еще случайное&amp;quot;, callback_data=&amp;quot;random_photo&amp;quot;)]&lt;br /&gt;
                ]&lt;br /&gt;
                reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                &lt;br /&gt;
                await message.reply_photo(&lt;br /&gt;
                    photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                    caption=caption,&lt;br /&gt;
                    parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                    reply_markup=reply_markup&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Сохраняем фото для возможного запроса ссылок&lt;br /&gt;
                if not hasattr(self, &#039;random_photos&#039;):&lt;br /&gt;
                    self.random_photos = {}&lt;br /&gt;
                self.random_photos[photo[&#039;id&#039;]] = photo&lt;br /&gt;
                &lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при получении случайного фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /popular - случайное фото из подборки &amp;quot;Популярное&amp;quot; из Unsplash. делается наподобие команды /random.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def popular_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Популярные фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        status_msg = await update.message.reply_text(&amp;quot;🔥 Загружаю популярные фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;per_page&#039;: 5, &#039;order_by&#039;: &#039;popular&#039;}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photos = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                for i, photo in enumerate(photos, 1):&lt;br /&gt;
                    caption = (&lt;br /&gt;
                        f&amp;quot;🔥 **Популярное фото #{i}**\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;❤️ **Лайков:** {photo[&#039;likes&#039;]}\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Кнопка для получения ссылок&lt;br /&gt;
                    keyboard = [[InlineKeyboardButton(&amp;quot;🔗 Получить ссылки&amp;quot;, callback_data=f&amp;quot;get_link_popular_{photo[&#039;id&#039;]}&amp;quot;)]]&lt;br /&gt;
                    reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                    &lt;br /&gt;
                    await update.message.reply_photo(&lt;br /&gt;
                        photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                        caption=caption,&lt;br /&gt;
                        parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                        reply_markup=reply_markup&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Сохраняем фото&lt;br /&gt;
                    if not hasattr(self, &#039;popular_photos_cache&#039;):&lt;br /&gt;
                        self.popular_photos_cache = {}&lt;br /&gt;
                    self.popular_photos_cache[photo[&#039;id&#039;]] = photo&lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при загрузке популярных фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Написание обработчика на запуск бота =====&lt;br /&gt;
&lt;br /&gt;
Функция на запуск бота, включает в себя создание приложения, обработчики команд и логирование всех команд в терминале.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    def run(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Запуск бота&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        application = Application.builder().token(self.telegram_token).build()&lt;br /&gt;
        &lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;start&amp;quot;, self.start))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;help&amp;quot;, self.help_command))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;search&amp;quot;, self.search_photos))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;random&amp;quot;, self.random_photo))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;popular&amp;quot;, self.popular_photos))&lt;br /&gt;
        application.add_handler(MessageHandler(filters.TEXT &amp;amp; ~filters.COMMAND, self.handle_message))&lt;br /&gt;
        application.add_handler(CallbackQueryHandler(self.handle_callback))&lt;br /&gt;
        &lt;br /&gt;
        print(&amp;quot;/start&amp;quot;)&lt;br /&gt;
        application.run_polling(allowed_updates=Update.ALL_TYPES)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Вне класса также создаем класс и запускаем бота.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    bot = UnsplashTelegramBot()&lt;br /&gt;
    bot.run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Запуск бота и его тестирование ====&lt;br /&gt;
&lt;br /&gt;
Производим запуск бота через запуск исполняемого python файла:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233335238.png|400px]]&lt;br /&gt;
&lt;br /&gt;
После этого действия можем начинать работать и тестировать бота.&lt;br /&gt;
&lt;br /&gt;
Переходим в телеграм, пишем команду /start&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233503508.png|400px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, запрос прошел успешно, бот ответил то, что я зашил в код программы.&lt;br /&gt;
&lt;br /&gt;
Попробуем получить случайное фото командой /random:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233610248.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233641178.png|200px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь попробуем найти что-то конкретное и посмотреть как это будет выглядеть. Для этого сформируем команду /search. Искать будем собаку с породой самоед:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233810808.png|200px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233832396.png|200px]]&lt;br /&gt;
&lt;br /&gt;
Как видим, бот отправил нам 4 варианта фотографий, к каждой из которой добавил ссылку на оригинал.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 📚 Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;br /&gt;
* [[UML]]&lt;br /&gt;
{{#ask: [[UML]] | ?Description }}&lt;br /&gt;
* [[Telegram]]&lt;br /&gt;
{{#ask: [[Telegram]] | ?Description }}&lt;br /&gt;
* [[API]]&lt;br /&gt;
{{#ask: [[API]] | ?Description }}&lt;br /&gt;
&lt;br /&gt;
[[Участник:CherenkovIR|CherenkovIR]] ([[Обсуждение участника:CherenkovIR|обсуждение]])&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_233832396.png&amp;diff=44804</id>
		<title>Файл:Изображение 2026-03-05 233832396.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_233832396.png&amp;diff=44804"/>
		<updated>2026-03-05T20:38:34Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_233810808.png&amp;diff=44803</id>
		<title>Файл:Изображение 2026-03-05 233810808.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_233810808.png&amp;diff=44803"/>
		<updated>2026-03-05T20:38:13Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_233641178.png&amp;diff=44802</id>
		<title>Файл:Изображение 2026-03-05 233641178.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_233641178.png&amp;diff=44802"/>
		<updated>2026-03-05T20:36:44Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_233610248.png&amp;diff=44801</id>
		<title>Файл:Изображение 2026-03-05 233610248.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_233610248.png&amp;diff=44801"/>
		<updated>2026-03-05T20:36:12Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_233503508.png&amp;diff=44800</id>
		<title>Файл:Изображение 2026-03-05 233503508.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_233503508.png&amp;diff=44800"/>
		<updated>2026-03-05T20:35:06Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44799</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44799"/>
		<updated>2026-03-05T20:33:44Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с API фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
pip install -r requirements.txt&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
В соответствии с ранее описанными функциональными требованиями диаграмма [[UML]] ниже&lt;br /&gt;
&lt;br /&gt;
&amp;lt;uml&amp;gt;&lt;br /&gt;
@startuml&lt;br /&gt;
actor Пользователь as User&lt;br /&gt;
participant &amp;quot;Telegram Bot&amp;quot; as TB&lt;br /&gt;
participant &amp;quot;Обработчик&amp;quot; as H&lt;br /&gt;
participant &amp;quot;Unsplash API&amp;quot; as API&lt;br /&gt;
participant &amp;quot;Хранилище&amp;quot; as DB&lt;br /&gt;
&lt;br /&gt;
== Поиск фото ==&lt;br /&gt;
User -&amp;gt; TB: /search природа&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET photos?query=природа&lt;br /&gt;
API --&amp;gt; H: 4 фото&lt;br /&gt;
H -&amp;gt; DB: Сохранить фото&lt;br /&gt;
loop 4 фото&lt;br /&gt;
    H -&amp;gt; TB: Фото + кнопка&lt;br /&gt;
    TB --&amp;gt; User: Показать&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Выбор фото ==&lt;br /&gt;
User -&amp;gt; TB: Нажимает кнопку&lt;br /&gt;
TB -&amp;gt; H: callback_data&lt;br /&gt;
H -&amp;gt; DB: Получить фото&lt;br /&gt;
DB --&amp;gt; H: Данные фото&lt;br /&gt;
H -&amp;gt; TB: Ссылки на фото&lt;br /&gt;
TB --&amp;gt; User: Оригинал\nАвтор\nUnsplash&lt;br /&gt;
&lt;br /&gt;
== Случайное фото ==&lt;br /&gt;
User -&amp;gt; TB: /random&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET random photo&lt;br /&gt;
API --&amp;gt; H: Случайное фото&lt;br /&gt;
H -&amp;gt; TB: Фото + ссылки&lt;br /&gt;
TB --&amp;gt; User: Показать&lt;br /&gt;
&lt;br /&gt;
alt Ошибка&lt;br /&gt;
    H -&amp;gt; TB: Ошибка...&lt;br /&gt;
    TB --&amp;gt; User: Сообщение об ошибке&lt;br /&gt;
end&lt;br /&gt;
@enduml&lt;br /&gt;
&amp;lt;/uml&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Непосредственная работа с кодом приложения ====&lt;br /&gt;
&lt;br /&gt;
===== Код перед созданием класса ======&lt;br /&gt;
&lt;br /&gt;
Импорт вышеописанных библиотек и подгрузка переменных из файла .env:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import logging&lt;br /&gt;
import os&lt;br /&gt;
import uuid&lt;br /&gt;
from dotenv import load_dotenv&lt;br /&gt;
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup&lt;br /&gt;
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes, CallbackQueryHandler&lt;br /&gt;
import requests&lt;br /&gt;
&lt;br /&gt;
load_dotenv()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Настройка логирования&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
logging.basicConfig(&lt;br /&gt;
    format=&#039;%(asctime)s - %(name)s - %(levelname)s - %(message)s&#039;,&lt;br /&gt;
    level=logging.INFO&lt;br /&gt;
)&lt;br /&gt;
logger = logging.getLogger(__name__)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Работа с классом =====&lt;br /&gt;
&lt;br /&gt;
Далее, так как пользователей у бота может быть несколько - крайне важно создать класс, который поможет работать нескольким пользователям одновременно в соответствие с объектно-ориентированным программированием.&lt;br /&gt;
&lt;br /&gt;
Для этого я создал класс, в котором указал основные метаданные, а также функции:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
class UnsplashTelegramBot:&lt;br /&gt;
    def __init__(self):&lt;br /&gt;
        self.unsplash_key = os.getenv(&#039;UNSPLASH_ACCESS_KEY&#039;)&lt;br /&gt;
        self.telegram_token = os.getenv(&#039;TELEGRAM_BOT_TOKEN&#039;)&lt;br /&gt;
        self.unsplash_url = &amp;quot;https://api.unsplash.com&amp;quot;&lt;br /&gt;
        self.headers = {&lt;br /&gt;
            &#039;Authorization&#039;: f&#039;Client-ID {self.unsplash_key}&#039;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        self.search_results = {}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Программирование функций класса бота ======&lt;br /&gt;
&lt;br /&gt;
* /start - основная команда, которая будет запускать бота и создавать класс в нем. После отправки этой команды пользователь должен получать информацию о боте.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /start&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        welcome_message = (&lt;br /&gt;
            &amp;quot;Привет! Я бот для поиска фотографий на Unsplash.\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Команды:\n&amp;quot;&lt;br /&gt;
            &amp;quot;/search &amp;lt;запрос&amp;gt; - поиск и выбор из 4 фотографий\n&amp;quot;&lt;br /&gt;
            &amp;quot;/random - случайное фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/popular - популярные фото\n&amp;quot;&lt;br /&gt;
            &amp;quot;/help - помощь&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(welcome_message)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /help - дополнительная команда, которая будет отчасти повторять функционал команды /start, но описывать функционал более подробно.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Команда /help&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        help_text = (&lt;br /&gt;
            &amp;quot;? Как пользоваться ботом:\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;1. Для поиска фото напиши: /search природа\n&amp;quot;&lt;br /&gt;
            &amp;quot;   Бот покажет 4 фото на выбор\n&amp;quot;&lt;br /&gt;
            &amp;quot;2. Нажми на кнопку под фото, чтобы получить ссылку на источник\n&amp;quot;&lt;br /&gt;
            &amp;quot;3. Для случайного фото: /random\n&amp;quot;&lt;br /&gt;
            &amp;quot;4. Для популярных фото: /popular\n\n&amp;quot;&lt;br /&gt;
            &amp;quot;Также можно просто отправить текстовый запрос для поиска!&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        await update.message.reply_text(help_text)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /search или сообщение без команды - основа всего бота, поиск фотографий и показ 4-х фотографий.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def search_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Поиск фотографий и показ 4 вариантов&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        if context.args:&lt;br /&gt;
            query = &#039; &#039;.join(context.args)&lt;br /&gt;
        else:&lt;br /&gt;
            # Команда без арг-тов, просто сообщение&lt;br /&gt;
            if update.message and update.message.text and not update.message.text.startswith(&#039;/&#039;):&lt;br /&gt;
                query = update.message.text&lt;br /&gt;
            else:&lt;br /&gt;
                await update.message.reply_text(&amp;quot;Пожалуйста, укажи запрос. Например: /search природа&amp;quot;)&lt;br /&gt;
                return&lt;br /&gt;
        &lt;br /&gt;
        # Отправляем сообщение о поиске&lt;br /&gt;
        status_message = await update.message.reply_text(f&amp;quot;Ищу фото по запросу: {query}...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            # Получаем 4 фотографии&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/search/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;query&#039;: query, &#039;per_page&#039;: 4, &#039;page&#039;: 1}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                data = response.json()&lt;br /&gt;
                if data[&#039;results&#039;]:&lt;br /&gt;
                    # Удаляем сообщение о поиске&lt;br /&gt;
                    await status_message.delete()&lt;br /&gt;
                    &lt;br /&gt;
                    # Уникальный ID&lt;br /&gt;
                    search_id = str(uuid.uuid4())[:8]&lt;br /&gt;
                    self.search_results[search_id] = data[&#039;results&#039;]&lt;br /&gt;
                    &lt;br /&gt;
                    for i, photo in enumerate(data[&#039;results&#039;], 1):&lt;br /&gt;
                        caption = f&amp;quot;Вариант {i} из 4\n👤 Автор: {photo[&#039;user&#039;][&#039;name&#039;]}&amp;quot;&lt;br /&gt;
                        &lt;br /&gt;
                        keyboard = [&lt;br /&gt;
                            [InlineKeyboardButton(&lt;br /&gt;
                                &amp;quot;Получить ссылку на источник&amp;quot;, &lt;br /&gt;
                                callback_data=f&amp;quot;get_link_{search_id}_{i-1}&amp;quot;&lt;br /&gt;
                            )]&lt;br /&gt;
                        ]&lt;br /&gt;
                        reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                        &lt;br /&gt;
                        await update.message.reply_photo(&lt;br /&gt;
                            photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                            caption=caption,&lt;br /&gt;
                            reply_markup=reply_markup&lt;br /&gt;
                        )&lt;br /&gt;
                    &lt;br /&gt;
                    # Отправляем сообщение с инструкцией&lt;br /&gt;
                    await update.message.reply_text(&lt;br /&gt;
                        f&amp;quot;✅ Найдено {data[&#039;total&#039;]} фото. Выбери понравившийся вариант, чтобы получить ссылку на источник!&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                else:&lt;br /&gt;
                    await status_message.edit_text(&amp;quot;Ничего не найдено. Попробуй другой запрос.&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                await status_message.edit_text(&amp;quot;❌ Ошибка при поиске фото. Попробуй позже.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_message.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* /random - случайное фото из Unsplash.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Случайное фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        await self.send_random_photo(update, context)&lt;br /&gt;
    &lt;br /&gt;
    async def send_random_photo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Отправка случайного фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # Определяем, откуда пришел запрос&lt;br /&gt;
        if update.callback_query:&lt;br /&gt;
            message = update.callback_query.message&lt;br /&gt;
            await update.callback_query.answer()&lt;br /&gt;
        else:&lt;br /&gt;
            message = update.message&lt;br /&gt;
        &lt;br /&gt;
        query = &#039; &#039;.join(context.args) if context.args and hasattr(context, &#039;args&#039;) else None&lt;br /&gt;
        &lt;br /&gt;
        status_msg = await message.reply_text(&amp;quot;🎲 Получаю случайное фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            params = {}&lt;br /&gt;
            if query:&lt;br /&gt;
                params[&#039;query&#039;] = query&lt;br /&gt;
            &lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos/random&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params=params&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photo = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                caption = (&lt;br /&gt;
                    f&amp;quot;**Случайное фото**\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;📝 **Описание:** {photo.get(&#039;description&#039;, &#039;Нет описания&#039;)}\n\n&amp;quot;&lt;br /&gt;
                    f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Создаем кнопки для действий с фото&lt;br /&gt;
                keyboard = [&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔗 Получить все ссылки&amp;quot;, callback_data=f&amp;quot;get_link_random_{photo[&#039;id&#039;]}&amp;quot;)],&lt;br /&gt;
                    [InlineKeyboardButton(&amp;quot;🔄 Еще случайное&amp;quot;, callback_data=&amp;quot;random_photo&amp;quot;)]&lt;br /&gt;
                ]&lt;br /&gt;
                reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                &lt;br /&gt;
                await message.reply_photo(&lt;br /&gt;
                    photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                    caption=caption,&lt;br /&gt;
                    parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                    reply_markup=reply_markup&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                # Сохраняем фото для возможного запроса ссылок&lt;br /&gt;
                if not hasattr(self, &#039;random_photos&#039;):&lt;br /&gt;
                    self.random_photos = {}&lt;br /&gt;
                self.random_photos[photo[&#039;id&#039;]] = photo&lt;br /&gt;
                &lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при получении случайного фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка. Попробуй еще раз.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /popular - случайное фото из подборки &amp;quot;Популярное&amp;quot; из Unsplash. делается наподобие команды /random.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    async def popular_photos(self, update: Update, context: ContextTypes.DEFAULT_TYPE):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Популярные фото&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        status_msg = await update.message.reply_text(&amp;quot;🔥 Загружаю популярные фото...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            response = requests.get(&lt;br /&gt;
                f&amp;quot;{self.unsplash_url}/photos&amp;quot;,&lt;br /&gt;
                headers=self.headers,&lt;br /&gt;
                params={&#039;per_page&#039;: 5, &#039;order_by&#039;: &#039;popular&#039;}&lt;br /&gt;
            )&lt;br /&gt;
            &lt;br /&gt;
            if response.status_code == 200:&lt;br /&gt;
                photos = response.json()&lt;br /&gt;
                await status_msg.delete()&lt;br /&gt;
                &lt;br /&gt;
                for i, photo in enumerate(photos, 1):&lt;br /&gt;
                    caption = (&lt;br /&gt;
                        f&amp;quot;🔥 **Популярное фото #{i}**\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;👤 **Автор:** {photo[&#039;user&#039;][&#039;name&#039;]}\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;❤️ **Лайков:** {photo[&#039;likes&#039;]}\n\n&amp;quot;&lt;br /&gt;
                        f&amp;quot;[🔗 Открыть на Unsplash]({photo[&#039;links&#039;][&#039;html&#039;]})&amp;quot;&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Кнопка для получения ссылок&lt;br /&gt;
                    keyboard = [[InlineKeyboardButton(&amp;quot;🔗 Получить ссылки&amp;quot;, callback_data=f&amp;quot;get_link_popular_{photo[&#039;id&#039;]}&amp;quot;)]]&lt;br /&gt;
                    reply_markup = InlineKeyboardMarkup(keyboard)&lt;br /&gt;
                    &lt;br /&gt;
                    await update.message.reply_photo(&lt;br /&gt;
                        photo=photo[&#039;urls&#039;][&#039;regular&#039;],&lt;br /&gt;
                        caption=caption,&lt;br /&gt;
                        parse_mode=&#039;Markdown&#039;,&lt;br /&gt;
                        reply_markup=reply_markup&lt;br /&gt;
                    )&lt;br /&gt;
                    &lt;br /&gt;
                    # Сохраняем фото&lt;br /&gt;
                    if not hasattr(self, &#039;popular_photos_cache&#039;):&lt;br /&gt;
                        self.popular_photos_cache = {}&lt;br /&gt;
                    self.popular_photos_cache[photo[&#039;id&#039;]] = photo&lt;br /&gt;
            else:&lt;br /&gt;
                await status_msg.edit_text(&amp;quot;❌ Ошибка при загрузке популярных фото.&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logger.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;br /&gt;
            await status_msg.edit_text(&amp;quot;❌ Произошла ошибка.&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Написание обработчика на запуск бота =====&lt;br /&gt;
&lt;br /&gt;
Функция на запуск бота, включает в себя создание приложения, обработчики команд и логирование всех команд в терминале.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
    def run(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;Запуск бота&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        application = Application.builder().token(self.telegram_token).build()&lt;br /&gt;
        &lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;start&amp;quot;, self.start))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;help&amp;quot;, self.help_command))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;search&amp;quot;, self.search_photos))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;random&amp;quot;, self.random_photo))&lt;br /&gt;
        application.add_handler(CommandHandler(&amp;quot;popular&amp;quot;, self.popular_photos))&lt;br /&gt;
        application.add_handler(MessageHandler(filters.TEXT &amp;amp; ~filters.COMMAND, self.handle_message))&lt;br /&gt;
        application.add_handler(CallbackQueryHandler(self.handle_callback))&lt;br /&gt;
        &lt;br /&gt;
        print(&amp;quot;/start&amp;quot;)&lt;br /&gt;
        application.run_polling(allowed_updates=Update.ALL_TYPES)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Вне класса также создаем класс и запускаем бота.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    bot = UnsplashTelegramBot()&lt;br /&gt;
    bot.run()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Запуск бота и его тестирование ====&lt;br /&gt;
&lt;br /&gt;
Производим запуск бота через запуск исполняемого python файла:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 233335238.png|400px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 📚 Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;br /&gt;
* [[UML]]&lt;br /&gt;
{{#ask: [[UML]] | ?Description }}&lt;br /&gt;
* [[Telegram]]&lt;br /&gt;
{{#ask: [[Telegram]] | ?Description }}&lt;br /&gt;
* [[API]]&lt;br /&gt;
{{#ask: [[API]] | ?Description }}&lt;br /&gt;
&lt;br /&gt;
[[Участник:CherenkovIR|CherenkovIR]] ([[Обсуждение участника:CherenkovIR|обсуждение]])&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_233335238.png&amp;diff=44798</id>
		<title>Файл:Изображение 2026-03-05 233335238.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_233335238.png&amp;diff=44798"/>
		<updated>2026-03-05T20:33:38Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:CherenkovIR&amp;diff=44797</id>
		<title>Обсуждение участника:CherenkovIR</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:CherenkovIR&amp;diff=44797"/>
		<updated>2026-03-05T19:41:42Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: /* А зачем изображения листинга? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Пример [[Mermaid]] ==&lt;br /&gt;
&lt;br /&gt;
{{#mermaid:sequenceDiagram&lt;br /&gt;
actor Student #red&lt;br /&gt;
    autonumber&lt;br /&gt;
    Student-&amp;gt;&amp;gt;Admin: Can I enrol this semester?&lt;br /&gt;
    loop enrolmentCheck&lt;br /&gt;
        Admin-&amp;gt;&amp;gt;Admin: Check previous results&lt;br /&gt;
    end&lt;br /&gt;
    Note right of Admin: Exam results may &amp;lt;br&amp;gt; be delayed&lt;br /&gt;
    Admin--&amp;gt;&amp;gt;Student: Enrolment success&lt;br /&gt;
    Admin-&amp;gt;&amp;gt;Professor: Assign student to tutor&lt;br /&gt;
    Professor--&amp;gt;&amp;gt;Admin: Student is assigned&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== возможности графвиз ==&lt;br /&gt;
&lt;br /&gt;
[[MedicalOntologyDiagram]]&lt;br /&gt;
&lt;br /&gt;
{{#ask: [[MedicalOntologyDiagram]] | format = embedded }}}}&lt;br /&gt;
&lt;br /&gt;
== А зачем изображения листинга? ==&lt;br /&gt;
&lt;br /&gt;
Пишите просто текст программы через &amp;lt;nowiki&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
python-telegram-bot==20.7&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Спасибо, не знал про этот тег)&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44788</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44788"/>
		<updated>2026-03-05T17:32:11Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: /* 📚 Методические материалы */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с API фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
В соответствии с ранее описанными функциональными требованиями диаграмма [[UML]] ниже&lt;br /&gt;
&lt;br /&gt;
&amp;lt;uml&amp;gt;&lt;br /&gt;
@startuml&lt;br /&gt;
actor Пользователь as User&lt;br /&gt;
participant &amp;quot;Telegram Bot&amp;quot; as TB&lt;br /&gt;
participant &amp;quot;Обработчик&amp;quot; as H&lt;br /&gt;
participant &amp;quot;Unsplash API&amp;quot; as API&lt;br /&gt;
participant &amp;quot;Хранилище&amp;quot; as DB&lt;br /&gt;
&lt;br /&gt;
== Поиск фото ==&lt;br /&gt;
User -&amp;gt; TB: /search природа&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET photos?query=природа&lt;br /&gt;
API --&amp;gt; H: 4 фото&lt;br /&gt;
H -&amp;gt; DB: Сохранить фото&lt;br /&gt;
loop 4 фото&lt;br /&gt;
    H -&amp;gt; TB: Фото + кнопка&lt;br /&gt;
    TB --&amp;gt; User: Показать&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Выбор фото ==&lt;br /&gt;
User -&amp;gt; TB: Нажимает кнопку&lt;br /&gt;
TB -&amp;gt; H: callback_data&lt;br /&gt;
H -&amp;gt; DB: Получить фото&lt;br /&gt;
DB --&amp;gt; H: Данные фото&lt;br /&gt;
H -&amp;gt; TB: Ссылки на фото&lt;br /&gt;
TB --&amp;gt; User: Оригинал\nАвтор\nUnsplash&lt;br /&gt;
&lt;br /&gt;
== Случайное фото ==&lt;br /&gt;
User -&amp;gt; TB: /random&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET random photo&lt;br /&gt;
API --&amp;gt; H: Случайное фото&lt;br /&gt;
H -&amp;gt; TB: Фото + ссылки&lt;br /&gt;
TB --&amp;gt; User: Показать&lt;br /&gt;
&lt;br /&gt;
alt Ошибка&lt;br /&gt;
    H -&amp;gt; TB: Ошибка...&lt;br /&gt;
    TB --&amp;gt; User: Сообщение об ошибке&lt;br /&gt;
end&lt;br /&gt;
@enduml&lt;br /&gt;
&amp;lt;/uml&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Непосредственная работа с кодом приложения ====&lt;br /&gt;
&lt;br /&gt;
===== Код перед созданием класса ======&lt;br /&gt;
&lt;br /&gt;
Импорт вышеописанных библиотек и подгрузка переменных из файла .env:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 201047804.png|400px]]&lt;br /&gt;
&lt;br /&gt;
Настройка логирования&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 201145853.png|400px]]&lt;br /&gt;
&lt;br /&gt;
===== Работа с классом =====&lt;br /&gt;
&lt;br /&gt;
Далее, так как пользователей у бота может быть несколько - крайне важно создать класс, который поможет работать нескольким пользователям одновременно в соответствие с объектно-ориентированным программированием.&lt;br /&gt;
&lt;br /&gt;
Для этого я создал класс, в котором указал основные метаданные, а также функции:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 201355452.png|500px]]&lt;br /&gt;
&lt;br /&gt;
====== Программирование функций класса бота ======&lt;br /&gt;
&lt;br /&gt;
* /start - основная команда, которая будет запускать бота и создавать класс в нем. После отправки этой команды пользователь должен получать информацию о боте.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 201713890.png|500px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /help - дополнительная команда, которая будет отчасти повторять функционал команды /start, но описывать функционал более подробно.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 201937556.png|500px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /search или сообщение без команды - основа всего бота, поиск фотографий и показ 4-х фотографий.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 202422793.png|700px]]&lt;br /&gt;
&lt;br /&gt;
* /random - случайное фото из Unsplash.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 202638446.png|700px]]&lt;br /&gt;
&lt;br /&gt;
* /popular - случайное фото из подборки &amp;quot;Популярное&amp;quot; из Unsplash. делается наподобие команды /random.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===== Написание обработчика на запуск бота =====&lt;br /&gt;
&lt;br /&gt;
Функция на запуск бота, включает в себя создание приложения, обработчики команд и логгирование всех команд в терминале.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 202911267.png|500px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Вне класса также создаем класс и запускаем бота.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 203020179.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Запуск бота и его тестирование ====&lt;br /&gt;
&lt;br /&gt;
=== 📚 Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;br /&gt;
* [[UML]]&lt;br /&gt;
{{#ask: [[UML]] | ?Description }}&lt;br /&gt;
* [[Telegram]]&lt;br /&gt;
{{#ask: [[Telegram]] | ?Description }}&lt;br /&gt;
* [[API]]&lt;br /&gt;
{{#ask: [[API]] | ?Description }}&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44787</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44787"/>
		<updated>2026-03-05T17:31:14Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с API фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
В соответствии с ранее описанными функциональными требованиями диаграмма [[UML]] ниже&lt;br /&gt;
&lt;br /&gt;
&amp;lt;uml&amp;gt;&lt;br /&gt;
@startuml&lt;br /&gt;
actor Пользователь as User&lt;br /&gt;
participant &amp;quot;Telegram Bot&amp;quot; as TB&lt;br /&gt;
participant &amp;quot;Обработчик&amp;quot; as H&lt;br /&gt;
participant &amp;quot;Unsplash API&amp;quot; as API&lt;br /&gt;
participant &amp;quot;Хранилище&amp;quot; as DB&lt;br /&gt;
&lt;br /&gt;
== Поиск фото ==&lt;br /&gt;
User -&amp;gt; TB: /search природа&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET photos?query=природа&lt;br /&gt;
API --&amp;gt; H: 4 фото&lt;br /&gt;
H -&amp;gt; DB: Сохранить фото&lt;br /&gt;
loop 4 фото&lt;br /&gt;
    H -&amp;gt; TB: Фото + кнопка&lt;br /&gt;
    TB --&amp;gt; User: Показать&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Выбор фото ==&lt;br /&gt;
User -&amp;gt; TB: Нажимает кнопку&lt;br /&gt;
TB -&amp;gt; H: callback_data&lt;br /&gt;
H -&amp;gt; DB: Получить фото&lt;br /&gt;
DB --&amp;gt; H: Данные фото&lt;br /&gt;
H -&amp;gt; TB: Ссылки на фото&lt;br /&gt;
TB --&amp;gt; User: Оригинал\nАвтор\nUnsplash&lt;br /&gt;
&lt;br /&gt;
== Случайное фото ==&lt;br /&gt;
User -&amp;gt; TB: /random&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET random photo&lt;br /&gt;
API --&amp;gt; H: Случайное фото&lt;br /&gt;
H -&amp;gt; TB: Фото + ссылки&lt;br /&gt;
TB --&amp;gt; User: Показать&lt;br /&gt;
&lt;br /&gt;
alt Ошибка&lt;br /&gt;
    H -&amp;gt; TB: Ошибка...&lt;br /&gt;
    TB --&amp;gt; User: Сообщение об ошибке&lt;br /&gt;
end&lt;br /&gt;
@enduml&lt;br /&gt;
&amp;lt;/uml&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Непосредственная работа с кодом приложения ====&lt;br /&gt;
&lt;br /&gt;
===== Код перед созданием класса ======&lt;br /&gt;
&lt;br /&gt;
Импорт вышеописанных библиотек и подгрузка переменных из файла .env:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 201047804.png|400px]]&lt;br /&gt;
&lt;br /&gt;
Настройка логирования&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 201145853.png|400px]]&lt;br /&gt;
&lt;br /&gt;
===== Работа с классом =====&lt;br /&gt;
&lt;br /&gt;
Далее, так как пользователей у бота может быть несколько - крайне важно создать класс, который поможет работать нескольким пользователям одновременно в соответствие с объектно-ориентированным программированием.&lt;br /&gt;
&lt;br /&gt;
Для этого я создал класс, в котором указал основные метаданные, а также функции:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 201355452.png|500px]]&lt;br /&gt;
&lt;br /&gt;
====== Программирование функций класса бота ======&lt;br /&gt;
&lt;br /&gt;
* /start - основная команда, которая будет запускать бота и создавать класс в нем. После отправки этой команды пользователь должен получать информацию о боте.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 201713890.png|500px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /help - дополнительная команда, которая будет отчасти повторять функционал команды /start, но описывать функционал более подробно.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 201937556.png|500px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* /search или сообщение без команды - основа всего бота, поиск фотографий и показ 4-х фотографий.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 202422793.png|700px]]&lt;br /&gt;
&lt;br /&gt;
* /random - случайное фото из Unsplash.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 202638446.png|700px]]&lt;br /&gt;
&lt;br /&gt;
* /popular - случайное фото из подборки &amp;quot;Популярное&amp;quot; из Unsplash. делается наподобие команды /random.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===== Написание обработчика на запуск бота =====&lt;br /&gt;
&lt;br /&gt;
Функция на запуск бота, включает в себя создание приложения, обработчики команд и логгирование всех команд в терминале.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 202911267.png|500px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Вне класса также создаем класс и запускаем бота.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 203020179.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Запуск бота и его тестирование ====&lt;br /&gt;
&lt;br /&gt;
=== 📚 Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_203020179.png&amp;diff=44786</id>
		<title>Файл:Изображение 2026-03-05 203020179.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_203020179.png&amp;diff=44786"/>
		<updated>2026-03-05T17:30:22Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_202911267.png&amp;diff=44785</id>
		<title>Файл:Изображение 2026-03-05 202911267.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_202911267.png&amp;diff=44785"/>
		<updated>2026-03-05T17:29:13Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_202638446.png&amp;diff=44783</id>
		<title>Файл:Изображение 2026-03-05 202638446.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_202638446.png&amp;diff=44783"/>
		<updated>2026-03-05T17:26:47Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_202422793.png&amp;diff=44782</id>
		<title>Файл:Изображение 2026-03-05 202422793.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_202422793.png&amp;diff=44782"/>
		<updated>2026-03-05T17:24:25Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;2&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_201937556.png&amp;diff=44781</id>
		<title>Файл:Изображение 2026-03-05 201937556.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_201937556.png&amp;diff=44781"/>
		<updated>2026-03-05T17:19:40Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44780</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44780"/>
		<updated>2026-03-05T17:17:22Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с API фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
В соответствии с ранее описанными функциональными требованиями диаграмма [[UML]] ниже&lt;br /&gt;
&lt;br /&gt;
&amp;lt;uml&amp;gt;&lt;br /&gt;
@startuml&lt;br /&gt;
actor Пользователь as User&lt;br /&gt;
participant &amp;quot;Telegram Bot&amp;quot; as TB&lt;br /&gt;
participant &amp;quot;Обработчик&amp;quot; as H&lt;br /&gt;
participant &amp;quot;Unsplash API&amp;quot; as API&lt;br /&gt;
participant &amp;quot;Хранилище&amp;quot; as DB&lt;br /&gt;
&lt;br /&gt;
== Поиск фото ==&lt;br /&gt;
User -&amp;gt; TB: /search природа&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET photos?query=природа&lt;br /&gt;
API --&amp;gt; H: 4 фото&lt;br /&gt;
H -&amp;gt; DB: Сохранить фото&lt;br /&gt;
loop 4 фото&lt;br /&gt;
    H -&amp;gt; TB: Фото + кнопка&lt;br /&gt;
    TB --&amp;gt; User: Показать&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Выбор фото ==&lt;br /&gt;
User -&amp;gt; TB: Нажимает кнопку&lt;br /&gt;
TB -&amp;gt; H: callback_data&lt;br /&gt;
H -&amp;gt; DB: Получить фото&lt;br /&gt;
DB --&amp;gt; H: Данные фото&lt;br /&gt;
H -&amp;gt; TB: Ссылки на фото&lt;br /&gt;
TB --&amp;gt; User: Оригинал\nАвтор\nUnsplash&lt;br /&gt;
&lt;br /&gt;
== Случайное фото ==&lt;br /&gt;
User -&amp;gt; TB: /random&lt;br /&gt;
TB -&amp;gt; H: Команда&lt;br /&gt;
H -&amp;gt; API: GET random photo&lt;br /&gt;
API --&amp;gt; H: Случайное фото&lt;br /&gt;
H -&amp;gt; TB: Фото + ссылки&lt;br /&gt;
TB --&amp;gt; User: Показать&lt;br /&gt;
&lt;br /&gt;
alt Ошибка&lt;br /&gt;
    H -&amp;gt; TB: Ошибка...&lt;br /&gt;
    TB --&amp;gt; User: Сообщение об ошибке&lt;br /&gt;
end&lt;br /&gt;
@enduml&lt;br /&gt;
&amp;lt;/uml&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Непосредственная работа с кодом приложения ====&lt;br /&gt;
&lt;br /&gt;
Импорт вышеописанных библиотек и подгрузка переменных из файла .env:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 201047804.png|400px]]&lt;br /&gt;
&lt;br /&gt;
Настройка логирования&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 201145853.png|400px]]&lt;br /&gt;
&lt;br /&gt;
===== Работа с классом =====&lt;br /&gt;
&lt;br /&gt;
Далее, так как пользователей у бота может быть несколько - крайне важно создать класс, который поможет работать нескольким пользователям одновременно в соответствие с объектно-ориентированным программированием.&lt;br /&gt;
&lt;br /&gt;
Для этого я создал класс, в котором указал основные метаданные, а также функции:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 201355452.png|300px]]&lt;br /&gt;
&lt;br /&gt;
====== Программирование функций класса бота ======&lt;br /&gt;
&lt;br /&gt;
======= Команда &amp;quot;/start&amp;quot; =======&lt;br /&gt;
/start - основная команда, которая будет запускать бота и создавать класс в нем. После отправки этой команды пользователь должен получать информацию о боте.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 201713890.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 📚 Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_201713890.png&amp;diff=44779</id>
		<title>Файл:Изображение 2026-03-05 201713890.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_201713890.png&amp;diff=44779"/>
		<updated>2026-03-05T17:17:18Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_201355452.png&amp;diff=44778</id>
		<title>Файл:Изображение 2026-03-05 201355452.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_201355452.png&amp;diff=44778"/>
		<updated>2026-03-05T17:14:18Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_201337716.png&amp;diff=44777</id>
		<title>Файл:Изображение 2026-03-05 201337716.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_201337716.png&amp;diff=44777"/>
		<updated>2026-03-05T17:13:40Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_201145853.png&amp;diff=44776</id>
		<title>Файл:Изображение 2026-03-05 201145853.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_201145853.png&amp;diff=44776"/>
		<updated>2026-03-05T17:11:49Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_201047804.png&amp;diff=44774</id>
		<title>Файл:Изображение 2026-03-05 201047804.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_201047804.png&amp;diff=44774"/>
		<updated>2026-03-05T17:10:50Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44771</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44771"/>
		<updated>2026-03-05T16:56:08Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с API фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;uml&amp;gt;&lt;br /&gt;
@startuml&lt;br /&gt;
class UnsplashTelegramBot {&lt;br /&gt;
    - unsplash_key: str&lt;br /&gt;
    - telegram_token: str&lt;br /&gt;
    - unsplash_url: str&lt;br /&gt;
    - headers: dict&lt;br /&gt;
    - search_results: dict&lt;br /&gt;
    - random_photos: dict&lt;br /&gt;
    - popular_photos_cache: dict&lt;br /&gt;
    + __init__()&lt;br /&gt;
    + start()&lt;br /&gt;
    + help_command()&lt;br /&gt;
    + search_photos()&lt;br /&gt;
    + handle_callback()&lt;br /&gt;
    + random_photo()&lt;br /&gt;
    + send_random_photo()&lt;br /&gt;
    + popular_photos()&lt;br /&gt;
    + handle_message()&lt;br /&gt;
    + run()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class &amp;quot;python-telegram-bot&amp;quot; {&lt;br /&gt;
    + Application&lt;br /&gt;
    + Update&lt;br /&gt;
    + CommandHandler&lt;br /&gt;
    + MessageHandler&lt;br /&gt;
    + CallbackQueryHandler&lt;br /&gt;
    + ContextTypes&lt;br /&gt;
    + filters&lt;br /&gt;
    + InlineKeyboardButton&lt;br /&gt;
    + InlineKeyboardMarkup&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class requests {&lt;br /&gt;
    + get()&lt;br /&gt;
    + post()&lt;br /&gt;
    + Response&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class logging {&lt;br /&gt;
    + basicConfig()&lt;br /&gt;
    + getLogger()&lt;br /&gt;
    + info()&lt;br /&gt;
    + error()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class os {&lt;br /&gt;
    + getenv()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class uuid {&lt;br /&gt;
    + uuid4()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class dotenv {&lt;br /&gt;
    + load_dotenv()&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class &amp;quot;Unsplash API&amp;quot; {&lt;br /&gt;
    + /search/photos&lt;br /&gt;
    + /photos/random&lt;br /&gt;
    + /photos&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
UnsplashTelegramBot --&amp;gt; &amp;quot;использует&amp;quot; python-telegram-bot&lt;br /&gt;
UnsplashTelegramBot --&amp;gt; &amp;quot;отправляет запросы&amp;quot; requests&lt;br /&gt;
UnsplashTelegramBot --&amp;gt; &amp;quot;логирует&amp;quot; logging&lt;br /&gt;
UnsplashTelegramBot --&amp;gt; &amp;quot;читает переменные&amp;quot; os&lt;br /&gt;
UnsplashTelegramBot --&amp;gt; &amp;quot;генерирует ID&amp;quot; uuid&lt;br /&gt;
UnsplashTelegramBot --&amp;gt; &amp;quot;загружает .env&amp;quot; dotenv&lt;br /&gt;
requests --&amp;gt; &amp;quot;обращается к&amp;quot; UnsplashAPI&lt;br /&gt;
&lt;br /&gt;
note top of UnsplashTelegramBot : Основной класс бота\nинтегрирует все библиотеки&lt;br /&gt;
&lt;br /&gt;
@enduml&lt;br /&gt;
&amp;lt;/uml&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 📚 Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44770</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44770"/>
		<updated>2026-03-05T16:55:36Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с API фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;uml&amp;gt;&lt;br /&gt;
@startuml&lt;br /&gt;
&#039; Описание взаимодействия компонентов бота с Unsplash API&lt;br /&gt;
&lt;br /&gt;
actor Пользователь as User&lt;br /&gt;
participant &amp;quot;Telegram Bot\n(Библиотека python-telegram-bot)&amp;quot; as TelegramBot&lt;br /&gt;
participant &amp;quot;Обработчик команд\n(CommandHandler)&amp;quot; as Handler&lt;br /&gt;
participant &amp;quot;CallbackQueryHandler\n(Обработчик кнопок)&amp;quot; as CallbackHandler&lt;br /&gt;
participant &amp;quot;Логирование\n(logging)&amp;quot; as Logger&lt;br /&gt;
participant &amp;quot;Хранилище\n(Словарь Python)&amp;quot; as Storage&lt;br /&gt;
participant &amp;quot;HTTP клиент\n(requests)&amp;quot; as HTTPClient&lt;br /&gt;
participant &amp;quot;API Unsplash&amp;quot; as UnsplashAPI&lt;br /&gt;
participant &amp;quot;Генератор UUID\n(uuid)&amp;quot; as UUIDGen&lt;br /&gt;
&lt;br /&gt;
== Регистрация и инициализация ==&lt;br /&gt;
note over TelegramBot: Загрузка токенов из .env\npython-dotenv + os&lt;br /&gt;
&lt;br /&gt;
== Поиск фотографий (/search природа) ==&lt;br /&gt;
User -&amp;gt; TelegramBot: Отправляет команду /search природа&lt;br /&gt;
TelegramBot -&amp;gt; Handler: Передает Update с командой&lt;br /&gt;
Handler -&amp;gt; Logger: log_info(&amp;quot;Поиск фото: природа&amp;quot;)&lt;br /&gt;
Handler -&amp;gt; UUIDGen: generate_uuid()&lt;br /&gt;
UUIDGen --&amp;gt; Handler: search_id = &amp;quot;abc123&amp;quot;&lt;br /&gt;
Handler -&amp;gt; Storage: save_search(search_id, empty_list)&lt;br /&gt;
Handler -&amp;gt; HTTPClient: GET /search/photos?query=природа&amp;amp;per_page=4&lt;br /&gt;
HTTPClient -&amp;gt; UnsplashAPI: HTTP Request с API ключом&lt;br /&gt;
UnsplashAPI --&amp;gt; HTTPClient: JSON с 4 фото&lt;br /&gt;
HTTPClient --&amp;gt; Handler: Парсинг ответа&lt;br /&gt;
Handler -&amp;gt; Storage: update_search(search_id, photos_list)&lt;br /&gt;
loop Для каждого из 4 фото&lt;br /&gt;
    Handler -&amp;gt; TelegramBot: send_photo() + InlineKeyboardButton&lt;br /&gt;
    TelegramBot --&amp;gt; User: Показывает фото с кнопкой&lt;br /&gt;
end&lt;br /&gt;
Handler -&amp;gt; TelegramBot: send_message(&amp;quot;Выберите фото&amp;quot;)&lt;br /&gt;
TelegramBot --&amp;gt; User: Сообщение с инструкцией&lt;br /&gt;
&lt;br /&gt;
== Выбор фото пользователем ==&lt;br /&gt;
User -&amp;gt; TelegramBot: Нажимает кнопку &amp;quot;Получить ссылку&amp;quot;&lt;br /&gt;
TelegramBot -&amp;gt; CallbackHandler: Передает callback_data&lt;br /&gt;
CallbackHandler -&amp;gt; Logger: log_info(&amp;quot;Запрос ссылки&amp;quot;)&lt;br /&gt;
CallbackHandler -&amp;gt; Storage: get_photo(search_id, photo_index)&lt;br /&gt;
Storage --&amp;gt; CallbackHandler: photo_data&lt;br /&gt;
&lt;br /&gt;
note over CallbackHandler: Формирование сообщения\nсо всеми ссылками&lt;br /&gt;
&lt;br /&gt;
CallbackHandler -&amp;gt; TelegramBot: edit_message_caption() с Markdown&lt;br /&gt;
TelegramBot --&amp;gt; User: Показывает все ссылки&lt;br /&gt;
&lt;br /&gt;
CallbackHandler -&amp;gt; TelegramBot: send_message() с кнопкой &amp;quot;Новый поиск&amp;quot;&lt;br /&gt;
TelegramBot --&amp;gt; User: Предложение нового поиска&lt;br /&gt;
&lt;br /&gt;
== Обработка ошибок ==&lt;br /&gt;
alt Ошибка API или сети&lt;br /&gt;
    HTTPClient -&amp;gt; Handler: Исключение/ошибка&lt;br /&gt;
    Handler -&amp;gt; Logger: log_error(&amp;quot;Ошибка API&amp;quot;)&lt;br /&gt;
    Handler -&amp;gt; TelegramBot: send_message(&amp;quot;❌ Ошибка...&amp;quot;)&lt;br /&gt;
    TelegramBot --&amp;gt; User: Сообщение об ошибке&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
== Случайное фото (/random) ==&lt;br /&gt;
User -&amp;gt; TelegramBot: Отправляет /random&lt;br /&gt;
TelegramBot -&amp;gt; Handler: Передает команду&lt;br /&gt;
Handler -&amp;gt; HTTPClient: GET /photos/random&lt;br /&gt;
HTTPClient -&amp;gt; UnsplashAPI: Запрос&lt;br /&gt;
UnsplashAPI --&amp;gt; HTTPClient: Случайное фото&lt;br /&gt;
Handler -&amp;gt; TelegramBot: send_photo() + кнопки&lt;br /&gt;
TelegramBot --&amp;gt; User: Показывает фото&lt;br /&gt;
&lt;br /&gt;
== Получение ссылок на случайное фото ==&lt;br /&gt;
User -&amp;gt; TelegramBot: Нажимает кнопку под случайным фото&lt;br /&gt;
TelegramBot -&amp;gt; CallbackHandler: callback_data=&amp;quot;get_link_random_...&amp;quot;&lt;br /&gt;
CallbackHandler -&amp;gt; Storage: get_random_photo(photo_id)&lt;br /&gt;
Storage --&amp;gt; CallbackHandler: photo_data&lt;br /&gt;
CallbackHandler -&amp;gt; TelegramBot: edit_message_caption() с ссылками&lt;br /&gt;
TelegramBot --&amp;gt; User: Обновленное сообщение&lt;br /&gt;
&lt;br /&gt;
== Завершение работы ==&lt;br /&gt;
note over TelegramBot: application.run_polling()\nпостоянное ожидание команд&lt;br /&gt;
&lt;br /&gt;
@enduml&lt;br /&gt;
&amp;lt;/uml&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 📚 Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44769</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44769"/>
		<updated>2026-03-05T16:54:32Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с API фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
==== Начало работы и создание заглушки ====&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
==== Начало разработки кода ====&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 📚 Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44768</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44768"/>
		<updated>2026-03-05T16:52:48Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с API фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Начало работы и создание заглушки ===&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их [https://unsplash.com/documentation сайт], зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 📚 Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44767</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44767"/>
		<updated>2026-03-05T16:51:37Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с API фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Начало работы и создание заглушки ===&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193728808.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения доступа к API бота - его нужно будет в дальнейшем сохранить в файл .env в той же папке, где и сам файл Python, но об этом чуть позже.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 193843194.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Теперь нужно получить API ключ фотостока, я выбрал [[Unsplash]], так как получение API там не затруднено лишними тарифными планами и дополнительными регистрациями. &lt;br /&gt;
&lt;br /&gt;
Чтобы получить доступ к API [[Unsplash]] необходимо зайти на их сайт https://unsplash.com/documentation, зарегистрироваться и в личном кабинете открыть новое приложение (проект), указать его вид и название. Важно, что Access и Secret ключи должны быть конфиденциальны и не могут передаваться третьим лицам, поэтому в этом проекте и статье, ключи будут скрыты.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194604126.png|300px]]&lt;br /&gt;
&lt;br /&gt;
После получения всех требуемых API ключей - сохраняем их в файл окружения .env в переменные, которые в дальнейшем будем использовать в Python-коде.&lt;br /&gt;
&lt;br /&gt;
[[Файл:Изображение 2026-03-05 194020044.png|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Разработка бота ===&lt;br /&gt;
&lt;br /&gt;
Так как разработка в [[Python]] позволяет работать с разными существующими библиотеками, имеющие свои документации, будем пользоваться предоставленными правами.&lt;br /&gt;
&lt;br /&gt;
Для работы бота я подключил несколько библиотек, такие как:&lt;br /&gt;
&lt;br /&gt;
* python-telegram-bot - основная библиотека для создания Telegram ботов&lt;br /&gt;
* requests - библиотека для отправки HTTP-запросов к API Unsplash&lt;br /&gt;
* python-dotenv - загрузка переменных окружения из файла .env&lt;br /&gt;
* logging - логирование событий и ошибок для отладки&lt;br /&gt;
* os - взаимодействие с операционной системой&lt;br /&gt;
* uuid - генерация уникальных идентификаторов&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 📚 Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_194604126.png&amp;diff=44766</id>
		<title>Файл:Изображение 2026-03-05 194604126.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_194604126.png&amp;diff=44766"/>
		<updated>2026-03-05T16:46:13Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_194020044.png&amp;diff=44765</id>
		<title>Файл:Изображение 2026-03-05 194020044.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_194020044.png&amp;diff=44765"/>
		<updated>2026-03-05T16:40:22Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_193843194.png&amp;diff=44764</id>
		<title>Файл:Изображение 2026-03-05 193843194.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_193843194.png&amp;diff=44764"/>
		<updated>2026-03-05T16:38:48Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_193728808.png&amp;diff=44763</id>
		<title>Файл:Изображение 2026-03-05 193728808.png</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:%D0%98%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5_2026-03-05_193728808.png&amp;diff=44763"/>
		<updated>2026-03-05T16:37:32Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44762</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44762"/>
		<updated>2026-03-05T16:32:44Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с API фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Начало работы и создание заглушки ===&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы получить ключ для бота [[Telegram]], необходимо его создать. Сделать это можно через бота @BotFather.&lt;br /&gt;
&lt;br /&gt;
При заходе в бота, необходимо открыть внутреннее интегрированное приложение через кнопку &amp;quot;Open&amp;quot; и создать своего бота:&lt;br /&gt;
&lt;br /&gt;
[[Файл:Vnutrprilozh.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 📚 Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Vnutrprilozh.jpg&amp;diff=44761</id>
		<title>Файл:Vnutrprilozh.jpg</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Vnutrprilozh.jpg&amp;diff=44761"/>
		<updated>2026-03-05T16:32:27Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44760</id>
		<title>Телеграм-бот для поиска фотографий через API-ключ фотостока</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A2%D0%B5%D0%BB%D0%B5%D0%B3%D1%80%D0%B0%D0%BC-%D0%B1%D0%BE%D1%82_%D0%B4%D0%BB%D1%8F_%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0_%D1%84%D0%BE%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D0%B9_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_API-%D0%BA%D0%BB%D1%8E%D1%87_%D1%84%D0%BE%D1%82%D0%BE%D1%81%D1%82%D0%BE%D0%BA%D0%B0&amp;diff=44760"/>
		<updated>2026-03-05T16:29:38Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Категория:Работа с API]]&lt;br /&gt;
&lt;br /&gt;
=== Определение функциональных требований к боту ===&lt;br /&gt;
&lt;br /&gt;
Перед работой над созданием бота, я решил создать реестр требований, чтобы точно отслеживать какие моменты я хочу увидеть в работе бота.&lt;br /&gt;
&lt;br /&gt;
В моем представлении, бот, работающий с API фотостока, должен уметь работать с медиа-файлами. Помимо всего прочего, бот - есть бот, он должен уметь первоначально взаимодействовать с пользователем и отзываться на его команды. Соответственно, необходимо эти команды придумать и понять, что пользователь захочет сделать.&lt;br /&gt;
&lt;br /&gt;
Размышляя таким образом, я пришел к следующему реестру функциональных и нефункциональных требований:&lt;br /&gt;
&lt;br /&gt;
* Бот должен уметь начинать работу с пользователем через команду /start&lt;br /&gt;
* Бот должен уметь рассказывать о себе и о том зачем он нужен&lt;br /&gt;
* В случае, если пользователь забыл команды - напомнить пользователю о них через команду /help&lt;br /&gt;
* Иметь возможность работать как с открытыми запросами (на случай, если нужна случайная фотография), так и с закрытыми (при конкретных запросах)&lt;br /&gt;
* Выдавать ссылку на источник с фотографией, при выборе пользователем нужной фотографии (из 4-х)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Начало работы и создание заглушки ===&lt;br /&gt;
&lt;br /&gt;
Для начала был создан файлик с начальными зависимостями в [[Python]]&lt;br /&gt;
&lt;br /&gt;
&amp;quot;requirements.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Файл:Requirements.jpg|300px]]&lt;br /&gt;
&lt;br /&gt;
После создания этого файлика необходимо запустить команду `pip install -r [путь к файлу]` и указать путь к созданному requirements.txt&lt;br /&gt;
&lt;br /&gt;
[[Файл:Pipinstall-r.jpg|500px]]&lt;br /&gt;
&lt;br /&gt;
=== Получение API и создание внутреннего конфигурационного API файла ===&lt;br /&gt;
Далее также важный момент, для работы бота потребуется &#039;&#039;&#039;ДВА API ключа&#039;&#039;&#039;. Первый - Telegram Bot Key, второй - как раз Unsplash API. &lt;br /&gt;
&lt;br /&gt;
=== 📚 Методические материалы ===&lt;br /&gt;
* [[Python]]&lt;br /&gt;
{{#ask: [[Python]] | ?Description }}&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
	<entry>
		<id>http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pipinstall-r.jpg&amp;diff=44759</id>
		<title>Файл:Pipinstall-r.jpg</title>
		<link rel="alternate" type="text/html" href="http://digida.mgpu.ru/index.php?title=%D0%A4%D0%B0%D0%B9%D0%BB:Pipinstall-r.jpg&amp;diff=44759"/>
		<updated>2026-03-05T16:27:34Z</updated>

		<summary type="html">&lt;p&gt;CherenkovIR: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;1&lt;/div&gt;</summary>
		<author><name>CherenkovIR</name></author>
	</entry>
</feed>