Skip to content

Commit cba31ef

Browse files
committed
Chat/group infos
1 parent 330337c commit cba31ef

File tree

9 files changed

+191
-6
lines changed

9 files changed

+191
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Group customization
2+
3+
Revision ID: 4a3336b87036
4+
Revises: 27dda7e6236a
5+
Create Date: 2024-04-27 18:42:55.905145
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = '4a3336b87036'
15+
down_revision = '27dda7e6236a'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
op.add_column('group', sa.Column('name', sa.String(), nullable=True))
22+
op.add_column('group', sa.Column('description', sa.String(), nullable=True))
23+
op.add_column('group', sa.Column('invite_link', sa.String(), nullable=True))
24+
op.add_column('group', sa.Column('hidden', sa.Boolean(), nullable=True))
25+
op.execute('UPDATE "group" SET hidden = false;')
26+
op.alter_column('group', 'hidden', nullable=False)
27+
28+
29+
def downgrade():
30+
op.drop_column('group', 'hidden')
31+
op.drop_column('group', 'invite_link')
32+
op.drop_column('group', 'description')
33+
op.drop_column('group', 'name')

social/exceptions.py

+9
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,12 @@ def __init__(self, user_id: int, secret_key: str, *args) -> None:
99
self.user_id = user_id
1010
self.secret_key = secret_key
1111
super().__init__(*args)
12+
13+
14+
class GroupNotFound(SocialApiError):
15+
"""Запрошенная группа не найдена"""
16+
17+
def __init__(self, user_id: int | None, group_id: int, *args) -> None:
18+
self.user_id = user_id
19+
self.group_id = group_id
20+
super().__init__(*args)

social/handlers_telegram/base.py

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ async def send_help(update: Update, context: CustomContext):
4949

5050

5151
async def validate_group(update: Update, context: CustomContext):
52+
"""Если получено сообщение команды /validate, то за группой закрепляется владелец"""
5253
logger.info("Validation message received")
5354
with db():
5455
approve_telegram_group(update)

social/handlers_vk/base.py

+1
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ def process_event(event: dict):
3232
object=lambda i: i.get("message", {}).get("text", "").startswith("/validate"),
3333
)
3434
def validate_group(event: dict):
35+
"""Если получено сообщение команды /validate, то за группой закрепляется владелец"""
3536
approve_vk_chat(event)

social/models/group.py

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ class Group(Base):
1111
type: Mapped[str]
1212
owner_id: Mapped[int | None]
1313

14+
name: Mapped[str | None]
15+
description: Mapped[str | None]
16+
invite_link: Mapped[str | None]
17+
hidden: Mapped[bool] = mapped_column(default=True)
18+
1419
is_deleted: Mapped[bool] = mapped_column(default=False)
1520
last_active_ts: Mapped[datetime] = mapped_column(default=lambda: datetime.now(UTC))
1621

social/routes/exceptions.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from fastapi import Request
22
from fastapi.responses import JSONResponse
33

4-
from social.exceptions import GroupRequestNotFound
4+
from social.exceptions import GroupNotFound, GroupRequestNotFound
55

66
from .base import app
77

@@ -17,3 +17,15 @@ def group_request_not_found(request: Request, exc: GroupRequestNotFound) -> JSON
1717
'secret_key': exc.secret_key,
1818
},
1919
)
20+
21+
22+
@app.exception_handler(GroupNotFound)
23+
def group_not_found(request: Request, exc: GroupNotFound) -> JSONResponse:
24+
return JSONResponse(
25+
status_code=404,
26+
content={
27+
'details': 'Group not found',
28+
'ru': 'Группа не найдена',
29+
'group_id': exc.group_id,
30+
},
31+
)

social/routes/group.py

+82-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
from fastapi_sqlalchemy import db
77
from pydantic import BaseModel
88

9-
from social.exceptions import GroupRequestNotFound
9+
from social.exceptions import GroupNotFound, GroupRequestNotFound
1010
from social.models.create_group_request import CreateGroupRequest
11+
from social.models.group import Group
1112
from social.settings import get_settings
13+
from social.utils.telegram_groups import update_tg_chat
14+
from social.utils.vk_groups import update_vk_chat
1215

1316

1417
router = APIRouter(prefix="/group", tags=['User defined groups'])
@@ -23,6 +26,21 @@ class GroupRequestGet(BaseModel):
2326

2427
class GroupGet(BaseModel):
2528
id: int
29+
owner_id: int | None = None
30+
name: str | None = None
31+
description: str | None = None
32+
invite_link: str | None = None
33+
34+
35+
class GroupGetMany(BaseModel):
36+
items: list[GroupGet]
37+
38+
39+
class GroupPatch(BaseModel):
40+
update_from_source: bool | None = False
41+
name: str | None = None
42+
description: str | None = None
43+
invite_link: str | None = None
2644

2745

2846
@router.post('')
@@ -35,11 +53,15 @@ def create_group_request(
3553
return obj
3654

3755

38-
@router.get('')
56+
@router.get('/validation')
3957
def validate_group_request(
4058
secret_key: str,
4159
user: dict[str] = Depends(UnionAuth(["social.group.create"])),
4260
) -> GroupGet | GroupRequestGet:
61+
"""Получение состояния валидации группы по коду валидации
62+
63+
Трубуются права: `social.group.create`
64+
"""
4365
obj = (
4466
db.session.query(CreateGroupRequest)
4567
.where(CreateGroupRequest.secret_key == secret_key, CreateGroupRequest.owner_id == user.get("id"))
@@ -52,3 +74,61 @@ def validate_group_request(
5274
return GroupGet.model_validate(obj.mapped_group, from_attributes=True)
5375

5476
return GroupRequestGet.model_validate(obj, from_attributes=True)
77+
78+
79+
@router.get('')
80+
def get_all_groups(
81+
my: bool = True,
82+
user: dict[str] = Depends(UnionAuth(allow_none=True, auto_error=False)),
83+
) -> GroupGetMany:
84+
"""Получение списка групп
85+
86+
Трубуются права:
87+
- Для получения списка своих групп права не требуются (`my=True`)
88+
- `social.group.read` для чтения списка всех групп, подключенных к приложению
89+
"""
90+
if not user:
91+
# Возвращаем список видимых всем групп
92+
return {"items": db.session.query(Group).where(Group.hidden == False).all()}
93+
94+
if user and my:
95+
# Возвращаем только свои группы
96+
return {"items": db.session.query(Group).where(Group.owner_id == user.get("id")).all()}
97+
98+
# Если у пользователя есть права на просмотр всех групп – показываем все неудаленные группы
99+
for scope in user.get("session_scopes", []):
100+
if scope.get("name") == "social.group.read":
101+
return {"items": db.session.query(Group).where(Group.is_deleted == False).all()}
102+
103+
# Возвращаем пустный список если не прошли ни по одному условию
104+
logger.debug("User %s has no rights to get groups", user.get("id") if user else None)
105+
return {"items": []}
106+
107+
108+
@router.patch('/{group_id}')
109+
def update_group_info(
110+
group_id: int,
111+
patch_info: GroupPatch,
112+
user: dict[str] = Depends(UnionAuth()),
113+
):
114+
group = db.session.get(Group, group_id)
115+
if group.owner_id != user.get("id"):
116+
raise GroupNotFound(user_id=user.get("id"), group_id=group_id)
117+
118+
# Пытаемся получить данные из источника (получение название чата ВК/Telegram)
119+
if patch_info.update_from_source:
120+
if group.type == "vk_chat":
121+
update_vk_chat(group)
122+
elif group.type == "tg_chat" or group.type == "tg_channel":
123+
update_tg_chat(group)
124+
125+
# Ручное обновление данных
126+
if patch_info.name:
127+
group.name = patch_info.name
128+
if patch_info.description:
129+
group.description = patch_info.description
130+
if patch_info.invite_link:
131+
group.invite_link = patch_info.invite_link
132+
133+
db.session.commit()
134+
return GroupGet.model_validate(group, from_attributes=True)

social/utils/telegram_groups.py

+17
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
import logging
22
from datetime import UTC, datetime
33

4+
import requests
45
from fastapi_sqlalchemy import db
56
from telegram import Update
67

78
from social.models import CreateGroupRequest, TelegramChannel, TelegramChat
9+
from social.settings import get_settings
810

911

1012
logger = logging.getLogger(__name__)
13+
settings = get_settings()
14+
15+
16+
def get_chat_info(id: int) -> dict:
17+
return requests.post(
18+
f'https://api.telegram.org/bot{settings.TELEGRAM_BOT_TOKEN}/getChat',
19+
json={'chat_id': id},
20+
).json()
1121

1222

1323
def create_telegram_group(update: Update):
@@ -47,3 +57,10 @@ def approve_telegram_group(update: Update):
4757
group.owner_id = request.owner_id
4858
db.session.commit()
4959
logger.info("Telegram group %d validated (secret=%s)", group.id, text)
60+
61+
62+
def update_tg_chat(group: TelegramChat):
63+
chat_info = get_chat_info(group.chat_id)
64+
group.name = chat_info.get("title")
65+
group.description = chat_info.get("description")
66+
group.invite_link = chat_info.get("invite_link")

social/utils/vk_groups.py

+30-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
settings = get_settings()
1313

1414

15-
def get_chat_name(peer_id):
15+
def get_chat_info(peer_id):
1616
"""Получить название чата ВК"""
1717
conversation = requests.post(
1818
"https://api.vk.com/method/messages.getConversationsById",
@@ -24,7 +24,25 @@ def get_chat_name(peer_id):
2424
},
2525
)
2626
try:
27-
return conversation["response"]["items"][0]["chat_settings"]["title"]
27+
return conversation["response"]["items"][0]["chat_settings"]
28+
except Exception as exc:
29+
logger.exception(exc)
30+
return None
31+
32+
33+
def get_chat_invite_link(peer_id):
34+
"""Получить название чата ВК"""
35+
conversation = requests.post(
36+
"https://api.vk.com/method/messages.getInviteLink",
37+
json={
38+
"peer_ids": peer_id,
39+
"group_id": settings.VK_BOT_GROUP_ID,
40+
"access_token": settings.VK_BOT_TOKEN,
41+
"v": 5.199,
42+
},
43+
)
44+
try:
45+
return conversation["response"]["link"]
2846
except Exception as exc:
2947
logger.exception(exc)
3048
return None
@@ -57,10 +75,19 @@ def approve_vk_chat(request_data: dict[str]):
5775
group = create_vk_chat(request_data)
5876
text = request_data.get("object", {}).get("message", {}).get("text", "").removeprefix("/validate").strip()
5977
if not text or not group or group.owner_id is not None:
60-
logger.error("Telegram group not validated (secret=%s, group=%s)", text, group)
78+
logger.error("VK group not validated (secret=%s, group=%s)", text, group)
6179
return
6280
request = db.session.query(CreateGroupRequest).where(CreateGroupRequest.secret_key == text).one_or_none()
6381
request.mapped_group_id = group.id
6482
group.owner_id = request.owner_id
6583
db.session.commit()
6684
logger.info("VK group %d validated (secret=%s)", group.id, text)
85+
86+
87+
def update_vk_chat(group: VkChat):
88+
"""Обновляет информацию о группе ВК"""
89+
chat_info = get_chat_info(group.peer_id)
90+
group.name = chat_info.get("title")
91+
group.description = chat_info.get("description")
92+
group.invite_link = get_chat_invite_link(group.peer_id)
93+
return group

0 commit comments

Comments
 (0)