Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d522e5f73 | ||
|
|
70f1cea78e | ||
|
|
763ca45ab2 |
@@ -0,0 +1,75 @@
|
||||
# Сборка образа и публикация на Docker Hub (hub.docker.com).
|
||||
#
|
||||
# Настройка в GitHub → Settings → Secrets and variables → Actions:
|
||||
# DOCKERHUB_USERNAME — логин Docker Hub
|
||||
# DOCKERHUB_TOKEN — Access Token (рекомендуется), см. https://hub.docker.com/settings/security
|
||||
#
|
||||
# Опционально: Variables → DOCKERHUB_IMAGE (например org/max2telegram), если имя образа
|
||||
# отличается от <DOCKERHUB_USERNAME>/max2telegram.
|
||||
|
||||
name: Docker Hub
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: docker-hub-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write # кэш слоёв Buildx (type=gha)
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set image name
|
||||
id: image
|
||||
run: |
|
||||
if [ -n "${{ vars.DOCKERHUB_IMAGE }}" ]; then
|
||||
echo "name=${{ vars.DOCKERHUB_IMAGE }}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "name=${{ secrets.DOCKERHUB_USERNAME }}/max2telegram" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Docker Hub login
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker metadata (теги)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ steps.image.outputs.name }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha,prefix=
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
+42
-9
@@ -48,6 +48,25 @@ def _format_forward_text(*, sender: dict[str, Any] | None, text: str) -> str:
|
||||
return header
|
||||
|
||||
|
||||
def _is_supported_telegram_message(message: dict[str, Any]) -> bool:
|
||||
# Текстовые сообщения и команды.
|
||||
text = message.get("text")
|
||||
if isinstance(text, str) and text.strip():
|
||||
return True
|
||||
|
||||
photos = message.get("photo")
|
||||
has_photo = isinstance(photos, list) and any(isinstance(p, dict) and p.get("file_id") for p in photos)
|
||||
if has_photo:
|
||||
return True
|
||||
|
||||
video = message.get("video")
|
||||
has_video = isinstance(video, dict) and video.get("file_id")
|
||||
if has_video:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@dataclass
|
||||
class _MediaGroupBuffer:
|
||||
first_seen_monotonic: float
|
||||
@@ -118,7 +137,7 @@ class TelegramToMaxBridge:
|
||||
max_update_id = upd_id if max_update_id is None else max(max_update_id, upd_id)
|
||||
|
||||
message = None
|
||||
for container in ("message", "edited_message", "channel_post", "edited_channel_post"):
|
||||
for container in ("message", "channel_post"):
|
||||
candidate = upd.get(container)
|
||||
if isinstance(candidate, dict):
|
||||
message = candidate
|
||||
@@ -126,6 +145,9 @@ class TelegramToMaxBridge:
|
||||
if not message:
|
||||
continue
|
||||
|
||||
if not _is_supported_telegram_message(message):
|
||||
continue
|
||||
|
||||
if self._is_own_telegram_message(message):
|
||||
continue
|
||||
|
||||
@@ -151,6 +173,14 @@ class TelegramToMaxBridge:
|
||||
if not isinstance(chat, dict):
|
||||
return
|
||||
|
||||
# Команда привязки чата Telegram к названию чата в MAX (для Max->Telegram маршрутизации).
|
||||
# Обрабатываем раньше control-команд, чтобы /bind_max не попадала как "неизвестная".
|
||||
text = str(message.get("text") or "").strip()
|
||||
cmd = text.split(maxsplit=1)[0].split("@", 1)[0].strip().casefold() if text else ""
|
||||
if cmd == "/bind_max":
|
||||
await self._handle_bind_max_command(message, chat)
|
||||
return
|
||||
|
||||
# Управление MAX через Telegram: только личка боту и только от fallback_user_id.
|
||||
# В этом случае команду не пересылаем в MAX.
|
||||
try:
|
||||
@@ -167,13 +197,6 @@ class TelegramToMaxBridge:
|
||||
logger.exception("Cannot send Telegram reply for control command")
|
||||
return
|
||||
|
||||
# Команда привязки чата Telegram к названию чата в MAX (для Max->Telegram маршрутизации).
|
||||
# Работает даже при privacy mode, т.к. команды приходят боту.
|
||||
text = str(message.get("text") or "").strip()
|
||||
if text.startswith("/bind_max"):
|
||||
await self._handle_bind_max_command(message, chat)
|
||||
return
|
||||
|
||||
chat_title = _telegram_chat_title(chat)
|
||||
normalized = _normalize_title(chat_title)
|
||||
if not normalized:
|
||||
@@ -209,10 +232,13 @@ class TelegramToMaxBridge:
|
||||
|
||||
async def _handle_bind_max_command(self, message: dict[str, Any], chat: dict[str, Any]) -> None:
|
||||
raw = str(message.get("text") or "").strip()
|
||||
chat_id = str(chat.get("id") or "")
|
||||
# формат: /bind_max <точное название чата в MAX>
|
||||
parts = raw.split(maxsplit=1)
|
||||
if len(parts) < 2 or not parts[1].strip():
|
||||
logger.error("bind_max: missing MAX chat title. Use: /bind_max <MAX chat title>")
|
||||
if chat_id:
|
||||
await self._telegram.send_text(chat_id=chat_id, text="Использование: /bind_max <точное название чата в MAX>")
|
||||
return
|
||||
|
||||
max_title = parts[1].strip()
|
||||
@@ -222,9 +248,11 @@ class TelegramToMaxBridge:
|
||||
max_chat_id = self._resolve_max_chat_id_by_title(norm)
|
||||
if max_chat_id is None:
|
||||
logger.error("bind_max: MAX чат '%s' не найден — привязку не сохраняю", max_title)
|
||||
if chat_id:
|
||||
await self._telegram.send_text(chat_id=chat_id, text=f"MAX чат '{max_title}' не найден. Привязка не сохранена.")
|
||||
return
|
||||
|
||||
telegram_chat_id = str(chat.get("id"))
|
||||
telegram_chat_id = chat_id
|
||||
telegram_title = _telegram_chat_title(chat)
|
||||
self._storage.set_chat_route(
|
||||
max_chat_title_norm=norm,
|
||||
@@ -237,6 +265,11 @@ class TelegramToMaxBridge:
|
||||
telegram_title,
|
||||
telegram_chat_id,
|
||||
)
|
||||
if chat_id:
|
||||
await self._telegram.send_text(
|
||||
chat_id=chat_id,
|
||||
text=f"Канал успешно привязан: Telegram '{telegram_title or telegram_chat_id}' -> MAX '{max_title}'.",
|
||||
)
|
||||
|
||||
async def _flush_ready_media_groups(self) -> None:
|
||||
now = time.monotonic()
|
||||
|
||||
+3
-3
@@ -132,8 +132,8 @@ class TelegramClient:
|
||||
payload: dict[str, Any] = {
|
||||
"timeout": timeout,
|
||||
"limit": limit,
|
||||
# чтобы получать посты из каналов (channel_post) и обычные сообщения
|
||||
"allowed_updates": ["message", "edited_message", "channel_post", "edited_channel_post"],
|
||||
# Реагируем только на новые сообщения/посты (текст, фото, видео).
|
||||
"allowed_updates": ["message", "channel_post"],
|
||||
}
|
||||
if offset is not None:
|
||||
payload["offset"] = offset
|
||||
@@ -145,7 +145,7 @@ class TelegramClient:
|
||||
# Важно: не делаем getUpdates нигде больше (иначе 409 Conflict).
|
||||
# Наполняем кэш чатов только из этого потока.
|
||||
for upd in updates:
|
||||
for container in ("message", "edited_message", "channel_post", "edited_channel_post"):
|
||||
for container in ("message", "channel_post"):
|
||||
msg = upd.get(container)
|
||||
if not isinstance(msg, dict):
|
||||
continue
|
||||
|
||||
@@ -159,7 +159,8 @@ async def handle_control_command(
|
||||
"/list — список активных чатов MAX\n"
|
||||
"/join <LINK> — присоединиться к группе/каналу по ссылке\n"
|
||||
"/leave <НАЗВАНИЕ> — покинуть указанный канал\n"
|
||||
"/last_messages <НАЗВАНИЕ> — последние 10 сообщений из канала"
|
||||
"/last_messages <НАЗВАНИЕ> — последние 10 сообщений из канала\n"
|
||||
"/bind_max <НАЗВАНИЕ> — привязать текущий Telegram-чат к чату MAX"
|
||||
)
|
||||
|
||||
if cmd == "/list":
|
||||
|
||||
Reference in New Issue
Block a user