Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tg group owner #31

Merged
merged 6 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions migrations/versions/27dda7e6236a_create_group_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Create group request

Revision ID: 27dda7e6236a
Revises: 62addefd9655
Create Date: 2024-04-15 03:59:03.133907

"""

import sqlalchemy as sa
from alembic import op


# revision identifiers, used by Alembic.
revision = '27dda7e6236a'
down_revision = '62addefd9655'
branch_labels = None
depends_on = None


def upgrade():
op.create_table(
'create_group_request',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('secret_key', sa.String(), nullable=False),
sa.Column('owner_id', sa.Integer(), nullable=False),
sa.Column('mapped_group_id', sa.Integer(), nullable=True),
sa.Column('create_ts', sa.DateTime(), nullable=False),
sa.Column('valid_ts', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(
['mapped_group_id'],
['group.id'],
),
sa.PrimaryKeyConstraint('id'),
)


def downgrade():
op.drop_table('create_group_request')
11 changes: 11 additions & 0 deletions social/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class SocialApiError(Exception):
"""Корневая ошибка Social API"""


class GroupRequestNotFound(SocialApiError):
"""Не найдено запроса на создание группы"""

def __init__(self, user_id: int, secret_key: str, *args) -> None:
self.user_id = user_id
self.secret_key = secret_key
super().__init__(*args)
11 changes: 11 additions & 0 deletions social/handlers_telegram/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
from functools import lru_cache
from textwrap import dedent

from fastapi_sqlalchemy import db
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes

from social.settings import get_settings
from social.utils.telegram_groups import approve_telegram_group

from .handlers_viribus import register_handlers
from .utils import CustomContext
Expand All @@ -24,6 +26,7 @@ def get_application():
logger.info("Telegram API initialized successfully")
# Общие хэндлеры
app.add_handler(CommandHandler(callback=send_help, command="help"))
app.add_handler(CommandHandler(callback=validate_group, command="validate", has_args=1))

# Хэндлеры конкретных чатов
register_handlers(app)
Expand All @@ -43,3 +46,11 @@ async def send_help(update: Update, context: CustomContext):
),
parse_mode='markdown',
)


async def validate_group(update: Update, context: CustomContext):
logger.info("Validation message received")
with db():
approve_telegram_group(update)
res = await update.effective_message.delete()
logger.info(f"Validation message handled, delete status = {res}")
3 changes: 2 additions & 1 deletion social/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .create_group_request import CreateGroupRequest
from .group import TelegramChannel, TelegramChat, VkChat, VkGroup
from .webhook_storage import WebhookStorage


__all__ = ['WebhookStorage', 'TelegramChannel', 'TelegramChat', 'VkGroup', 'VkChat']
__all__ = ['WebhookStorage', 'TelegramChannel', 'TelegramChat', 'VkGroup', 'VkChat', 'CreateGroupRequest']
21 changes: 21 additions & 0 deletions social/models/create_group_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from datetime import UTC, datetime, timedelta

import sqlalchemy as sa
from sqlalchemy.orm import Mapped, mapped_column, relationship

from social.utils.string import random_string

from .base import Base
from .group import Group


class CreateGroupRequest(Base):
id: Mapped[int] = mapped_column(primary_key=True)
secret_key: Mapped[str] = mapped_column(default=lambda: random_string(32))
owner_id: Mapped[int]
mapped_group_id: Mapped[int | None] = mapped_column(sa.ForeignKey("group.id"))

create_ts: Mapped[datetime] = mapped_column(default=lambda: datetime.now(UTC))
valid_ts: Mapped[datetime] = mapped_column(default=lambda: datetime.now(UTC) + timedelta(days=1))

mapped_group: Mapped[Group | None] = relationship(Group)
6 changes: 3 additions & 3 deletions social/models/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class Group(Base):
owner_id: Mapped[int | None]

is_deleted: Mapped[bool] = mapped_column(default=False)
last_active_ts: Mapped[datetime | None]
last_active_ts: Mapped[datetime]

create_ts: Mapped[datetime] = mapped_column(default=lambda: datetime.now(UTC))
update_ts: Mapped[datetime] = mapped_column(default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
Expand Down Expand Up @@ -44,7 +44,7 @@ class VkChat(Group):

class TelegramChannel(Group):
id: Mapped[int] = mapped_column(sa.ForeignKey("group.id"), primary_key=True)
channel_id: Mapped[int]
channel_id: Mapped[int] = mapped_column(sa.BigInteger)

__mapper_args__ = {
"polymorphic_identity": "tg_channel",
Expand All @@ -53,7 +53,7 @@ class TelegramChannel(Group):

class TelegramChat(Group):
id: Mapped[int] = mapped_column(sa.ForeignKey("group.id"), primary_key=True)
chat_id: Mapped[int]
chat_id: Mapped[int] = mapped_column(sa.BigInteger)

__mapper_args__ = {
"polymorphic_identity": "tg_chat",
Expand Down
1 change: 1 addition & 0 deletions social/routes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import exceptions # noqa
2 changes: 2 additions & 0 deletions social/routes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from .discord import router as discord_router
from .github import router as github_router
from .group import router as group_router
from .telegram import router as telegram_router
from .vk import router as vk_router

Expand Down Expand Up @@ -56,6 +57,7 @@ async def lifespan(app: FastAPI):
)


app.include_router(group_router)
app.include_router(github_router)
app.include_router(telegram_router)
app.include_router(vk_router)
Expand Down
19 changes: 19 additions & 0 deletions social/routes/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from fastapi import Request
from fastapi.responses import JSONResponse

from social.exceptions import GroupRequestNotFound

from .base import app


@app.exception_handler(GroupRequestNotFound)
def group_request_not_found(request: Request, exc: GroupRequestNotFound) -> JSONResponse:
return JSONResponse(
status_code=404,
content={
'details': 'Group request not found',
'ru': 'Запрос на создание группы не найден',
'user_id': exc.user_id,
'secret_key': exc.secret_key,
},
)
54 changes: 54 additions & 0 deletions social/routes/group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import logging
from datetime import UTC, datetime

from auth_lib.fastapi import UnionAuth
from fastapi import APIRouter, Depends
from fastapi_sqlalchemy import db
from pydantic import BaseModel

from social.exceptions import GroupRequestNotFound
from social.models.create_group_request import CreateGroupRequest
from social.settings import get_settings


router = APIRouter(prefix="/group", tags=['User defined groups'])
settings = get_settings()
logger = logging.getLogger(__name__)


class GroupRequestGet(BaseModel):
secret_key: str
valid_ts: datetime


class GroupGet(BaseModel):
id: int


@router.post('')
def create_group_request(
user: dict[str] = Depends(UnionAuth(["social.group.create"])),
) -> GroupRequestGet:
obj = CreateGroupRequest(owner_id=user.get("id"))
db.session.add(obj)
db.session.commit()
return obj


@router.get('')
def validate_group_request(
secret_key: str,
user: dict[str] = Depends(UnionAuth(["social.group.create"])),
) -> GroupGet | GroupRequestGet:
obj = (
db.session.query(CreateGroupRequest)
.where(CreateGroupRequest.secret_key == secret_key, CreateGroupRequest.owner_id == user.get("id"))
.one_or_none()
)
if obj is None or obj.valid_ts.replace(tzinfo=UTC) < datetime.now(UTC):
raise GroupRequestNotFound(user_id=user.get("id"), secret_key=secret_key)

if obj.mapped_group_id is not None:
return GroupGet.model_validate(obj.mapped_group, from_attributes=True)

return GroupRequestGet.model_validate(obj, from_attributes=True)
27 changes: 3 additions & 24 deletions social/routes/telegram.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import logging
from asyncio import create_task
from datetime import UTC, datetime

from fastapi import APIRouter, Request
from fastapi_sqlalchemy import db
from telegram import Update

from social.handlers_telegram import get_application
from social.models import TelegramChannel, TelegramChat
from social.models.webhook_storage import WebhookStorage, WebhookSystems
from social.settings import get_settings
from social.utils.telegram_groups import create_telegram_group


router = APIRouter(prefix="/telegram", tags=["webhooks"])
Expand All @@ -33,27 +31,8 @@ async def telegram_webhook(request: Request):
db.session.commit()

update = Update.de_json(data=request_data, bot=application.bot)
add_msg = create_task(application.update_queue.put(update))
await application.update_queue.put(update)
try:
chat = update.effective_chat
obj = None
if chat.type in ['group', 'supergroup']:
obj = db.session.query(TelegramChat).where(TelegramChat.chat_id == chat.id).one_or_none()
if obj is None:
obj = TelegramChat(chat_id=chat.id)
db.session.add(obj)
elif chat.type == 'channel':
obj = db.session.query(TelegramChannel).where(TelegramChannel.channel_id == chat.id).one_or_none()
if obj is None:
obj = TelegramChannel(channel_id=chat.id)
db.session.add(obj)

obj.last_active_ts = datetime.now(UTC)
db.session.commit()
logger.debug(obj)
create_telegram_group(update)
except Exception as exc:
logger.exception(exc)
finally:
await add_msg

return
5 changes: 2 additions & 3 deletions social/routes/vk.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from fastapi_sqlalchemy import db
from pydantic import BaseModel, ConfigDict

from social.handlers_telegram import get_application
from social.models.group import VkChat, VkGroup
from social.models.webhook_storage import WebhookStorage, WebhookSystems
from social.settings import get_settings
Expand All @@ -17,7 +16,6 @@
router = APIRouter(prefix="/vk", tags=['vk'])
settings = get_settings()
logger = logging.getLogger(__name__)
application = get_application()


class VkGroupCreate(BaseModel):
Expand Down Expand Up @@ -81,7 +79,8 @@ async def vk_webhook(request: Request) -> str:

@router.put('/{group_id}')
def create_or_replace_group(
group_id: int, group_info: VkGroupCreate,
group_id: int,
group_info: VkGroupCreate,
user: dict[str] = Depends(UnionAuth(["social.group.create"])),
) -> VkGroupCreateResponse:
group = db.session.query(VkGroup).where(VkGroup.group_id == group_id).one_or_none()
Expand Down
46 changes: 46 additions & 0 deletions social/utils/telegram_groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import logging
from datetime import UTC, datetime

from fastapi_sqlalchemy import db
from telegram import Update

from social.models import CreateGroupRequest, TelegramChannel, TelegramChat


logger = logging.getLogger(__name__)


def create_telegram_group(update: Update):
chat = update.effective_chat
obj = None
if chat.type in ['group', 'supergroup']:
obj = db.session.query(TelegramChat).where(TelegramChat.chat_id == chat.id).one_or_none()
if obj is None:
obj = TelegramChat(chat_id=chat.id)
db.session.add(obj)
elif chat.type == 'channel':
obj = db.session.query(TelegramChannel).where(TelegramChannel.channel_id == chat.id).one_or_none()
if obj is None:
obj = TelegramChannel(channel_id=chat.id)
db.session.add(obj)

if not obj:
return

obj.last_active_ts = datetime.now(UTC)
db.session.commit()
return obj


def approve_telegram_group(update: Update):
logger.debug("Validation started")
group = create_telegram_group(update)
text = update.effective_message.text
if not text or not group:
logger.error("Telegram group not validated (secret=%s, group=%s)", text, group)
return
text = text.removeprefix('/validate').removeprefix('@ViribusSocialBot').strip()
db.session.query(CreateGroupRequest).where(CreateGroupRequest.secret_key == text).update(
{CreateGroupRequest.mapped_group_id: group.id}
)
logger.info("Telegram group %d validated (secret=%s)", group.id, text)
Loading