Перед уроком
Обери свою групу й себе зі списку. Тоді відповіді й перевірки завдань запишуться саме на тебе.
Навчити бота писати першим і керувати сервером: за твоєю командою бот створює невеликі програми-скрипти й запускає їх, а за розкладом сам надсилає тобі результат, — і зробити це безпечно, щоб командувати міг лише ти.
Замість <логін> підставляєш свій
логін виду bohdan.g9: ім’я, крапка, номер групи. Кутові дужки не пишуться.
Кабінет — сторінка, де проти кожного критерію стоїть відмітка, зарахований він чи ні.
У прикладах на цій сторінці твій логін — bohdan.g9, а логін однокласника,
сервіс якого ти перевіряєш, — marta.g9. Обидва читай як справжні логіни й
виправляй у кожній команді, яку копіюєш.
Досі твій бот лише відповідав: ти пишеш — він відповідає. Сьогодні він навчиться двох нових речей.
Перша — писати першим. Ти скажеш: «щодня о 09:00 запускай оцю програму й надішли мені, що вийшло», — і о 09:00 у Telegram з’явиться повідомлення від бота, хоча ти нічого не писав. Так працюють нагадування, ранкові новини й сповіщення «сайт упав».
Друга — працювати на сервері за командою. Ти напишеш боту в чат: «створи програму, що показує, скільки лишилося місця на диску» — і бот сам створить на сервері файл-скрипт, а за наступною командою запустить його й надішле результат. Це схоже на пульт керування сервером просто з телефона. Саме тому більшу частину заняття ми вчимося робити таке безпечно: бот слухатиметься лише тебе, а кожен скрипт матиме обмеження за часом.
До кінця заняття в тебе буде:
/whoami, що показує номер твого чату, і перевірка «це власник бота»;/new, /show, /run, /scripts: створити, подивитися, запустити, перелічити скрипти в теці ~/app/scripts;/every, /schedule, /stop: розклад «щодня о такій годині»;morning.py і таймер, які щоранку о 08:00 надсилають тобі звіт — навіть коли сам бот вимкнено.Показати можна буде: повідомлення від бота, на яке ти не писав запиту; файл, створений командою з Telegram; відмову бота, коли команду надсилає хтось інший; скрипт, зупинений через 10 секунд; і list-timers з ранковим звітом.
Якщо в тебе немає TelegramУсе, крім живого повідомлення в Telegram, ти зробиш через try_bot.py: це програма, що імітує повідомлення боту без Telegram. Свій номер чату в такому разі придумай сам, наприклад 777, і дай його програмі прапорцем --id 777. Пункти, яким потрібен справжній токен, викладач оцінить сам.
Бот на занятті 02 працював так: питав Telegram «чи є нові повідомлення?» (це й називається long polling), і коли хтось писав — відповідав. Сам нічого не починав. Але Telegram не забороняє боту писати першим. Для надсилання повідомлення боту потрібні лише дві речі: його токен і chat_id — номер чату, куди писати. Вхідного повідомлення для цього не треба.
Є одне правило Telegram: бот може написати лише тому, хто вже хоч раз написав йому (натиснув Start). Інакше кожен бот міг би засипати спамом будь-кого. Тому для сьогоднішнього уроку ти спершу напишеш своєму боту /start — і він зможе писати тобі першим.
Хто ж «розбудить» бота о 09:00? Є два способи, і ми зробимо обидва:
| Годинник у боті | Таймер systemd | |
|---|---|---|
| Як працює | Окремий потік (друга програмка всередині бота, що працює паралельно з ним) раз на 20 секунд дивиться, котра година: «чи не час щось запускати?» | Операційна система сама запускає окрему коротку програму в потрібний час (як club-backup.timer із заняття 08) |
| Гарно, коли | розклад міняють командами з чату, а бот «пам’ятає» його у файлі | завдання постійне й має працювати, навіть коли бот зламано чи вимкнено |
| Слабке місце | якщо бот не працює — розклад мовчить | розклад змінюють файлом на сервері, а не командою з чату |
Ти хочеш, щоб бот щоранку о 08:00 надсилав тобі звіт, а ще — щоб звіт приходив, навіть коли бот «лежить» після невдалої правки. Що вибрати?
Бот відповідає всім, хто йому пише. Але керувати сервером має право лише одна людина — ти. Як бот зрозуміє, що це саме ти? По імені не можна: first_name кожен ставить собі сам — хтось назве себе так само. По нікові (@username) теж не можна: його можна змінити, а звільнений нік може зайняти інша людина.
Надійно спрацьовує лише chat_id — число, яке Telegram присвоїв цьому акаунту. Підробити його в повідомленні не вдасться: його ставить сам Telegram, а не відправник. Тому:
/whoami показує, який chat_id у того, хто пише;~/app/.env як ADMIN_IDS=… (заняття 07); власником бот вважає перший номер зі списку;Файл .env — бо номер власника це налаштування сервера, а не код (заняття 02): код можуть побачити на GitHub, а власника там вказувати не треба.
Однокласник змінив собі ім’я в Telegram на твоє й написав твоєму боту /run privit. Що має зробити бот?
Скрипт — звичайний текстовий файл, у якому команди термінала записано рядок за рядком. Сервер виконує їх по черзі так, ніби ти набираєш їх сам. Приклад — файл privit.sh:
#!/bin/bash
echo Привіт
dateПерший рядок #!/bin/bash (його називають «шебанг») каже системі, чим читати цей файл: програмою bash. Решта — команди: echo друкує слово, date — поточний час. Запускають так: bash privit.sh. Щоб файл можна було запускати прямо за іменем, йому дають право (дозвіл) на виконання: chmod 700 privit.sh. Число 700 означає: читати, писати й запускати файл може лише ти, власник (права ти вже проходив на занятті 07).
Коли скрипт закінчує роботу, він лишає код виходу — число: 0 означає «усе гаразд», будь-яке інше — «щось не так». Дізнатися код останньої команди можна так: echo $?. Бот покаже його тобі, якщо він не нуль.
Скрипт зручний тим, що це маленький «рецепт» дії: одного разу записав — і запускаєш скільки завгодно, вручну чи за розкладом. Сьогодні бот записуватиме такі рецепти зі слів, які ти напишеш у чаті. Кілька команд в одному рядку відокремлюй крапкою з комою й пробілом: /new privit echo Привіт; date — бот зробить із них два рядки файлу.
Подумай, що ми щойно собі дозволили: хтось надсилає боту текст, а сервер виконує його як команди. Це дуже потужна річ, і саме на таких місцях ламають сервери. Припустимо, до бота додали чужу людину, або в тебе вкрали телефон, або ти сам помилився в команді. Тому захист будують шарами, і жоден шар не вважають достатнім:
~/app/scripts, імена — латиницею й цифрами. Тому не вийде створити файл у чужій теці через ім’я на кшталт ../../щось.sleep 99999 міг би заблокувати бота.sudo, rm -rf, kill, .env…). Це найслабший шар: слово можна записати інакше, склавши з частин. Тому його вважаємо лише страховкою від випадкової помилки, а не захистом від зловмисника. Слово .env заборонено ще й з іншої причини: те, що скрипт надрукує, бот надішле в чат, а чат зберігається на серверах Telegram. Токени й паролі там не місце.~/logs/scripts.log з часом, тож потім видно, що відбувалося.Є ще сьомий «шар» — ти сам. Не запускай скрипт, якого не розумієш. Особливо тепер, коли скрипти можна попросити в ШІ: він пише швидко й упевнено, але іноді помиляється, а команди на сервері це не діалог: помилка виконується одразу. Правило таке: ШІ написав — ти прочитав кожен рядок і зрозумів — тоді запускай. Не розумієш рядок — спитай у ШІ, що він робить, або в викладача.
Ще одне правило: спершу /show (подивитись), потім /run (запустити). Дивитися дешево, а відкотити запущене часто неможливо.
Який шар захисту в цьому списку головний, а без якого решта вже не врятує?
ШІ дав тобі скрипт на десять рядків. Ти не розумієш два з них, але решта виглядає розумно. Що робити?
Розклад — це просто список записів «о котрій годині — який скрипт». Бот зберігає його у файлі ~/data/schedule.json (пам’ятаєш, чому стан не тримають лише в пам’яті програми: заняття 02). Перезапустили службу — розклад на місці.
Кожен запис має ще одне поле — last, дата останнього запуску. Навіщо воно? Потік дивиться, котра година, кожні 20 секунд, а 09:00 триває цілу хвилину, тож без відмітки скрипт запустився б тричі. Правило таке: час збігся і сьогодні ще не запускали — тоді запускаємо й одразу пишемо в last сьогоднішню дату. Відмітку ставимо до запуску: якщо скрипт зламається посеред роботи, повторювати його сто разів за ту саму хвилину не треба.
Кожен запуск за розкладом закінчується надсиланням результату тобі: це і є «бот пише першим». У коді це один виклик tg("sendMessage", chat_id=…, text=…) — той самий, що вже відправляє відповіді.
У розкладі стоїть «09:00 — privit». Потік перевіряє годинник кожні 20 секунд. Скільки разів о 09:00 запуститься скрипт, якщо поле last працює правильно?
| Слово | Що означає |
|---|---|
| писати першим | Бот сам надсилає повідомлення, не чекаючи, поки йому напишуть. Можна лише тому, хто вже писав боту |
| chat_id | Номер чату, який присвоїв Telegram. Підробити його в повідомленні не можна |
| власник | Єдина людина, чиї команди бот виконує. Визначається за chat_id: перший номер у ADMIN_IDS (або OWNER_CHAT_ID, якщо він заданий) |
| скрипт | Текстовий файл із командами термінала, які виконуються по черзі |
| шебанг | Перший рядок скрипта #!/bin/bash: чим його запускати |
| код виходу | Число, яке лишає програма в кінці: 0 — усе добре, інше — помилка |
| ліміт часу (таймаут) | Скільки секунд програмі дозволено працювати, потім її зупиняють |
| розклад | Список «о котрій годині що запускати»; лежить у файлі, тож переживає перезапуск |
| таймер systemd | Пара файлів .timer і .service: операційна система сама запускає програму в потрібний час |
Виконуй кроки по черзі. У прикладах <логін> заміни на свій, наприклад bohdan.g9 (дужки теж прибери). Усе, що змінюєш у bot.py, роби поступово й після кожної правки запускай python3 try_bot.py …: так помилка знайдеться одразу, а не наприкінці.
ssh <логін>@91.219.61.4
cd ~/app
git status --short
systemctl --user is-active club-bot
ls /opt/club/kurs/09/
TZ=Europe/Kyiv date +%H:%Mgit status --short нічого не друкує — усе із заняття 08 збережено. Видно файли — спершу git add -A і git commit -m "Заняття 08: доробки";active — служба працює; inactive — systemctl --user start club-bot;bot.py club-morning.service club-morning.timer morning.py scripts.py. No such file or directory — скажи викладачеві;cp /opt/club/kurs/09/scripts.py ~/app/
cd ~/app
less scripts.pyПройдися по файлу очима (вийти з less — q). Це готова «майстерня» для всіх команд: create() створює файл і ставить права 700; run() запускає його з обмеженням у 10 секунд; tick() перевіряє розклад; command() розбирає текст команди й кличе потрібну функцію. Поряд із ними — константа FORBIDDEN: список слів, які бот не пропустить (розділ 4 теорії). Бота для цього ще не треба — спробуй модуль окремо:
python3 -c "import scripts; print(scripts.command('/new privit echo Привіт; date'))"
cat ~/app/scripts/privit.sh
ls -l ~/app/scripts/
python3 -c "import scripts; print(scripts.command('/run privit'))"Перша команда створила файл ~/app/scripts/privit.sh і відповіла Створено scripts/privit.sh…. У файлі три рядки: шебанг, echo Привіт і date — крапку з комою бот замінив новим рядком. У ls -l права -rwx------ (це і є 700). Остання команда друкує ▶ privit: і під нею Привіт та час. Якщо замість цього No module named 'yaml' — запускай /opt/club/venv/bin/python3 замість python3.
Відкрий ~/app/bot.py (nano ~/app/bot.py, або в code-server, якщо ти там працюєш). Потрібні чотири невеликі вставки. Готовий приклад усього файлу лежить у /opt/club/kurs/09/bot.py, і ти можеш порівняти свій файл з ним командою diff ~/app/bot.py /opt/club/kurs/09/bot.py (порожній вивід означає «однакові»).
Вставка 1. Під рядком import env додай import scripts; поруч із from datetime import datetime — імпорт часового поясу; а під рядком із TOKEN = … — номер власника й київський пояс:
from zoneinfo import ZoneInfo # заняття 09
import scripts # заняття 09
...
# хто керує ботом: перший номер із ADMIN_IDS (заняття 07); OWNER_CHAT_ID, якщо задано, головніший
OWNER = (os.environ.get("OWNER_CHAT_ID") or os.environ.get("ADMIN_IDS", "").split(",")[0]).strip()
KYIV = ZoneInfo("Europe/Kyiv") # розклад іде за київським часом, а не за часом сервераВставка 2. Під текстом HELP додай рядок у підказку й список команд власника:
HELP = ("Що я вмію:\n"
...
"/ask питання — відповім за допомогою ШІ\n"
"/whoami — твій номер чату")
OWNER_COMMANDS = ("/new", "/show", "/run", "/scripts", "/every", "/schedule", "/stop")Вставка 3. У handle_update, перед блоком if text.startswith("/ask"):
chat_id = (msg.get("chat") or {}).get("id")
if text.startswith("/whoami"):
return f"Твій chat_id: {chat_id}"
if text.split()[0].split("@")[0].lower() in OWNER_COMMANDS:
if not OWNER or str(chat_id) != OWNER: # лише власник!
return "Ця команда лише для власника бота."
return scripts.command(text)Зверни увагу на порядок: перевірка власника стоїть раніше за розбір команди. Якби вона стояла після, чужа людина встигла б виконати команду. У рядку if not OWNER or … є слово or: якщо власника в .env не задано взагалі, бот відмовляє всім, а не пускає всіх. Це правило безпеки: коли не впевнений — відмовляй.
Збережи (Ctrl+O, Enter, Ctrl+X у nano) і перевір, що бот ще живий. Прапорець --id — це номер чату «відправника», як у справжньому Telegram:
python3 try_bot.py --id 555 "/whoami"
python3 try_bot.py --id 555 "/run privit"Очікувано: Твій chat_id: 555, а потім Ця команда лише для власника бота. (бо 555 — не власник). Помилка SyntaxError чи IndentationError — подивись рядок, який назвав Python: найчастіше зсунуто відступ вставки або забуто двокрапку. Вставка 4 буде в кроці 9.
Є два шляхи. З Telegram: перезапусти службу й напиши своєму боту /start, а потім /whoami.
systemctl --user restart club-bot
tail -n 5 ~/logs/club-bot.logУ відповідь прийде Твій chat_id: 512… — дев’яти- чи десятизначне число. (Бот не відповідає — подивись у журнал: там видно помилку імпорту.) Без Telegram: візьми довільне число, наприклад 777.
Свій номер ти вже записав у занятті 07 як ADMIN_IDS. Перевір, що він там є (число з'явиться лише у відповіді /whoami, а тут ми показуємо тільки назви змінних):
grep -c '^ADMIN_IDS=[0-9]' ~/app/.env
tail -n 3 ~/app/.env | sed 's/=.*/=…/'Перша команда має надрукувати 1. Друга друкує імена змінних, не показуючи значень. Рядка немає — допиши його (число підстав своє, без лапок і пробілів) і перезапусти службу: echo "ADMIN_IDS=512345678" >> ~/app/.env, потім systemctl --user restart club-bot. Якщо в ADMIN_IDS кілька номерів через кому, власником вважається перший. Перевір, як тепер поводиться бот:
OWNER_CHAT_ID=777 python3 try_bot.py --id 555 "/scripts"
OWNER_CHAT_ID=777 python3 try_bot.py --id 777 "/scripts"Перед командою стоїть OWNER_CHAT_ID=777: так ми підставляємо номер власника лише для цього запуску, а твій справжній .env не чіпаємо. Перший рядок має відповісти Ця команда лише для власника бота. (номер 555 — чужий), другий — перелічити скрипти: Скрипти: privit. Саме таку пару, чужий і власник, потім перевіряє автоперевірка.
Тепер власник захищений — можна створювати скрипти. Пиши боту в Telegram (або через try_bot.py з номером власника, наприклад OWNER_CHAT_ID=777 python3 try_bot.py --id 777 "…" замість кожного рядка):
/new disk df -h ~ | tail -n 2
/show disk
/run disk
/new hvylyna date +%H:%M
/run hvylyna
/scriptsПерша команда створює скрипт disk, друга показує його вміст (два рядки: шебанг і твоя команда), третя запускає й присилає результат: скільки місця на диску. df -h ~ показує вільне місце в читабельних одиницях, а tail -n 2 лишає останні два рядки. Після /scripts побачиш Скрипти: disk, hvylyna, privit. Перевір, що файли справді на сервері:
ls -l ~/app/scripts/
tail -n 5 ~/logs/scripts.logУ журналі кожен крок: створено disk, запуск disk: код 0. «Код 0» — скрипт завершився без помилок. Спробуй і помилку: /new bad ls /нема-такої-теки і /run bad: бот покаже текст помилки й додасть (код виходу 2).
/new test1 sudo ls
/new test2 cat ~/app/.env
/new Мій скрипт echo 1
/new dovgo sleep 30; echo готово
/run dovgoОчікувані відповіді:
sudo ls — Помилка: У скрипті є «sudo» — таке бот не створює.;cat ~/app/.env — те саме, але зі словом «.env». Подумай, чому цей випадок важливий (розділ 4 теорії);dovgo — через 10 секунд ⏱ Скрипт «dovgo» працював понад 10 с — зупинено. Скрипт хотів спати 30 секунд, але бот не чекав.Ліміт часу працює так: бот запускає скрипт окремою програмою, а через 10 секунд зупиняє її і все, що вона встигла запустити (і bash, і sleep усередині). Подивись у ~/app/scripts.py функцію run() і знайди там LIMIT_SEC: це ті самі 10 секунд.
Не вважай заборонені слова захистомХто знає Linux, обійде будь-який список. Список лише ловить випадкові помилки. Головний захист — що команди приймаються тільки від тебе (крок 4), і що в бота обмежені права.
Відкрий чат ШІ, який дозволено за твоїм віком (заняття 01), або OpenCode (заняття 06), і попроси:
Напиши bash-скрипт з 3-5 рядків для Linux: показати 5 найбільших файлів
у домашній теці користувача. Без sudo, без видалення файлів.
Поясни кожен рядок одним реченням.Прочитай відповідь. Типова відповідь схожа на таку (твоя може відрізнятися):
du -ah ~ 2>/dev/null | sort -rh | head -n 5du -ah ~ рахує розмір усіх файлів у домашній теці, 2>/dev/null викидає повідомлення про помилки, sort -rh сортує від найбільших до найменших, head -n 5 лишає п’ять перших. Якщо ти розумієш усе — створи й запусти скрипт:
/new bigfiles du -ah ~ 2>/dev/null | sort -rh | head -n 5
/show bigfiles
/run bigfilesЯкщо щось незрозуміло — спитай ШІ «поясни детальніше рядок …» і не запускай, поки не зрозумієш. Якщо ШІ запропонував sudo, rm, зміну прав чужих тек чи читання .env — не роби цього й покажи викладачеві. У чат ШІ ніколи не вставляй вміст .env та токен.
python3 -c "import scripts; print(scripts.command('/every 09:00 privit'))"
cat ~/data/schedule.json
python3 -c "import scripts; print(scripts.command('/schedule'))"
python3 -c "import scripts; print(scripts.command('/stop privit'))"Перша команда відповість Добре: щодня о 09:00 запускатиму «privit» і надішлю результат. У файлі ~/data/schedule.json тепер запис {"time": "09:00", "script": "privit", "last": ""}: last порожнє, бо ще не запускали. Команда /schedule покаже Розклад: і рядок 09:00 — privit. Остання команда розклад прибрала, і файл став порожнім списком []. Невдалі спроби теж корисні: /every 25:99 privit дасть «Час — у форматі ГГ:ХХ», а /every 09:00 nemaye — «Скрипта «nemaye» немає».
Тепер вставка 4 у bot.py. Перед функцією start_polling() додай дві нові функції:
def run_due(hhmm=None):
"""Запускає скрипти, чий час настав, і надсилає результат власнику."""
if not OWNER:
return
t = datetime.now(KYIV)
scripts.tick(hhmm or t.strftime("%H:%M"), t.strftime("%Y-%m-%d"),
lambda text: tg("sendMessage", chat_id=int(OWNER), text=text))
def schedule_forever(stop):
while not stop.is_set():
try:
run_due()
except Exception as e: # збій розкладу не валить бота
print(f"{now()} помилка розкладу: {e!r}", flush=True)
stop.wait(20)А всередині start_polling(), одразу під рядком, що запускає потік bot, — потік розкладу:
threading.Thread(target=schedule_forever, args=(stop,), daemon=True, name="schedule").start()Що робить run_due. Бере поточні годину й хвилину за Києвом ("09:00") та сьогоднішню дату й кличе scripts.tick. Той переглядає розклад: якщо час запису збігся, а last не дорівнює сьогоднішній даті, він запускає скрипт і передає результат «відправнику». Відправник — це маленька безіменна функція (lambda), яка викликає знайомий tg("sendMessage", …) із номером власника.
Що робить schedule_forever. Це нескінченний цикл у власному потоці: кожні 20 секунд він кличе run_due. Слово daemon=True у вставці нижче означає, що цей потік не заважає боту вимкнутись.
Навіщо try … except і stop.wait(20). try … except потрібен, щоб одна помилка в розкладі не вбила потік назавжди. stop.wait(20) — «поспи 20 секунд, але прокинься негайно, якщо службу зупиняють».
Перезапусти службу й постав розклад на найближчі дві хвилини за київським годинником (з кроку 1). Приклад для часу 14:37:
systemctl --user restart club-bot
TZ=Europe/Kyiv date +%H:%MТепер надішли боту команду. Це пишуть у Telegram боту (або через try_bot.py), а не в термінал:
/every 14:39 diskБез Telegram те саме робить OWNER_CHAT_ID=777 python3 try_bot.py --id 777 "/every 14:39 disk". Справжнє повідомлення першим тоді не прийде, але запуск за розкладом можна побачити й без Telegram. Якщо Telegram у тебе немає, виконай команду нижче: вона вдає, що настала хвилина 14:39, і друкує те, що бот надіслав би власнику:
python3 -c "import scripts; scripts.tick('14:39', '2099-01-01', print)"Побачиш ⏰ За розкладом. і результат скрипта disk. У Telegram ти просто чекаєш: рівно о 14:39 прийде повідомлення, на яке ти не писав, — теж ⏰ За розкладом. і результат disk. А ось що лежить у файлі після запуску:
cat ~/data/schedule.json
tail -n 4 ~/logs/scripts.logУ полі last тепер сьогоднішня дата, а в журналі — запис запуск disk. Повідомлення не приходить: перевір, що ти писав боту /start; що в .env саме твій номер; що час у команді не в минулому; і tail -n 20 ~/logs/club-bot.log — там помилка, якщо вона є.
systemctl --user restart club-bot
OWNER_CHAT_ID=777 python3 try_bot.py --id 777 "/schedule"
systemctl --user kill -s KILL club-bot; sleep 6
systemctl --user is-active club-botТретя команда — «аварійне вимкнення»: вона різко обриває бота, ніби вимкнули світло (sleep 6 чекає шість секунд, поки служба сама підніметься). Остання має надрукувати active. Перезапуск і навіть аварійне вимкнення (з заняття 08) не стирають розклад: він лежить у файлі, а служба піднімається сама (Restart=always). Команда /schedule показує той самий запис. Бачиш Розклад порожній — подивись, чи не опинився файл деінде: шлях має бути ~/data/schedule.json.
cp /opt/club/kurs/09/morning.py ~/app/
cp /opt/club/kurs/09/club-morning.service /opt/club/kurs/09/club-morning.timer ~/.config/systemd/user/
cd ~/app
python3 morning.py --dry--dry («сухий прогін») — лише надрукувати текст, нічого не надсилаючи. Побачиш щось на зразок:
☀ Доброго ранку! Зараз 25.10 14:41.
У базі 9 людей, разом 42 повідомлення.
Вільного місця на диску: 61.3 ГБ.Цифри твої. Тепер справжнє надсилання і таймер:
python3 morning.py
systemctl --user daemon-reload
systemctl --user enable --now club-morning.timer
systemctl --user list-timers club-morning.timerПерша команда надсилає звіт у Telegram (виведе надіслано); для цього в .env мають бути BOT_TOKEN і OWNER_CHAT_ID. Без Telegram ця команда скаже, що токена немає, — це нормально. Далі daemon-reload пропонує systemd перечитати файли, а enable --now вмикає таймер назавжди й одразу. Обидві команди нічого не друкують, і це добре: помилка виглядала б як червоний текст. list-timers покаже, коли таймер спрацює. У колонці NEXT час за сервером: він на 3,5 години попереду Києва, це той самий момент — найближчі 08:00 за Києвом. Запусти ранковий звіт зараз, не чекаючи ранку:
systemctl --user start club-morning.service
tail -n 5 ~/logs/club-morning.logЦей звіт прийде, навіть коли бота вимкнено: morning.py — окрема програма. Переконайся: systemctl --user stop club-bot, потім знову systemctl --user start club-morning.service — звіт усе одно приходить. Не забудь systemctl --user start club-bot після досліду.
Запиши в ~/app/README.md (розділ «Як запустити» чи окремим абзацом) два-три речення своїми словами: чому ранковий звіт зроблено таймером, а розклад скриптів — годинником у боті; і що буде, якщо бот зламається. Це готуватиме тебе до питання викладача.
Скрипти лежать в ~/app/scripts, тому потраплять у Git: це код, і його історія корисна. Розклад (~/data/schedule.json), журнал і файли таймера — поза репозиторієм. Оригінали файлів таймера є в /opt/club/kurs/09/.
cd ~/app
git status --short
git add -A
git commit -m "Заняття 09: бот пише першим, скрипти, розклад, ранковий звіт"
git push origin main
git push github maingit status --short покаже M bot.py і нові ?? scripts.py, ?? morning.py, ?? scripts/. Бачиш .env — зупинись: його не мало бути в списку (заняття 03). Кожен git push закінчується рядком main -> main. Якщо в тебе гілка master, пиши master.
Автоперевірка не пише у твій Telegram і не чіпає твоїх справжніх скриптів: вона запускає твого бота з тимчасовими теками й «підставним» власником — собою. Ось що вона перевіряє:
/whoami бот відповідає твоїм номером чату;~/app/.env є номер власника: рядок ADMIN_IDS= (або OWNER_CHAT_ID=) із числом;/scripts;sudo не створює;run_due справді хтось викликає;morning.py --dry друкує звіт, а в ~/.config/systemd/user є ввімкнений таймер club-morning.timer;Натисни «Перевірити практичну частину» в розділі «Перевірка виконаних завдань» нижче на сторінці. Служба має працювати в цю мить: перевіряй під час заняття або ввечері.
| Код | Що перевіряється | Якщо не зараховано |
|---|---|---|
| L09.1 | /whoami показує номер чату автоперевірка | Додай у handle_update команду /whoami: return f"Твій chat_id: {chat_id}" (крок 3 уроку). |
| L09.2 | у .env є номер власника (ADMIN_IDS) — число автоперевірка | echo "ADMIN_IDS=твій_номер" >> ~/app/.env, без лапок і пробілів (він мав з'явитися ще в занятті 07); номер покаже /whoami. |
| L09.3 | чужа людина не може створювати й запускати скрипти автоперевірка | Перевірка chat_id == OWNER (номер власника з ADMIN_IDS) має стояти ПЕРЕД scripts.command(text) для всіх семи команд. |
| L09.4 | /new створює скрипт, /run запускає, /scripts показує автоперевірка | Скопіюй scripts.py з /opt/club/kurs/09/ у ~/app і перевір вставку 3 у bot.py. |
| L09.5 | завислий скрипт зупиняється, небезпечне слово не пропускається автоперевірка | Не змінюй scripts.py: у ньому ліміт 10 секунд і список заборонених слів. Оригінал: /opt/club/kurs/09/scripts.py |
| L09.6 | за розкладом бот сам пише власнику, один раз на день автоперевірка | Потрібні: функція run_due(hhmm=None) у bot.py, яку кличе цикл schedule_forever (потік у start_polling()), і команда /every. Крок 9 заняття. |
| L09.7 | ранковий звіт: morning.py і увімкнений таймер автоперевірка | cp /opt/club/kurs/09/morning.py ~/app/; скопіюй club-morning.service і .timer у ~/.config/systemd/user/; systemctl --user daemon-reload && systemctl --user enable --now club-morning.timer |
| L09.8 | повідомлення від бота прийшло першим — без запиту викладач | Позначку ставить викладач на занятті або після нього. |
Кожне питання оцінюється відразу в браузері — це лишається так само. Ця кнопка лише надсилає підсумок учителю, щоб відповіді не зникли разом із закритою вкладкою.
Список критеріїв і що саме вони означають — у таблиці вище, «Що перевіряє автоматика». Тут — та сама перевірка, але прямо зараз, без очікування на найближчий прохід за розкладом.
☀ Доброго ранку! Якщо ні — systemctl --user list-timers club-morning.timer (стовпець LAST має показати сьогоднішній ранок) і tail -n 20 ~/logs/club-morning.log.sqlite3 ~/data/app.db "SELECT SUM(messages) FROM users"), або чи працює твій сайт (curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1/u/$(id -un)/, а відповідь 200 означає «працює»; $(id -un) підставляє твій логін, а %{http_code} просить curl надрукувати лише код відповіді). Створи його командою /new, постав у розклад і напиши в README, що він робить.last ставлять до запуску; чим таймер systemd відрізняється від годинника в боті. На початку заняття 10 викладач спитає щось одне з цього.git add -A, git commit -m "Заняття 09: домашня робота", git push origin main і git push github main.Подивись журнал: tail -n 30 ~/logs/club-bot.log. Найчастіше причина в одному з трьох:
ModuleNotFoundError: No module named 'scripts' — файлу scripts.py немає в ~/app (крок 2);SyntaxError чи IndentationError після вставок у bot.py — зсунуто відступ або забуто двокрапку; порівняй: diff ~/app/bot.py /opt/club/kurs/09/bot.py;if text.startswith("/"); її треба перед ним.На /whoami бот має відповісти текстом, де є число. Перевір вставку 3 (команда має бути до перевірки власника). Для L09.2: grep -c '^ADMIN_IDS=[0-9]' ~/app/.env має дати 1. Лапки, пробіл біля = і літери в номері не підходять.
Перевірка власника має бути першою для всіх семи команд: /new /show /run /scripts /every /schedule /stop. Подивись, чи всі вони є в OWNER_COMMANDS і чи перевірка стоїть перед scripts.command(text). Не «додавай виняток для своїх друзів»: усе, що не власник, — відмова.
_, -, до 20 символів;/run порожній — спробуй самостійно bash ~/app/scripts/ім’я.sh, помилку видно одразу;scripts.py: поверни оригінал із /opt/club/kurs/09/scripts.py;sudo створено — ти прибрав FORBIDDEN із create().у bot.py немає функції run_due — вставка 4 не збережена;run_due описано, але ніде не викликається — немає рядка з потоком schedule у start_polling();scripts.py оригінальний (відмітка last ставиться в tick до запуску);OWNER_CHAT_ID — run_due тоді нічого не робить (так і задумано).python3 morning.py --dry дає No module named 'env' — запускай з теки ~/app (cd ~/app);Базу прочитати не вдалося — немає таблиці users: виконай python3 init_db.py (заняття 04);timers.target.wants/club-morning.timer — не виконано systemctl --user enable --now club-morning.timer;Unit club-morning.timer not found — файли не скопійовано у ~/.config/systemd/user/ або забуто daemon-reload.