Compare commits
4 Commits
2507a1531e
...
c9672d093b
| Author | SHA1 | Date | |
|---|---|---|---|
| c9672d093b | |||
| eef26aba0a | |||
| 14c54ee737 | |||
| e13557059c |
94
.drone.yml
94
.drone.yml
@@ -5,37 +5,103 @@ name: default
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
- dev
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
# --- НАЧАЛО БЛОКА ЛОКАЛЬНОГО КЭШИРОВАНИЯ ---
|
||||
volumes:
|
||||
- name: go-cache # Логическое имя кэша для Go
|
||||
host:
|
||||
# Путь на сервере, где запущен Drone Agent.
|
||||
# Убедитесь, что эта директория существует и у Drone есть права на запись.
|
||||
path: /var/cache/drone/gocache
|
||||
- name: lint-cache # Отдельный кэш для линтера
|
||||
host:
|
||||
path: /var/cache/drone/lintcache
|
||||
# --- КОНЕЦ БЛОКА ЛОКАЛЬНОГО КЭШИРОВАНИЯ ---
|
||||
|
||||
steps:
|
||||
# Общие шаги для всех веток
|
||||
- name: deps
|
||||
image: golang:1.24-alpine
|
||||
volumes:
|
||||
# Монтируем наш go-cache в стандартный GOPATH контейнера
|
||||
- name: go-cache
|
||||
path: /go
|
||||
commands:
|
||||
# Теперь go mod download будет сразу использовать и сохранять кэш на хост-машине
|
||||
- go mod download
|
||||
- go mod tidy
|
||||
|
||||
- name: lint
|
||||
image: golangci/golangci-lint:v1.64-alpine
|
||||
volumes:
|
||||
# Монтируем кэш линтера
|
||||
- name: lint-cache
|
||||
path: /root/.cache
|
||||
commands:
|
||||
# Линтер автоматически подхватит кэш из /root/.cache
|
||||
- golangci-lint run --timeout=5m --verbose ./...
|
||||
|
||||
- name: test
|
||||
image: golang:1.24-alpine
|
||||
volumes:
|
||||
# Также монтируем go-cache, чтобы тесты использовали скачанные модули и кэш сборки
|
||||
- name: go-cache
|
||||
path: /go
|
||||
commands:
|
||||
- apk add --no-cache build-base
|
||||
# CGO_ENABLED=1 go test теперь будет значительно быстрее при повторных запусках
|
||||
- CGO_ENABLED=1 go test -race -cover ./...
|
||||
# Шаг сборки и публикации будет добавлен позже
|
||||
# - name: build-and-publish
|
||||
# image: plugins/docker
|
||||
# settings:
|
||||
# repo: gitea.mrixs.me/mrixs/yamusic-bot
|
||||
# registry: gitea.mrixs.me
|
||||
# username:
|
||||
# from_secret: gitea_username
|
||||
# password:
|
||||
# from_secret: gitea_password
|
||||
# auto_tag: true
|
||||
# platforms:
|
||||
# - linux/amd64
|
||||
# - linux/arm64
|
||||
|
||||
# Шаги сборки и публикации остаются почти без изменений.
|
||||
# Кэширование Docker-слоев (`cache_from`) - это отдельный механизм, и его стоит оставить.
|
||||
# Он дополняет кэширование зависимостей, ускоряя саму сборку Docker-образа.
|
||||
- name: build-and-publish-master
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: gitea.mrixs.me/mrixs/yamusic-bot
|
||||
registry: gitea.mrixs.me
|
||||
username:
|
||||
from_secret: gitea_username
|
||||
password:
|
||||
from_secret: gitea_password
|
||||
auto_tag: true
|
||||
# Оставляем кэширование Docker-слоев, это очень эффективно
|
||||
cache_from:
|
||||
- gitea.mrixs.me/mrixs/yamusic-bot:latest
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
- linux/arm/v7
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- push
|
||||
|
||||
- name: build-and-publish-dev
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: gitea.mrixs.me/mrixs/yamusic-bot
|
||||
registry: gitea.mrixs.me
|
||||
username:
|
||||
from_secret: gitea_username
|
||||
password:
|
||||
from_secret: gitea_password
|
||||
tags:
|
||||
- dev
|
||||
- dev-${DRONE_COMMIT_SHA:0:7}
|
||||
cache_from:
|
||||
- gitea.mrixs.me/mrixs/yamusic-bot:dev
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
- linux/arm/v7
|
||||
when:
|
||||
branch:
|
||||
- dev
|
||||
event:
|
||||
- push
|
||||
|
||||
7
LICENSE
Normal file
7
LICENSE
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright 2025 Vladimir Zagainov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
108
README.md
108
README.md
@@ -1,17 +1,109 @@
|
||||
# Yandex.Music Downloader Bot
|
||||
|
||||
[](https://drone.mrixs.me/Mrixs/yamusic-bot)
|
||||
[](https://drone.mrixs.me/Mrixs/yamusic-bot?branch=master) [](LICENSE)
|
||||
|
||||
Удобный и быстрый Telegram-бот для получения аудиофайлов треков из сервиса Yandex.Music по ссылке.
|
||||
Удобный и быстрый Telegram-бот для получения аудиофайлов из сервиса Yandex.Music. Работает в inline-режиме, позволяя отправлять музыку в любой чат. Поддерживает поиск, а также ссылки на треки, альбомы и исполнителей.
|
||||
|
||||
## Конфигурация
|
||||
## 🚀 Основные возможности
|
||||
|
||||
_(Будет заполнено позже)_
|
||||
* **Inline-режим:** Используйте бота в любом чате, просто упомянув его `@username`.
|
||||
* **Поддержка ссылок:** Распознает и обрабатывает ссылки на треки, альбомы и страницы исполнителей.
|
||||
* **Поиск:** Если введенный текст не является ссылкой, бот выполнит поиск треков по этому тексту.
|
||||
* **Метаданные:** Автоматически встраивает в аудиофайлы ID3-теги (название, исполнитель, альбом, год) и обложку.
|
||||
* **Кэширование:** Мгновенная отправка уже обработанных треков благодаря системе кэширования на базе SQLite и приватного Telegram-канала.
|
||||
* **Администрирование:** Предоставляет набор команд для администраторов для мониторинга и управления ботом.
|
||||
|
||||
## Запуск
|
||||
## ⚙️ Конфигурация
|
||||
|
||||
_(Будет заполнено позже)_
|
||||
Бот настраивается с помощью переменных окружения.
|
||||
|
||||
## Использование
|
||||
| Переменная | Описание | Пример | Обязательно |
|
||||
| ------------------------- | ------------------------------------------------------ | ----------------------------- | ----------- |
|
||||
| `TELEGRAM_BOT_TOKEN` | Токен, полученный от @BotFather. | `12345:ABC-DEF` | **Да** |
|
||||
| `TELEGRAM_ADMIN_IDS` | Список Telegram ID администраторов через запятую. | `1234567,9876543` | **Да** |
|
||||
| `TELEGRAM_CACHE_CHAT_ID` | ID приватного канала/чата для хранения файлов. | `-100123456789` | **Да** |
|
||||
| `YANDEX_MUSIC_TOKEN` | OAuth-токен для доступа к Yandex.Music API. | `y0_...` | **Да** |
|
||||
| `DATABASE_PATH` | Путь к файлу базы данных SQLite. | `/data/bot.db` | Нет (`/data/bot.db`) |
|
||||
| `LOG_LEVEL` | Уровень логирования (`debug`, `info`, `warn`, `error`). | `info` | Нет (`info`) |
|
||||
| `PROCESSOR_WORKERS` | Количество воркеров для обработки треков. | `4` | Нет (`4`) |
|
||||
| `YANDEX_API_RATE_LIMIT` | Запросов в секунду к Yandex API. | `5` | Нет (`5`) |
|
||||
|
||||
_(Будет заполнено позже)_
|
||||
## ▶️ Запуск
|
||||
|
||||
### С помощью Docker 🐳
|
||||
|
||||
Рекомендуемый способ запуска. Убедитесь, что у вас установлен Docker.
|
||||
|
||||
1. Создайте директорию для хранения базы данных: `mkdir -p ./data`
|
||||
2. Запустите контейнер:
|
||||
|
||||
```bash
|
||||
docker run -d --name yamusic-bot \
|
||||
-v $(pwd)/data:/data \
|
||||
-e TELEGRAM_BOT_TOKEN="ВАШ_ТОКЕН" \
|
||||
-e TELEGRAM_ADMIN_IDS="ВАШ_ID,ID_ДРУГА" \
|
||||
-e TELEGRAM_CACHE_CHAT_ID="-100..." \
|
||||
-e YANDEX_MUSIC_TOKEN="y0_..." \
|
||||
--restart always \
|
||||
gitea.mrixs.me/mrixs/yamusic-bot:dev
|
||||
```
|
||||
*Примечание: Используйте тег `:latest` для стабильной версии из ветки `master` или `:dev` для последней сборки из ветки `dev`.*
|
||||
|
||||
### С помощью Docker Compose
|
||||
|
||||
1. Создайте файл `docker-compose.yml`:
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
bot:
|
||||
image: gitea.mrixs.me/mrixs/yamusic-bot:dev
|
||||
container_name: yamusic-bot
|
||||
restart: always
|
||||
volumes:
|
||||
- ./data:/data
|
||||
environment:
|
||||
- TELEGRAM_BOT_TOKEN=
|
||||
- TELEGRAM_ADMIN_IDS=
|
||||
- TELEGRAM_CACHE_CHAT_ID=
|
||||
- YANDEX_MUSIC_TOKEN=
|
||||
- LOG_LEVEL=info
|
||||
```
|
||||
2. Заполните переменные окружения в файле или создайте рядом `.env` файл.
|
||||
3. Запустите: `docker-compose up -d`
|
||||
|
||||
## 🕹️ Использование
|
||||
|
||||
### Для пользователей
|
||||
Начните вводить в любом чате `@bot_username`, а затем вставьте ссылку или напишите поисковый запрос.
|
||||
|
||||
- **Ссылка на трек:** `@bot_username https://music.yandex.ru/album/123/track/456`
|
||||
- **Ссылка на альбом:** `@bot_username https://music.yandex.ru/album/123`
|
||||
- **Поиск:** `@bot_username Rammstein - Sonne`
|
||||
|
||||
### Для администраторов
|
||||
Отправьте команду в личные сообщения боту.
|
||||
|
||||
- `/help` — Показать список команд.
|
||||
- `/stats` — Показать статистику работы бота.
|
||||
- `/find <yandex_track_id>` — Найти трек в кэше по ID.
|
||||
- `/warm <URL>` — "Прогреть" кэш для альбома или исполнителя.
|
||||
|
||||
## 🗺️ План разработки (Dev Roadmap)
|
||||
|
||||
| Статус | Задача | Комментарий |
|
||||
| :----: | -------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
||||
| ✅ | Базовый CI/CD пайплайн (lint, test) | Настроен в `.drone.yml`. |
|
||||
| ✅ | Основная логика обработки URL (трек, альбом, артист) | Реализовано в `internal/bot/handler.go`. |
|
||||
| ✅ | Система кэширования (SQLite + Telegram) | Реализовано в `internal/storage` и `internal/processor`. |
|
||||
| ✅ | Базовая структура проекта и конфигурация | Вся структура соответствует ТЗ. |
|
||||
| ✅ | Административные команды `/help`, `/stats`, `/find` | Основной функционал реализован в `internal/admin/handler.go`. |
|
||||
| ✅ | Публикация Docker-образов в CI/CD | Шаги `build-and-publish-*` активны в `.drone.yml` для `master` и `dev`. |
|
||||
| ⏳ | Расширение тестового покрытия | Есть тесты для `storage`, но нужны для `processor`, `bot`, `admin`. |
|
||||
| ⏳ | Финализация документации | Этот `README.md` является частью задачи. |
|
||||
| ❌ | Реализация логики команды `/warm` | Существует только заглушка, фоновая обработка не реализована. |
|
||||
| ❌ | Ограничение частоты запросов (Rate Limiting) к Yandex API | Требуется внедрение `rate.Limiter`. |
|
||||
| ❌ | Поддержка текстового поиска и коротких URL | Задача из нового ТЗ, требуется реализация в `handler` и `yamusic client`. |
|
||||
|
||||
## 📄 Лицензия
|
||||
|
||||
Проект распространяется под лицензией MIT. См. файл `LICENSE` для получения дополнительной информации.
|
||||
|
||||
8
example.env
Normal file
8
example.env
Normal file
@@ -0,0 +1,8 @@
|
||||
TELEGRAM_BOT_TOKEN=aaa:bbb
|
||||
TELEGRAM_ADMIN_IDS=ccc
|
||||
TELEGRAM_CACHE_CHAT_ID=ddd
|
||||
YANDEX_MUSIC_TOKEN=eee
|
||||
DATABASE_PATH="/data/bot.db"
|
||||
LOG_LEVEL="info"
|
||||
PROCESSOR_WORKERS=4
|
||||
YANDEX_API_RATE_LIMIT=5
|
||||
@@ -4,6 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.mrixs.me/Mrixs/yamusic-bot/internal/interfaces"
|
||||
@@ -56,7 +59,8 @@ func (h *Handler) handleHelp(ctx context.Context, chatID int64) {
|
||||
"/help - Показать это сообщение\n" +
|
||||
"/stats - Показать статистику бота\n" +
|
||||
"/find <yandex_track_id> - Найти трек в кэше по ID\n" +
|
||||
"/warm <URL> - \"Прогреть\" кэш для альбома или исполнителя (в разработке)"
|
||||
"/warm <URL> - \"Прогреть\" кэш для альбома или исполнителя (в разработке)\n" +
|
||||
"/warm --from-dir <path> - Прогреть кэш из локальной директории внутри контейнера"
|
||||
|
||||
if err := h.telegram.SendMessage(ctx, chatID, helpText); err != nil {
|
||||
slog.Error("Failed to send help message", "error", err, "chat_id", chatID)
|
||||
@@ -110,8 +114,93 @@ func (h *Handler) handleFind(ctx context.Context, chatID int64, trackID string)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleWarm(ctx context.Context, chatID int64, url string) {
|
||||
if err := h.telegram.SendMessage(ctx, chatID, "Команда /warm находится в разработке."); err != nil {
|
||||
func (h *Handler) handleWarm(ctx context.Context, chatID int64, args string) {
|
||||
const fromDirPrefix = "--from-dir "
|
||||
if strings.HasPrefix(args, fromDirPrefix) {
|
||||
dirPath := strings.TrimPrefix(args, fromDirPrefix)
|
||||
h.handleWarmFromDir(ctx, chatID, dirPath)
|
||||
return
|
||||
}
|
||||
|
||||
// Здесь будет логика для прогрева по URL
|
||||
if err := h.telegram.SendMessage(ctx, chatID, "Прогрев по URL находится в разработке."); err != nil {
|
||||
slog.Error("Failed to send 'warm in development' message", "error", err, "chat_id", chatID)
|
||||
}
|
||||
}
|
||||
|
||||
// handleWarmFromDir запускает фоновую задачу прогрева кэша из локальной директории.
|
||||
func (h *Handler) handleWarmFromDir(ctx context.Context, chatID int64, dirPath string) {
|
||||
msg := fmt.Sprintf("Принято в обработку. Начинаю прогрев кэша из директории: `%s`", dirPath)
|
||||
if err := h.telegram.SendMessage(ctx, chatID, msg); err != nil {
|
||||
slog.Error("Failed to send 'warm from dir started' message", "error", err, "chat_id", chatID)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
slog.Info("Starting cache warming from directory", "path", dirPath)
|
||||
files, err := os.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
slog.Error("Failed to read directory for warming", "path", dirPath, "error", err)
|
||||
errMsg := fmt.Sprintf("Ошибка: не удалось прочитать директорию `%s`. Убедитесь, что она существует и доступна.", dirPath)
|
||||
_ = h.telegram.SendMessage(context.Background(), chatID, errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
var addedCount, skippedCount, errorCount int
|
||||
totalFiles := len(files)
|
||||
|
||||
for i, file := range files {
|
||||
if file.IsDir() || !strings.HasSuffix(file.Name(), ".mp3") {
|
||||
continue
|
||||
}
|
||||
|
||||
trackID := strings.TrimSuffix(file.Name(), ".mp3")
|
||||
fullPath := filepath.Join(dirPath, file.Name())
|
||||
|
||||
// 1. Проверяем, есть ли трек в кэше
|
||||
_, err := h.storage.Get(ctx, trackID)
|
||||
if err == nil {
|
||||
slog.Debug("Skipping already cached track", "track_id", trackID)
|
||||
skippedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// 2. Загружаем в Telegram
|
||||
// Поскольку метатеги уже вшиты, для отображения в кэш-канале можно использовать простые title/performer
|
||||
slog.Debug("Uploading track to cache channel", "track_id", trackID, "path", fullPath)
|
||||
fileID, err := h.telegram.SendAudioToCacheChannel(ctx, fullPath, trackID, "Pre-cached")
|
||||
if err != nil {
|
||||
slog.Error("Failed to upload pre-cached file", "track_id", trackID, "error", err)
|
||||
errorCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// 3. Сохраняем в БД
|
||||
err = h.storage.Set(ctx, trackID, fileID)
|
||||
if err != nil {
|
||||
slog.Error("Failed to save pre-cached file to storage", "track_id", trackID, "error", err)
|
||||
errorCount++
|
||||
continue
|
||||
}
|
||||
|
||||
addedCount++
|
||||
slog.Info("Successfully cached track from local file", "track_id", trackID, "file_id", fileID)
|
||||
|
||||
// Опционально: отправляем прогресс каждые N файлов
|
||||
if (i+1)%1000 == 0 {
|
||||
progressMsg := fmt.Sprintf("Прогресс: обработано %d из %d файлов...", i+1, totalFiles)
|
||||
_ = h.telegram.SendMessage(context.Background(), chatID, progressMsg)
|
||||
}
|
||||
}
|
||||
|
||||
finalMsg := fmt.Sprintf(
|
||||
"✅ Прогрев кэша из директории `%s` завершен.\n\n"+
|
||||
"Новых треков добавлено: %d\n"+
|
||||
"Треков пропущено (уже в кэше): %d\n"+
|
||||
"Ошибок при обработке: %d",
|
||||
dirPath, addedCount, skippedCount, errorCount,
|
||||
)
|
||||
_ = h.telegram.SendMessage(context.Background(), chatID, finalMsg)
|
||||
slog.Info("Finished cache warming from directory", "path", dirPath, "added", addedCount, "skipped", skippedCount, "errors", errorCount)
|
||||
}()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user