mirror of
https://github.com/openmax-server/server.git
synced 2026-05-23 20:11:41 +03:00
Compare commits
25 Commits
6bb0d52419
...
dev/0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0e23840b5 | ||
|
|
c5721f3f9e | ||
|
|
03cffc24aa | ||
|
|
87f22a3feb | ||
|
|
7d2e070d1f | ||
|
|
24b0123185 | ||
|
|
31844c7fa2 | ||
|
|
9b60b15538 | ||
|
|
0d91f6542e | ||
|
|
77d6ca8cc0 | ||
|
|
3bf8bc5770 | ||
|
|
861b75eb1c | ||
|
|
fa0ed34adc | ||
|
|
87cfc1932e | ||
|
|
17245f44d0 | ||
|
|
b1a37bfa24 | ||
|
|
d81eec5532 | ||
|
|
ddb810589f | ||
|
|
dff6937da8 | ||
|
|
ac40cc53c9 | ||
|
|
756956d8a0 | ||
|
|
00071c80be | ||
|
|
a045457128 | ||
|
|
4d51c70f8e | ||
|
|
2d3b9285bf |
@@ -44,6 +44,7 @@ class Opcodes:
|
|||||||
CONTACT_MUTUAL = 38
|
CONTACT_MUTUAL = 38
|
||||||
CONTACT_PHOTOS = 39
|
CONTACT_PHOTOS = 39
|
||||||
CONTACT_SORT = 40
|
CONTACT_SORT = 40
|
||||||
|
CONTACT_ADD_BY_PHONE = 41
|
||||||
CONTACT_VERIFY = 42
|
CONTACT_VERIFY = 42
|
||||||
REMOVE_CONTACT_PHOTO = 43
|
REMOVE_CONTACT_PHOTO = 43
|
||||||
CHAT_INFO = 48
|
CHAT_INFO = 48
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ class SQLQueries:
|
|||||||
|
|
||||||
INSERT_USER = """
|
INSERT_USER = """
|
||||||
INSERT INTO users
|
INSERT INTO users
|
||||||
(phone, telegram_id, firstname, lastname, username,
|
(id, phone, telegram_id, firstname, lastname, username,
|
||||||
profileoptions, options, accountstatus, updatetime, lastseen)
|
profileoptions, options, accountstatus, updatetime, lastseen)
|
||||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
INSERT_USER_DATA = """
|
INSERT_USER_DATA = """
|
||||||
|
|||||||
63
src/common/sqlite.py
Normal file
63
src/common/sqlite.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
class SQLiteCursorCompat:
|
||||||
|
def __init__(self, connection):
|
||||||
|
self.connection = connection
|
||||||
|
self.cursor = None
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
self.cursor = await self.connection.cursor()
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type, exc, tb):
|
||||||
|
if self.cursor is not None:
|
||||||
|
await self.cursor.close()
|
||||||
|
self.cursor = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lastrowid(self):
|
||||||
|
return None if self.cursor is None else self.cursor.lastrowid
|
||||||
|
|
||||||
|
def _normalize_query(self, query):
|
||||||
|
return query.replace("%s", "?").replace(
|
||||||
|
"UNIX_TIMESTAMP()", "CAST(strftime('%s','now') AS INTEGER)"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def execute(self, query, params=()):
|
||||||
|
normalized_query = self._normalize_query(query)
|
||||||
|
if params is None:
|
||||||
|
params = ()
|
||||||
|
elif not isinstance(params, (tuple, list, dict)):
|
||||||
|
params = (params,)
|
||||||
|
await self.cursor.execute(normalized_query, params)
|
||||||
|
|
||||||
|
async def fetchone(self):
|
||||||
|
row = await self.cursor.fetchone()
|
||||||
|
if row is None:
|
||||||
|
return None
|
||||||
|
return dict(row)
|
||||||
|
|
||||||
|
async def fetchall(self):
|
||||||
|
rows = await self.cursor.fetchall()
|
||||||
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
class SQLiteConnectionCompat:
|
||||||
|
def __init__(self, connection):
|
||||||
|
self.connection = connection
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type, exc, tb):
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def commit(self):
|
||||||
|
await self.connection.commit()
|
||||||
|
|
||||||
|
def cursor(self):
|
||||||
|
return SQLiteCursorCompat(self.connection)
|
||||||
|
|
||||||
|
class SQLitePoolCompat:
|
||||||
|
def __init__(self, connection):
|
||||||
|
self.connection = connection
|
||||||
|
|
||||||
|
def acquire(self):
|
||||||
|
return SQLiteConnectionCompat(self.connection)
|
||||||
@@ -13,6 +13,9 @@ class Static:
|
|||||||
CHAT_NOT_FOUND = "chat_not_found"
|
CHAT_NOT_FOUND = "chat_not_found"
|
||||||
CHAT_NOT_ACCESS = "chat_not_access"
|
CHAT_NOT_ACCESS = "chat_not_access"
|
||||||
RATE_LIMITED = "rate_limited"
|
RATE_LIMITED = "rate_limited"
|
||||||
|
CONTACT_NOT_FOUND = "contact_not_found"
|
||||||
|
CONTACT_ALREADY_ADDED = "contact_already_added"
|
||||||
|
CONTACT_BLOCKED = "contact_blocked"
|
||||||
|
|
||||||
class ChatTypes:
|
class ChatTypes:
|
||||||
DIALOG = "DIALOG"
|
DIALOG = "DIALOG"
|
||||||
@@ -80,7 +83,25 @@ class Static:
|
|||||||
"error": "error.rate_limited",
|
"error": "error.rate_limited",
|
||||||
"message": "Too many attempts. Please try again later",
|
"message": "Too many attempts. Please try again later",
|
||||||
"title": "Слишком много попыток"
|
"title": "Слишком много попыток"
|
||||||
}
|
},
|
||||||
|
"contact_not_found": {
|
||||||
|
"localizedMessage": "Контакт не найден",
|
||||||
|
"error": "contact.not.found",
|
||||||
|
"message": "Contact not found",
|
||||||
|
"title": "Контакт не найден"
|
||||||
|
},
|
||||||
|
"contact_already_added": {
|
||||||
|
"localizedMessage": "Контакт уже добавлен",
|
||||||
|
"error": "contact.already.added",
|
||||||
|
"message": "Contact already added",
|
||||||
|
"title": "Контакт уже добавлен"
|
||||||
|
},
|
||||||
|
"contact_blocked": {
|
||||||
|
"localizedMessage": "Вы не можете написать этому пользователю",
|
||||||
|
"error": "contact.blocked",
|
||||||
|
"message": "Contact is blocked",
|
||||||
|
"title": "Вы не можете написать этому пользователю"
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
### Сообщения бота
|
### Сообщения бота
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
|
import random
|
||||||
|
import secrets
|
||||||
import time
|
import time
|
||||||
import os
|
|
||||||
|
|
||||||
import geoip2.database
|
import geoip2.database
|
||||||
|
|
||||||
@@ -9,19 +10,46 @@ class Tools:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def build_message_dict(self, row, protocol_type="mobile"):
|
||||||
|
"""Сборка тела сообщения"""
|
||||||
|
try:
|
||||||
|
attaches = json.loads(row.get("attaches") or "[]")
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
attaches = []
|
||||||
|
try:
|
||||||
|
elements = json.loads(row.get("elements") or "[]")
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
elements = []
|
||||||
|
|
||||||
|
message = {
|
||||||
|
"id": row.get("id") if protocol_type == "mobile" else str(row.get("id")),
|
||||||
|
"cid": int(row.get("cid") or 0),
|
||||||
|
"chatId": int(row.get("chat_id") or 0),
|
||||||
|
"time": int(row.get("time")),
|
||||||
|
"type": row.get("type") or "USER",
|
||||||
|
"sender": row.get("sender"),
|
||||||
|
"text": row.get("text") or "",
|
||||||
|
"attaches": attaches,
|
||||||
|
"elements": elements,
|
||||||
|
"reactionInfo": {},
|
||||||
|
"link": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return message
|
||||||
|
|
||||||
def generate_profile(
|
def generate_profile(
|
||||||
self,
|
self,
|
||||||
id=1,
|
id=None,
|
||||||
phone=70000000000,
|
phone=None,
|
||||||
avatarUrl=None,
|
avatarUrl=None,
|
||||||
photoId=None,
|
photoId=None,
|
||||||
updateTime=0,
|
updateTime=None,
|
||||||
firstName="Test",
|
firstName=None,
|
||||||
lastName="Account",
|
lastName=None,
|
||||||
options=[],
|
options=None,
|
||||||
description=None,
|
description=None,
|
||||||
accountStatus=0,
|
accountStatus=None,
|
||||||
profileOptions=[],
|
profileOptions=None,
|
||||||
includeProfileOptions=True,
|
includeProfileOptions=True,
|
||||||
username=None,
|
username=None,
|
||||||
|
|
||||||
@@ -45,6 +73,8 @@ class Tools:
|
|||||||
],
|
],
|
||||||
"options": options,
|
"options": options,
|
||||||
"accountStatus": accountStatus,
|
"accountStatus": accountStatus,
|
||||||
|
"location": "RU",
|
||||||
|
"registrationTime": int(time.time() * 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
if avatarUrl:
|
if avatarUrl:
|
||||||
@@ -78,22 +108,40 @@ class Tools:
|
|||||||
|
|
||||||
def generate_profile_tt(
|
def generate_profile_tt(
|
||||||
self,
|
self,
|
||||||
id=1,
|
id=None,
|
||||||
phone=70000000000,
|
phone=None,
|
||||||
avatarUrl=None,
|
avatarUrl=None,
|
||||||
photoId=None,
|
photoId=None,
|
||||||
updateTime=0,
|
updateTime=None,
|
||||||
firstName="Test",
|
firstName=None,
|
||||||
lastName="Account",
|
lastName=None,
|
||||||
options=[],
|
options=None,
|
||||||
description=None,
|
description=None,
|
||||||
username=None,
|
username=None,
|
||||||
|
custom_firstname=None,
|
||||||
|
custom_lastname=None,
|
||||||
|
blocked=None
|
||||||
):
|
):
|
||||||
|
# Так как TT не поддерживает фамилию, и если нам ее не передали в функцию
|
||||||
|
# то используем только имя, чтобы избежать None в фамилии
|
||||||
|
if firstName and lastName:
|
||||||
|
name = f"{firstName} {lastName}"
|
||||||
|
else:
|
||||||
|
name = firstName
|
||||||
|
|
||||||
|
# Используем такой же костыль, как и выше
|
||||||
|
if custom_firstname and custom_lastname:
|
||||||
|
custom_name = f"{custom_firstname} {custom_lastname}"
|
||||||
|
elif custom_firstname:
|
||||||
|
custom_name = custom_firstname
|
||||||
|
else:
|
||||||
|
custom_name = None
|
||||||
|
|
||||||
contact = {
|
contact = {
|
||||||
"id": id,
|
"id": id,
|
||||||
"updateTime": updateTime,
|
"updateTime": updateTime,
|
||||||
"phone": phone,
|
"phone": phone,
|
||||||
"names": [{"name": f"{firstName} {lastName}", "type": "TT"}],
|
"names": [{"name": name, "type": "TT"}],
|
||||||
"options": options,
|
"options": options,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,8 +153,19 @@ class Tools:
|
|||||||
if description:
|
if description:
|
||||||
contact["description"] = description
|
contact["description"] = description
|
||||||
|
|
||||||
|
# NOTE: официальный сервер вроде как отдавал tt.me, но клиент примет любую ссылку
|
||||||
|
# можно потом как нибудь сделать возможность редактирования этого момента, но это
|
||||||
|
# позже, так как по юзернейму искать пока нельзя
|
||||||
if username:
|
if username:
|
||||||
contact["link"] = "https://tamtam.chat/" + username
|
contact["link"] = "https://tt.me/" + username
|
||||||
|
|
||||||
|
if custom_firstname:
|
||||||
|
contact["names"].append(
|
||||||
|
{"name": custom_name, "type": "CUSTOM"}
|
||||||
|
)
|
||||||
|
|
||||||
|
if blocked:
|
||||||
|
contact["status"] = "BLOCKED"
|
||||||
|
|
||||||
return contact
|
return contact
|
||||||
|
|
||||||
@@ -204,27 +263,28 @@ class Tools:
|
|||||||
|
|
||||||
if include_favourites:
|
if include_favourites:
|
||||||
# Получаем последнее сообщение из избранного
|
# Получаем последнее сообщение из избранного
|
||||||
|
favouriteChatId = -senderId
|
||||||
message, messageTime = await self.get_last_message(
|
message, messageTime = await self.get_last_message(
|
||||||
senderId, db_pool, protocol_type=protocol_type
|
favouriteChatId, db_pool, protocol_type=protocol_type
|
||||||
)
|
)
|
||||||
|
|
||||||
# ID избранного
|
# ID избранного для клиента
|
||||||
chatId = senderId ^ senderId
|
chatId = senderId ^ senderId
|
||||||
|
|
||||||
# Получаем последнюю активность участника (отправителя) в избранном
|
# Получаем последнюю активность в избранном
|
||||||
participants = await self.get_participant_last_activity(
|
participants = await self.get_participant_last_activity(
|
||||||
senderId, [senderId], db_pool
|
favouriteChatId, [senderId], db_pool
|
||||||
)
|
)
|
||||||
|
|
||||||
# Получаем ID предыдущего сообщения для избранного (чат ID = senderId)
|
# Получаем ID предыдущего сообщения для избранного
|
||||||
prevMessageId = await self.get_previous_message_id(
|
prevMessageId = await self.get_previous_message_id(
|
||||||
senderId, db_pool, protocol_type=protocol_type
|
favouriteChatId, db_pool, protocol_type=protocol_type
|
||||||
)
|
)
|
||||||
|
|
||||||
# Хардкодим в лист чатов избранное
|
# Хардкодим в лист чатов избранное
|
||||||
chats.append(
|
chats.append(
|
||||||
self.generate_chat(
|
self.generate_chat(
|
||||||
chatId if protocol_type == "mobile" else str(chatId),
|
chatId,
|
||||||
senderId,
|
senderId,
|
||||||
"DIALOG",
|
"DIALOG",
|
||||||
participants,
|
participants,
|
||||||
@@ -370,10 +430,14 @@ class Tools:
|
|||||||
last_message_id = row.get("id") or 0 # последнее id сообщения в чате
|
last_message_id = row.get("id") or 0 # последнее id сообщения в чате
|
||||||
message_time = int(time.time() * 1000) # время отправки сообщения
|
message_time = int(time.time() * 1000) # время отправки сообщения
|
||||||
|
|
||||||
|
# Генерируем ID сообщения
|
||||||
|
message_id = int(time.time() * 1000000) * 1000 + random.randint(100, 999)
|
||||||
|
|
||||||
# Вносим новое сообщение в таблицу
|
# Вносим новое сообщение в таблицу
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"INSERT INTO `messages` (chat_id, sender, time, text, attaches, cid, elements, type) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
|
"INSERT INTO `messages` (id, chat_id, sender, time, text, attaches, cid, elements, type) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)",
|
||||||
(
|
(
|
||||||
|
message_id,
|
||||||
chatId,
|
chatId,
|
||||||
senderId,
|
senderId,
|
||||||
message_time,
|
message_time,
|
||||||
@@ -385,8 +449,6 @@ class Tools:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
message_id = cursor.lastrowid
|
|
||||||
|
|
||||||
# Возвращаем айдишки
|
# Возвращаем айдишки
|
||||||
return int(message_id), int(last_message_id), message_time
|
return int(message_id), int(last_message_id), message_time
|
||||||
|
|
||||||
@@ -406,24 +468,8 @@ class Tools:
|
|||||||
if not row:
|
if not row:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
# Собираем сообщение
|
|
||||||
message = {
|
|
||||||
"id": row.get("id")
|
|
||||||
if protocol_type == "mobile"
|
|
||||||
else str(row.get("id")),
|
|
||||||
"time": int(row.get("time")),
|
|
||||||
"type": row.get("type"),
|
|
||||||
"sender": row.get("sender"),
|
|
||||||
"cid": int(row.get("cid")),
|
|
||||||
"text": row.get("text"),
|
|
||||||
"attaches": json.loads(row.get("attaches")),
|
|
||||||
"elements": json.loads(row.get("elements")),
|
|
||||||
"reactionInfo": {},
|
|
||||||
"link": {}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Возвращаем
|
# Возвращаем
|
||||||
return message, int(row.get("time"))
|
return self.build_message_dict(row, protocol_type), int(row.get("time"))
|
||||||
|
|
||||||
async def get_previous_message_id(self, chatId, db_pool, protocol_type="mobile"):
|
async def get_previous_message_id(self, chatId, db_pool, protocol_type="mobile"):
|
||||||
"""Получение ID предыдущего сообщения (второго с конца) в чате."""
|
"""Получение ID предыдущего сообщения (второго с конца) в чате."""
|
||||||
@@ -561,4 +607,34 @@ class Tools:
|
|||||||
response = reader.country(ip)
|
response = reader.country(ip)
|
||||||
return response.country.name or "Localhost Federation"
|
return response.country.name or "Localhost Federation"
|
||||||
except Exception:
|
except Exception:
|
||||||
return "Localhost Federation"
|
return "Localhost Federation"
|
||||||
|
|
||||||
|
async def generate_user_id(self, db_pool):
|
||||||
|
"""Генерация id пользователя"""
|
||||||
|
async with db_pool.acquire() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
while True:
|
||||||
|
user_id = secrets.randbelow(2_147_383_647) + 100_000
|
||||||
|
await cursor.execute("SELECT id FROM users WHERE id = %s", (user_id,))
|
||||||
|
if not await cursor.fetchone():
|
||||||
|
return user_id
|
||||||
|
|
||||||
|
async def contact_is_blocked(self, owner_id, contact_id, db_pool):
|
||||||
|
"""
|
||||||
|
По изначальной задумке, данная функция должна проверять, заблокирован ли контакт
|
||||||
|
На сервере долгое время не был доделан черный список, хотя управление им было реализовано
|
||||||
|
(на деле, это я поленился)
|
||||||
|
|
||||||
|
Вернёт вам true, если контакт заблокирован, иначе false
|
||||||
|
"""
|
||||||
|
# Проверяем наличие контакта
|
||||||
|
async with db_pool.acquire() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
await cursor.execute("SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s AND is_blocked = %s", (owner_id, contact_id, True))
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
|
||||||
|
# Есди контакт существует и заблокирован, возвращаем true,
|
||||||
|
if row:
|
||||||
|
return True
|
||||||
|
else: # в ином случае false
|
||||||
|
return False
|
||||||
77
src/main.py
77
src/main.py
@@ -4,9 +4,11 @@ import logging
|
|||||||
import signal
|
import signal
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
from common.config import ServerConfig
|
from common.config import ServerConfig
|
||||||
from common.push import PushService
|
from common.push import PushService
|
||||||
|
from common.sqlite import SQLitePoolCompat
|
||||||
from oneme.controller import OnemeController
|
from oneme.controller import OnemeController
|
||||||
from tamtam.controller import TTController
|
from tamtam.controller import TTController
|
||||||
from telegrambot.controller import TelegramBotController
|
from telegrambot.controller import TelegramBotController
|
||||||
@@ -14,71 +16,6 @@ from telegrambot.controller import TelegramBotController
|
|||||||
# Конфиг сервера
|
# Конфиг сервера
|
||||||
server_config = ServerConfig()
|
server_config = ServerConfig()
|
||||||
|
|
||||||
|
|
||||||
class SQLiteCursorCompat:
|
|
||||||
def __init__(self, connection):
|
|
||||||
self.connection = connection
|
|
||||||
self.cursor = None
|
|
||||||
|
|
||||||
async def __aenter__(self):
|
|
||||||
self.cursor = await self.connection.cursor()
|
|
||||||
return self
|
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc, tb):
|
|
||||||
if self.cursor is not None:
|
|
||||||
await self.cursor.close()
|
|
||||||
self.cursor = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def lastrowid(self):
|
|
||||||
return None if self.cursor is None else self.cursor.lastrowid
|
|
||||||
|
|
||||||
def _normalize_query(self, query):
|
|
||||||
return query.replace("%s", "?").replace(
|
|
||||||
"UNIX_TIMESTAMP()", "CAST(strftime('%s','now') AS INTEGER)"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def execute(self, query, params=()):
|
|
||||||
normalized_query = self._normalize_query(query)
|
|
||||||
if params is None:
|
|
||||||
params = ()
|
|
||||||
elif not isinstance(params, (tuple, list, dict)):
|
|
||||||
params = (params,)
|
|
||||||
await self.cursor.execute(normalized_query, params)
|
|
||||||
|
|
||||||
async def fetchone(self):
|
|
||||||
row = await self.cursor.fetchone()
|
|
||||||
if row is None:
|
|
||||||
return None
|
|
||||||
return dict(row)
|
|
||||||
|
|
||||||
async def fetchall(self):
|
|
||||||
rows = await self.cursor.fetchall()
|
|
||||||
return [dict(row) for row in rows]
|
|
||||||
|
|
||||||
|
|
||||||
class SQLiteConnectionCompat:
|
|
||||||
def __init__(self, connection):
|
|
||||||
self.connection = connection
|
|
||||||
|
|
||||||
async def __aenter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc, tb):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def cursor(self):
|
|
||||||
return SQLiteCursorCompat(self.connection)
|
|
||||||
|
|
||||||
|
|
||||||
class SQLitePoolCompat:
|
|
||||||
def __init__(self, connection):
|
|
||||||
self.connection = connection
|
|
||||||
|
|
||||||
def acquire(self):
|
|
||||||
return SQLiteConnectionCompat(self.connection)
|
|
||||||
|
|
||||||
|
|
||||||
async def init_db():
|
async def init_db():
|
||||||
"""Инициализация базы данных"""
|
"""Инициализация базы данных"""
|
||||||
|
|
||||||
@@ -116,7 +53,6 @@ def init_ssl():
|
|||||||
# Возвращаем
|
# Возвращаем
|
||||||
return ssl_context
|
return ssl_context
|
||||||
|
|
||||||
|
|
||||||
def set_logging():
|
def set_logging():
|
||||||
"""Настройка уровня логирования"""
|
"""Настройка уровня логирования"""
|
||||||
# Настройка уровня логирования
|
# Настройка уровня логирования
|
||||||
@@ -195,8 +131,13 @@ async def main():
|
|||||||
# Запускаем контроллеры
|
# Запускаем контроллеры
|
||||||
try:
|
try:
|
||||||
await asyncio.gather(*running_tasks)
|
await asyncio.gather(*running_tasks)
|
||||||
except (asyncio.CancelledError, Exception):
|
except asyncio.CancelledError:
|
||||||
logging.info("Все задачи завершены, выходим")
|
logging.info("Все задачи завершены")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(
|
||||||
|
f"Произошла неизвестная ошибка: {e}"
|
||||||
|
)
|
||||||
|
traceback.print_exc()
|
||||||
finally:
|
finally:
|
||||||
if hasattr(db, 'close'):
|
if hasattr(db, 'close'):
|
||||||
db.close()
|
db.close()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class OnemeController(ControllerBase):
|
|||||||
|
|
||||||
# Выбираем протокол в зависимости от типа подключения
|
# Выбираем протокол в зависимости от типа подключения
|
||||||
proto = self.proto_web if is_web else self.proto_tcp
|
proto = self.proto_web if is_web else self.proto_tcp
|
||||||
|
packet = None
|
||||||
|
|
||||||
# Не отправляем событие самому себе
|
# Не отправляем событие самому себе
|
||||||
if writer == eventData.get("writer"):
|
if writer == eventData.get("writer"):
|
||||||
@@ -94,7 +95,9 @@ class OnemeController(ControllerBase):
|
|||||||
cmd=0, seq=1, opcode=self.opcodes.NOTIF_PRESENCE, payload=payload
|
cmd=0, seq=1, opcode=self.opcodes.NOTIF_PRESENCE, payload=payload
|
||||||
)
|
)
|
||||||
|
|
||||||
# Отправляем пакет
|
if not packet:
|
||||||
|
return
|
||||||
|
|
||||||
if is_web:
|
if is_web:
|
||||||
await writer.send(packet)
|
await writer.send(packet)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -54,6 +54,32 @@ class AssetsPayloadModel(pydantic.BaseModel):
|
|||||||
sync: int
|
sync: int
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
|
class AssetsGetPayloadModel(pydantic.BaseModel):
|
||||||
|
type: str
|
||||||
|
count: int = 100
|
||||||
|
query: str = None
|
||||||
|
|
||||||
|
class AssetsGetByIdsPayloadModel(pydantic.BaseModel):
|
||||||
|
type: str
|
||||||
|
ids: list
|
||||||
|
|
||||||
|
class AssetsAddPayloadModel(pydantic.BaseModel):
|
||||||
|
type: str
|
||||||
|
id: int = None
|
||||||
|
|
||||||
|
class AssetsRemovePayloadModel(pydantic.BaseModel):
|
||||||
|
type: str
|
||||||
|
ids: list
|
||||||
|
|
||||||
|
class AssetsMovePayloadModel(pydantic.BaseModel):
|
||||||
|
type: str
|
||||||
|
id: int
|
||||||
|
position: int
|
||||||
|
|
||||||
|
class AssetsListModifyPayloadModel(pydantic.BaseModel):
|
||||||
|
type: str
|
||||||
|
ids: list
|
||||||
|
|
||||||
class GetCallHistoryPayloadModel(pydantic.BaseModel):
|
class GetCallHistoryPayloadModel(pydantic.BaseModel):
|
||||||
forward: bool
|
forward: bool
|
||||||
count: int
|
count: int
|
||||||
@@ -145,8 +171,12 @@ class ContactListPayloadModel(pydantic.BaseModel):
|
|||||||
class ContactPresencePayloadModel(pydantic.BaseModel):
|
class ContactPresencePayloadModel(pydantic.BaseModel):
|
||||||
contactIds: list
|
contactIds: list
|
||||||
|
|
||||||
|
class ContactAddByPhonePayloadModel(pydantic.BaseModel):
|
||||||
|
phone: str
|
||||||
|
firstName: str
|
||||||
|
|
||||||
class ContactUpdatePayloadModel(pydantic.BaseModel):
|
class ContactUpdatePayloadModel(pydantic.BaseModel):
|
||||||
action: str
|
action: str
|
||||||
contactId: int
|
contactId: int
|
||||||
firstName: str
|
firstName: str = None
|
||||||
lastName: str = None
|
lastName: str = None
|
||||||
@@ -2,7 +2,7 @@ from .assets import AssetsProcessors
|
|||||||
from .auth import AuthProcessors
|
from .auth import AuthProcessors
|
||||||
from .calls import CallsProcessors
|
from .calls import CallsProcessors
|
||||||
from .chats import ChatsProcessors
|
from .chats import ChatsProcessors
|
||||||
from .complains import ComplainsProcessors
|
from .complaints import ComplaintsProcessors
|
||||||
from .contacts import ContactsProcessors
|
from .contacts import ContactsProcessors
|
||||||
from .folders import FoldersProcessors
|
from .folders import FoldersProcessors
|
||||||
from .history import HistoryProcessors
|
from .history import HistoryProcessors
|
||||||
@@ -16,7 +16,7 @@ class Processors(
|
|||||||
AuthProcessors,
|
AuthProcessors,
|
||||||
CallsProcessors,
|
CallsProcessors,
|
||||||
ChatsProcessors,
|
ChatsProcessors,
|
||||||
ComplainsProcessors,
|
ComplaintsProcessors,
|
||||||
ContactsProcessors,
|
ContactsProcessors,
|
||||||
FoldersProcessors,
|
FoldersProcessors,
|
||||||
HistoryProcessors,
|
HistoryProcessors,
|
||||||
|
|||||||
@@ -1,31 +1,167 @@
|
|||||||
import pydantic
|
import pydantic
|
||||||
import time
|
import time
|
||||||
from classes.baseprocessor import BaseProcessor
|
from classes.baseprocessor import BaseProcessor
|
||||||
from oneme.models import AssetsPayloadModel
|
from oneme.models import (
|
||||||
|
AssetsPayloadModel,
|
||||||
|
AssetsGetPayloadModel,
|
||||||
|
AssetsGetByIdsPayloadModel,
|
||||||
|
AssetsAddPayloadModel,
|
||||||
|
AssetsRemovePayloadModel,
|
||||||
|
AssetsMovePayloadModel,
|
||||||
|
AssetsListModifyPayloadModel,
|
||||||
|
)
|
||||||
|
|
||||||
class AssetsProcessors(BaseProcessor):
|
class AssetsProcessors(BaseProcessor):
|
||||||
async def assets_update(self, payload, seq, writer):
|
async def assets_update(self, payload, seq, writer):
|
||||||
"""Обработчик запроса ассетов клиента на сервере"""
|
|
||||||
# Валидируем данные пакета
|
|
||||||
try:
|
try:
|
||||||
AssetsPayloadModel.model_validate(payload)
|
AssetsPayloadModel.model_validate(payload)
|
||||||
except pydantic.ValidationError as error:
|
except pydantic.ValidationError as error:
|
||||||
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
await self._send_error(seq, self.opcodes.ASSETS_UPDATE, self.error_types.INVALID_PAYLOAD, writer)
|
await self._send_error(seq, self.opcodes.ASSETS_UPDATE, self.error_types.INVALID_PAYLOAD, writer)
|
||||||
return
|
return
|
||||||
|
|
||||||
# TODO: сейчас это заглушка, а попозже нужно сделать полноценную реализацию
|
|
||||||
|
|
||||||
# Данные пакета
|
response = {
|
||||||
payload = {
|
"sync": int(time.time() * 1000),
|
||||||
"sections": [],
|
"stickerSetsUpdates": {},
|
||||||
"sync": int(time.time() * 1000)
|
"stickersUpdates": {},
|
||||||
|
"stickersOrder": [
|
||||||
|
"RECENT",
|
||||||
|
"FAVORITE_STICKERS",
|
||||||
|
"FAVORITE_STICKER_SETS",
|
||||||
|
"TOP",
|
||||||
|
"NEW",
|
||||||
|
"NEW_STICKER_SETS",
|
||||||
|
],
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"id": "RECENT",
|
||||||
|
"type": "RECENTS",
|
||||||
|
"recentsList": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "FAVORITE_STICKERS",
|
||||||
|
"type": "STICKERS",
|
||||||
|
"stickers": [],
|
||||||
|
"marker": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "FAVORITE_STICKER_SETS",
|
||||||
|
"type": "STICKER_SETS",
|
||||||
|
"stickerSets": [],
|
||||||
|
"marker": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "TOP",
|
||||||
|
"type": "STICKERS",
|
||||||
|
"stickers": [],
|
||||||
|
"marker": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "NEW",
|
||||||
|
"type": "STICKERS",
|
||||||
|
"stickers": [],
|
||||||
|
"marker": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "NEW_STICKER_SETS",
|
||||||
|
"type": "STICKER_SETS",
|
||||||
|
"stickerSets": [],
|
||||||
|
"marker": None,
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Собираем пакет
|
|
||||||
packet = self.proto.pack_packet(
|
packet = self.proto.pack_packet(
|
||||||
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_UPDATE, payload=payload
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_UPDATE, payload=response
|
||||||
)
|
)
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
# Отправляем
|
async def assets_get(self, payload, seq, writer):
|
||||||
await self._send(writer, packet)
|
try:
|
||||||
|
data = AssetsGetPayloadModel.model_validate(payload)
|
||||||
|
except pydantic.ValidationError as error:
|
||||||
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
|
await self._send_error(seq, self.opcodes.ASSETS_GET, self.error_types.INVALID_PAYLOAD, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
asset_type = data.type
|
||||||
|
if asset_type == "STICKER_SET":
|
||||||
|
response = {"stickerSets": [], "marker": None}
|
||||||
|
else:
|
||||||
|
response = {"stickers": [], "marker": None}
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_GET, payload=response
|
||||||
|
)
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
async def assets_get_by_ids(self, payload, seq, writer):
|
||||||
|
try:
|
||||||
|
data = AssetsGetByIdsPayloadModel.model_validate(payload)
|
||||||
|
except pydantic.ValidationError as error:
|
||||||
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
|
await self._send_error(seq, self.opcodes.ASSETS_GET_BY_IDS, self.error_types.INVALID_PAYLOAD, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
asset_type = data.type
|
||||||
|
if asset_type == "STICKER_SET":
|
||||||
|
response = {"stickerSets": []}
|
||||||
|
else:
|
||||||
|
response = {"stickers": []}
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_GET_BY_IDS, payload=response
|
||||||
|
)
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
async def assets_add(self, payload, seq, writer):
|
||||||
|
try:
|
||||||
|
AssetsAddPayloadModel.model_validate(payload)
|
||||||
|
except pydantic.ValidationError as error:
|
||||||
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
|
await self._send_error(seq, self.opcodes.ASSETS_ADD, self.error_types.INVALID_PAYLOAD, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_ADD, payload={}
|
||||||
|
)
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
async def assets_remove(self, payload, seq, writer):
|
||||||
|
try:
|
||||||
|
AssetsRemovePayloadModel.model_validate(payload)
|
||||||
|
except pydantic.ValidationError as error:
|
||||||
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
|
await self._send_error(seq, self.opcodes.ASSETS_REMOVE, self.error_types.INVALID_PAYLOAD, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_REMOVE, payload={}
|
||||||
|
)
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
async def assets_move(self, payload, seq, writer):
|
||||||
|
try:
|
||||||
|
AssetsMovePayloadModel.model_validate(payload)
|
||||||
|
except pydantic.ValidationError as error:
|
||||||
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
|
await self._send_error(seq, self.opcodes.ASSETS_MOVE, self.error_types.INVALID_PAYLOAD, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_MOVE, payload={}
|
||||||
|
)
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
async def assets_list_modify(self, payload, seq, writer):
|
||||||
|
try:
|
||||||
|
AssetsListModifyPayloadModel.model_validate(payload)
|
||||||
|
except pydantic.ValidationError as error:
|
||||||
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
|
await self._send_error(seq, self.opcodes.ASSETS_LIST_MODIFY, self.error_types.INVALID_PAYLOAD, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_LIST_MODIFY, payload={}
|
||||||
|
)
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ class AuthProcessors(BaseProcessor):
|
|||||||
photoId = (
|
photoId = (
|
||||||
None if not account.get("avatar_id") else int(account.get("avatar_id"))
|
None if not account.get("avatar_id") else int(account.get("avatar_id"))
|
||||||
)
|
)
|
||||||
avatar_url = None if not photoId else self.config.avatar_base_url + photoId
|
avatar_url = None if not photoId else self.config.avatar_base_url + str(photoId)
|
||||||
description = (
|
description = (
|
||||||
None if not account.get("description") else account.get("description")
|
None if not account.get("description") else account.get("description")
|
||||||
)
|
)
|
||||||
@@ -397,15 +397,19 @@ class AuthProcessors(BaseProcessor):
|
|||||||
now_ms = int(time.time() * 1000)
|
now_ms = int(time.time() * 1000)
|
||||||
now_s = int(time.time())
|
now_s = int(time.time())
|
||||||
|
|
||||||
|
# Генерируем ID пользователя
|
||||||
|
user_id = await self.tools.generate_user_id(self.db_pool)
|
||||||
|
|
||||||
# Создаем пользователя
|
# Создаем пользователя
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO users
|
INSERT INTO users
|
||||||
(phone, telegram_id, firstname, lastname, username,
|
(id, phone, telegram_id, firstname, lastname, username,
|
||||||
profileoptions, options, accountstatus, updatetime, lastseen)
|
profileoptions, options, accountstatus, updatetime, lastseen)
|
||||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
|
user_id,
|
||||||
phone,
|
phone,
|
||||||
None,
|
None,
|
||||||
first_name,
|
first_name,
|
||||||
@@ -419,8 +423,6 @@ class AuthProcessors(BaseProcessor):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
user_id = cursor.lastrowid
|
|
||||||
|
|
||||||
# Добавляем данные аккаунта
|
# Добавляем данные аккаунта
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"""
|
"""
|
||||||
@@ -519,7 +521,7 @@ class AuthProcessors(BaseProcessor):
|
|||||||
await self._send_error(
|
await self._send_error(
|
||||||
seq, self.opcodes.LOGIN, self.error_types.INVALID_PAYLOAD, writer
|
seq, self.opcodes.LOGIN, self.error_types.INVALID_PAYLOAD, writer
|
||||||
)
|
)
|
||||||
return
|
return None, None, None
|
||||||
|
|
||||||
# Чаты, где состоит пользователь
|
# Чаты, где состоит пользователь
|
||||||
chats = []
|
chats = []
|
||||||
@@ -543,7 +545,7 @@ class AuthProcessors(BaseProcessor):
|
|||||||
await self._send_error(
|
await self._send_error(
|
||||||
seq, self.opcodes.LOGIN, self.error_types.INVALID_TOKEN, writer
|
seq, self.opcodes.LOGIN, self.error_types.INVALID_TOKEN, writer
|
||||||
)
|
)
|
||||||
return
|
return None, None, None
|
||||||
|
|
||||||
# Ищем аккаунт пользователя в бд
|
# Ищем аккаунт пользователя в бд
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
@@ -561,7 +563,7 @@ class AuthProcessors(BaseProcessor):
|
|||||||
# Ищем все чаты, где состоит пользователь
|
# Ищем все чаты, где состоит пользователь
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"SELECT * FROM chat_participants WHERE user_id = %s",
|
"SELECT * FROM chat_participants WHERE user_id = %s",
|
||||||
(user.get("id")),
|
(user.get("id"),),
|
||||||
)
|
)
|
||||||
user_chats = await cursor.fetchall()
|
user_chats = await cursor.fetchall()
|
||||||
|
|
||||||
@@ -576,7 +578,7 @@ class AuthProcessors(BaseProcessor):
|
|||||||
|
|
||||||
# Аватарка с биографией
|
# Аватарка с биографией
|
||||||
photoId = None if not user.get("avatar_id") else int(user.get("avatar_id"))
|
photoId = None if not user.get("avatar_id") else int(user.get("avatar_id"))
|
||||||
avatar_url = None if not photoId else self.config.avatar_base_url + photoId
|
avatar_url = None if not photoId else self.config.avatar_base_url + str(photoId)
|
||||||
description = None if not user.get("description") else user.get("description")
|
description = None if not user.get("description") else user.get("description")
|
||||||
|
|
||||||
if self._check_legacy_version(appVersion):
|
if self._check_legacy_version(appVersion):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import time
|
|||||||
from classes.baseprocessor import BaseProcessor
|
from classes.baseprocessor import BaseProcessor
|
||||||
from oneme.models import ComplainReasonsGetPayloadModel
|
from oneme.models import ComplainReasonsGetPayloadModel
|
||||||
|
|
||||||
class ComplainsProcessors(BaseProcessor):
|
class ComplaintsProcessors(BaseProcessor):
|
||||||
async def complain_reasons_get(self, payload, seq, writer):
|
async def complain_reasons_get(self, payload, seq, writer):
|
||||||
"""Обработчик получения причин жалоб"""
|
"""Обработчик получения причин жалоб"""
|
||||||
# Валидируем данные пакета
|
# Валидируем данные пакета
|
||||||
@@ -2,7 +2,7 @@ import pydantic
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from classes.baseprocessor import BaseProcessor
|
from classes.baseprocessor import BaseProcessor
|
||||||
from oneme.models import ContactListPayloadModel, ContactPresencePayloadModel, ContactUpdatePayloadModel
|
from oneme.models import ContactAddByPhonePayloadModel, ContactListPayloadModel, ContactPresencePayloadModel, ContactUpdatePayloadModel
|
||||||
|
|
||||||
class ContactsProcessors(BaseProcessor):
|
class ContactsProcessors(BaseProcessor):
|
||||||
async def contact_list(self, payload, seq, writer, userId):
|
async def contact_list(self, payload, seq, writer, userId):
|
||||||
@@ -95,7 +95,7 @@ class ContactsProcessors(BaseProcessor):
|
|||||||
user = await cursor.fetchone()
|
user = await cursor.fetchone()
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.USER_NOT_FOUND, writer)
|
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_NOT_FOUND, writer)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Проверяем, не добавлен ли уже контакт
|
# Проверяем, не добавлен ли уже контакт
|
||||||
@@ -112,9 +112,26 @@ class ContactsProcessors(BaseProcessor):
|
|||||||
"INSERT INTO contacts (owner_id, contact_id, custom_firstname, custom_lastname, is_blocked) VALUES (%s, %s, %s, %s, FALSE)",
|
"INSERT INTO contacts (owner_id, contact_id, custom_firstname, custom_lastname, is_blocked) VALUES (%s, %s, %s, %s, FALSE)",
|
||||||
(userId, contactId, firstName, lastName)
|
(userId, contactId, firstName, lastName)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Создаем диалог, если его нет
|
||||||
|
chatId = userId ^ contactId
|
||||||
|
await cursor.execute("SELECT * FROM chats WHERE id = %s", (chatId,))
|
||||||
|
chat = await cursor.fetchone()
|
||||||
|
|
||||||
|
if not chat:
|
||||||
|
await cursor.execute(
|
||||||
|
"INSERT INTO chats (id, owner, type) VALUES (%s, %s, %s)",
|
||||||
|
(chatId, userId, "DIALOG")
|
||||||
|
)
|
||||||
|
|
||||||
|
for uid in [int(userId), int(contactId)]:
|
||||||
|
await cursor.execute(
|
||||||
|
"INSERT INTO chat_participants (chat_id, user_id) VALUES (%s, %s)",
|
||||||
|
(chatId, uid)
|
||||||
|
)
|
||||||
# а если уже существует, отправляем ошибку
|
# а если уже существует, отправляем ошибку
|
||||||
else:
|
else:
|
||||||
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_ALREADY_EXISTS, writer)
|
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_ALREADY_ADDED, writer)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Генерируем профиль
|
# Генерируем профиль
|
||||||
@@ -161,6 +178,213 @@ class ContactsProcessors(BaseProcessor):
|
|||||||
|
|
||||||
await self._send(writer, packet)
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
elif action == "BLOCK":
|
||||||
|
async with self.db_pool.acquire() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
# Проверяем, существует ли контакт
|
||||||
|
await cursor.execute(
|
||||||
|
"SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s",
|
||||||
|
(userId, contactId)
|
||||||
|
)
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
|
||||||
|
# Обновляем существующий контакт, если такой есть
|
||||||
|
if row:
|
||||||
|
await cursor.execute(
|
||||||
|
"UPDATE contacts SET is_blocked = TRUE WHERE owner_id = %s AND contact_id = %s",
|
||||||
|
(userId, contactId)
|
||||||
|
)
|
||||||
|
else: # В ином случае добавляем новую запись в бд
|
||||||
|
await cursor.execute("SELECT * FROM users WHERE id = %s", (contactId,))
|
||||||
|
user = await cursor.fetchone()
|
||||||
|
if not user:
|
||||||
|
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.USER_NOT_FOUND, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
await cursor.execute(
|
||||||
|
"INSERT INTO contacts (owner_id, contact_id, custom_firstname, custom_lastname, is_blocked) VALUES (%s, %s, %s, %s, TRUE)",
|
||||||
|
(userId, contactId, firstName, lastName)
|
||||||
|
)
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_UPDATE, payload=None
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
elif action == "UNBLOCK":
|
||||||
|
# Разблокируем контакт
|
||||||
|
async with self.db_pool.acquire() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
# Проверяем, существует ли контакт
|
||||||
|
await cursor.execute(
|
||||||
|
"SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s",
|
||||||
|
(userId, contactId)
|
||||||
|
)
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
|
||||||
|
# Обновляем контакт, если он есть
|
||||||
|
if row:
|
||||||
|
await cursor.execute(
|
||||||
|
"UPDATE contacts SET is_blocked = FALSE WHERE owner_id = %s AND contact_id = %s",
|
||||||
|
(userId, contactId)
|
||||||
|
)
|
||||||
|
else: # В ином случае отправляем ошибку
|
||||||
|
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_NOT_FOUND, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_UPDATE, payload=None
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
elif action == "UPDATE":
|
||||||
|
async with self.db_pool.acquire() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
# Проверяем, существует ли контакт
|
||||||
|
await cursor.execute(
|
||||||
|
"SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s",
|
||||||
|
(userId, contactId)
|
||||||
|
)
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
|
||||||
|
# Если контакта нет, отдаем ошибку
|
||||||
|
if not row:
|
||||||
|
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_NOT_FOUND, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Обновляем контакт
|
||||||
|
await cursor.execute(
|
||||||
|
"UPDATE contacts SET custom_firstname = %s, custom_lastname = %s WHERE owner_id = %s AND contact_id = %s",
|
||||||
|
(firstName, lastName, userId, contactId)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Получаем данные пользователя
|
||||||
|
await cursor.execute("SELECT * FROM users WHERE id = %s", (contactId,))
|
||||||
|
user = await cursor.fetchone()
|
||||||
|
|
||||||
|
# Генерируем профиль
|
||||||
|
photoId = None if not user.get("avatar_id") else int(user.get("avatar_id"))
|
||||||
|
avatar_url = None if not photoId else self.config.avatar_base_url + str(photoId)
|
||||||
|
|
||||||
|
contact = self.tools.generate_profile(
|
||||||
|
id=user.get("id"),
|
||||||
|
phone=int(user.get("phone")),
|
||||||
|
avatarUrl=avatar_url,
|
||||||
|
photoId=photoId,
|
||||||
|
updateTime=int(user.get("updatetime")),
|
||||||
|
firstName=user.get("firstname"),
|
||||||
|
lastName=user.get("lastname"),
|
||||||
|
options=json.loads(user.get("options")),
|
||||||
|
accountStatus=int(user.get("accountstatus")),
|
||||||
|
description=user.get("description"),
|
||||||
|
includeProfileOptions=False,
|
||||||
|
custom_firstname=firstName,
|
||||||
|
custom_lastname=lastName,
|
||||||
|
)
|
||||||
|
|
||||||
|
response_payload = {
|
||||||
|
"contact": contact
|
||||||
|
}
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_UPDATE, payload=response_payload
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
async def contact_add_by_phone(self, payload, seq, writer, userId):
|
||||||
|
"""Добавление контакта по номеру телефона"""
|
||||||
|
# Валидируем данные пакета
|
||||||
|
try:
|
||||||
|
ContactAddByPhonePayloadModel.model_validate(payload)
|
||||||
|
except pydantic.ValidationError as error:
|
||||||
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
|
await self._send_error(seq, self.opcodes.CONTACT_ADD_BY_PHONE, self.error_types.INVALID_PAYLOAD, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
phone = payload.get("phone")
|
||||||
|
firstName = payload.get("firstName")
|
||||||
|
lastName = payload.get("lastName")
|
||||||
|
|
||||||
|
# Ищем пользователя по номеру телефона
|
||||||
|
async with self.db_pool.acquire() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
await cursor.execute("SELECT * FROM users WHERE phone = %s", (int(phone),))
|
||||||
|
user = await cursor.fetchone()
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
await self._send_error(seq, self.opcodes.CONTACT_ADD_BY_PHONE, self.error_types.CONTACT_NOT_FOUND, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
contactId = user.get("id")
|
||||||
|
|
||||||
|
# Проверяем, не добавлен ли уже контакт
|
||||||
|
await cursor.execute(
|
||||||
|
"SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s",
|
||||||
|
(userId, contactId)
|
||||||
|
)
|
||||||
|
existing_contact = await cursor.fetchone()
|
||||||
|
|
||||||
|
is_new = existing_contact is None
|
||||||
|
|
||||||
|
if is_new:
|
||||||
|
# Добавляем контакт
|
||||||
|
await cursor.execute(
|
||||||
|
"INSERT INTO contacts (owner_id, contact_id, custom_firstname, custom_lastname) VALUES (%s, %s, %s, %s)",
|
||||||
|
(userId, contactId, firstName, lastName)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Создаем диалог, если его нет
|
||||||
|
chatId = userId ^ contactId
|
||||||
|
await cursor.execute("SELECT * FROM chats WHERE id = %s", (chatId,))
|
||||||
|
chat = await cursor.fetchone()
|
||||||
|
|
||||||
|
if not chat:
|
||||||
|
await cursor.execute(
|
||||||
|
"INSERT INTO chats (id, owner, type) VALUES (%s, %s, %s)",
|
||||||
|
(chatId, userId, "DIALOG")
|
||||||
|
)
|
||||||
|
|
||||||
|
for uid in [int(userId), int(contactId)]:
|
||||||
|
await cursor.execute(
|
||||||
|
"INSERT INTO chat_participants (chat_id, user_id) VALUES (%s, %s)",
|
||||||
|
(chatId, uid)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Генерируем профиль
|
||||||
|
photoId = None if not user.get("avatar_id") else int(user.get("avatar_id"))
|
||||||
|
avatar_url = None if not photoId else self.config.avatar_base_url + str(photoId)
|
||||||
|
|
||||||
|
contact = self.tools.generate_profile(
|
||||||
|
id=user.get("id"),
|
||||||
|
phone=int(user.get("phone")),
|
||||||
|
avatarUrl=avatar_url,
|
||||||
|
photoId=photoId,
|
||||||
|
updateTime=int(user.get("updatetime")),
|
||||||
|
firstName=user.get("firstname"),
|
||||||
|
lastName=user.get("lastname"),
|
||||||
|
options=json.loads(user.get("options")),
|
||||||
|
accountStatus=int(user.get("accountstatus")),
|
||||||
|
description=user.get("description"),
|
||||||
|
includeProfileOptions=False,
|
||||||
|
custom_firstname=firstName,
|
||||||
|
custom_lastname=lastName,
|
||||||
|
username=user.get("username"),
|
||||||
|
)
|
||||||
|
|
||||||
|
response_payload = {
|
||||||
|
"new": is_new,
|
||||||
|
"contact": contact
|
||||||
|
}
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_ADD_BY_PHONE, payload=response_payload
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
async def contact_presence(self, payload, seq, writer):
|
async def contact_presence(self, payload, seq, writer):
|
||||||
"""Обработчик получения статуса контактов"""
|
"""Обработчик получения статуса контактов"""
|
||||||
# Валидируем данные пакета
|
# Валидируем данные пакета
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import pydantic
|
import pydantic
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
from classes.baseprocessor import BaseProcessor
|
from classes.baseprocessor import BaseProcessor
|
||||||
from oneme.models import ChatHistoryPayloadModel
|
from oneme.models import ChatHistoryPayloadModel
|
||||||
|
|
||||||
@@ -20,19 +21,21 @@ class HistoryProcessors(BaseProcessor):
|
|||||||
backward = payload.get("backward", 0)
|
backward = payload.get("backward", 0)
|
||||||
from_time = payload.get("from", 0)
|
from_time = payload.get("from", 0)
|
||||||
getMessages = payload.get("getMessages", True)
|
getMessages = payload.get("getMessages", True)
|
||||||
|
getChat = payload.get("getChat", False)
|
||||||
messages = []
|
messages = []
|
||||||
|
|
||||||
# Если пользователь хочет получить историю из избранного,
|
# Если пользователь хочет получить историю из избранного,
|
||||||
# то выставляем в качестве ID чата его ID
|
# то выставляем в качестве ID чата отрицательный ID отправителя
|
||||||
if chatId == 0:
|
isFavourite = chatId == (senderId ^ senderId)
|
||||||
chatId = senderId
|
if isFavourite:
|
||||||
|
chatId = -senderId
|
||||||
|
|
||||||
# Проверяем, существует ли чат
|
# Проверяем, существует ли чат
|
||||||
async with self.db_pool.acquire() as conn:
|
async with self.db_pool.acquire() as conn:
|
||||||
async with conn.cursor() as cursor:
|
async with conn.cursor() as cursor:
|
||||||
# Проверяем состоит ли пользователь в чате,
|
# Проверяем состоит ли пользователь в чате,
|
||||||
# только в случае того, если это не избранное
|
# только в случае того, если это не избранное
|
||||||
if chatId != senderId:
|
if not isFavourite:
|
||||||
await cursor.execute("SELECT * FROM chats WHERE id = %s", (chatId,))
|
await cursor.execute("SELECT * FROM chats WHERE id = %s", (chatId,))
|
||||||
chat = await cursor.fetchone()
|
chat = await cursor.fetchone()
|
||||||
|
|
||||||
@@ -58,21 +61,8 @@ class HistoryProcessors(BaseProcessor):
|
|||||||
result = await cursor.fetchall()
|
result = await cursor.fetchall()
|
||||||
|
|
||||||
for row in result:
|
for row in result:
|
||||||
# TODO: Сборку тела сообщения нужно вынести в отдельную функцию
|
messages.append(self.tools.build_message_dict(row, self.type))
|
||||||
messages.append({
|
backward_count = len(result)
|
||||||
"id": row.get("id") if self.type == 'mobile' else str(row.get('id')),
|
|
||||||
"time": int(row.get("time")),
|
|
||||||
"type": row.get("type"),
|
|
||||||
"sender": row.get("sender"),
|
|
||||||
"cid": int(row.get("cid")),
|
|
||||||
"text": row.get("text"),
|
|
||||||
"attaches": json.loads(row.get("attaches")),
|
|
||||||
"elements": json.loads(row.get("elements")),
|
|
||||||
"reactionInfo": {},
|
|
||||||
"link": {},
|
|
||||||
#"options": 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
if forward > 0:
|
if forward > 0:
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"SELECT * FROM messages WHERE chat_id = %s AND time > %s ORDER BY time ASC LIMIT %s",
|
"SELECT * FROM messages WHERE chat_id = %s AND time > %s ORDER BY time ASC LIMIT %s",
|
||||||
@@ -82,28 +72,19 @@ class HistoryProcessors(BaseProcessor):
|
|||||||
result = await cursor.fetchall()
|
result = await cursor.fetchall()
|
||||||
|
|
||||||
for row in result:
|
for row in result:
|
||||||
messages.append({
|
messages.append(self.tools.build_message_dict(row, self.type))
|
||||||
"id": row.get("id") if self.type == 'mobile' else str(row.get('id')),
|
forward_count = len(result)
|
||||||
"time": int(row.get("time")),
|
|
||||||
"type": row.get("type"),
|
|
||||||
"sender": row.get("sender"),
|
|
||||||
"cid": int(row.get("cid")),
|
|
||||||
"text": row.get("text"),
|
|
||||||
"attaches": json.loads(row.get("attaches")),
|
|
||||||
"elements": json.loads(row.get("elements")),
|
|
||||||
"reactionInfo": {},
|
|
||||||
"link": {}
|
|
||||||
#"options": 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
# Сортируем сообщения по времени
|
# Сортируем сообщения по времени
|
||||||
messages.sort(key=lambda x: x["time"])
|
messages.sort(key=lambda x: x["time"])
|
||||||
|
|
||||||
# Формируем ответ
|
|
||||||
payload = {
|
payload = {
|
||||||
"messages": messages
|
"messages": messages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if getChat:
|
||||||
|
payload["chat"] = {}
|
||||||
|
|
||||||
# Собираем пакет
|
# Собираем пакет
|
||||||
packet = self.proto.pack_packet(
|
packet = self.proto.pack_packet(
|
||||||
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CHAT_HISTORY, payload=payload
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CHAT_HISTORY, payload=payload
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ class MainProcessors(BaseProcessor):
|
|||||||
|
|
||||||
# Аватарка с биографией
|
# Аватарка с биографией
|
||||||
photoId = None if not user.get("avatar_id") else int(user.get("avatar_id"))
|
photoId = None if not user.get("avatar_id") else int(user.get("avatar_id"))
|
||||||
avatar_url = None if not photoId else self.config.avatar_base_url + photoId
|
avatar_url = None if not photoId else self.config.avatar_base_url + str(photoId)
|
||||||
description = None if not user.get("description") else user.get("description")
|
description = None if not user.get("description") else user.get("description")
|
||||||
|
|
||||||
# Генерируем профиль
|
# Генерируем профиль
|
||||||
|
|||||||
@@ -88,10 +88,9 @@ class MessagesProcessors(BaseProcessor):
|
|||||||
chatId = userId ^ senderId
|
chatId = userId ^ senderId
|
||||||
|
|
||||||
# Если клиент хочет отправить сообщение в избранное,
|
# Если клиент хочет отправить сообщение в избранное,
|
||||||
# то выставляем в качестве ID чата ID отправителя
|
# то выставляем в качестве ID чата отрицательный ID отправителя
|
||||||
# (А ещё используем это, если клиент вообще ничего не указал)
|
if chatId == (senderId ^ senderId):
|
||||||
if chatId == 0 or not chatId:
|
chatId = -senderId
|
||||||
chatId = senderId
|
|
||||||
participants = [senderId]
|
participants = [senderId]
|
||||||
else:
|
else:
|
||||||
# Если все таки клиент хочет отправить сообщение в нормальный чат,
|
# Если все таки клиент хочет отправить сообщение в нормальный чат,
|
||||||
@@ -114,6 +113,14 @@ class MessagesProcessors(BaseProcessor):
|
|||||||
await self._send_error(seq, self.opcodes.MSG_SEND, self.error_types.CHAT_NOT_ACCESS, writer)
|
await self._send_error(seq, self.opcodes.MSG_SEND, self.error_types.CHAT_NOT_ACCESS, writer)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Проверяем блокировку собеседника
|
||||||
|
if chat.get("type") == "DIALOG":
|
||||||
|
contactid = [p for p in participants if p != int(senderId)][0]
|
||||||
|
# Проверяем, заблокировал ли отправитель собеседника
|
||||||
|
if await self.tools.contact_is_blocked(contactid, senderId, db_pool):
|
||||||
|
await self._send_error(seq, self.opcodes.MSG_SEND, self.error_types.CONTACT_BLOCKED, writer)
|
||||||
|
return
|
||||||
|
|
||||||
# Добавляем сообщение в историю
|
# Добавляем сообщение в историю
|
||||||
messageId, lastMessageId, messageTime = await self.tools.insert_message(
|
messageId, lastMessageId, messageTime = await self.tools.insert_message(
|
||||||
chatId=chatId,
|
chatId=chatId,
|
||||||
@@ -126,16 +133,21 @@ class MessagesProcessors(BaseProcessor):
|
|||||||
db_pool=self.db_pool
|
db_pool=self.db_pool
|
||||||
)
|
)
|
||||||
|
|
||||||
# Готовое тело сообщения
|
# Готовое тело сообщения. Поля cid / elements / reactionInfo / link
|
||||||
|
# должны присутствовать ВСЕГДА (даже пустые) — десктопный MAX
|
||||||
|
# ожидает фиксированную msgpack-схему и обрывает соединение
|
||||||
|
# при отсутствии любого из них (см. регрессию из 87cfc19).
|
||||||
bodyMessage = {
|
bodyMessage = {
|
||||||
"id": messageId,
|
"id": messageId if self.type == "mobile" else str(messageId),
|
||||||
|
"cid": int(cid or 0),
|
||||||
"time": messageTime,
|
"time": messageTime,
|
||||||
"type": "USER",
|
"type": "USER",
|
||||||
"sender": senderId,
|
"sender": senderId,
|
||||||
"cid": cid,
|
|
||||||
"text": text,
|
"text": text,
|
||||||
"attaches": attaches,
|
"attaches": attaches if isinstance(attaches, list) else [],
|
||||||
"elements": elements
|
"elements": elements if isinstance(elements, list) else [],
|
||||||
|
"reactionInfo": {},
|
||||||
|
"link": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Отправляем событие всем участникам чата
|
# Отправляем событие всем участникам чата
|
||||||
@@ -144,7 +156,7 @@ class MessagesProcessors(BaseProcessor):
|
|||||||
participant,
|
participant,
|
||||||
{
|
{
|
||||||
"eventType": "new_msg",
|
"eventType": "new_msg",
|
||||||
"chatId": 0 if chatId == senderId else chatId,
|
"chatId": 0 if chatId == -senderId else chatId,
|
||||||
"message": bodyMessage,
|
"message": bodyMessage,
|
||||||
"prevMessageId": lastMessageId,
|
"prevMessageId": lastMessageId,
|
||||||
"time": messageTime,
|
"time": messageTime,
|
||||||
@@ -154,7 +166,7 @@ class MessagesProcessors(BaseProcessor):
|
|||||||
|
|
||||||
# Данные пакета
|
# Данные пакета
|
||||||
payload = {
|
payload = {
|
||||||
"chatId": 0 if chatId == senderId else chatId,
|
"chatId": 0 if chatId == -senderId else chatId,
|
||||||
"message": bodyMessage,
|
"message": bodyMessage,
|
||||||
"unread": 0,
|
"unread": 0,
|
||||||
"mark": messageTime
|
"mark": messageTime
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class SearchProcessors(BaseProcessor):
|
|||||||
if user:
|
if user:
|
||||||
# Аватарка с биографией
|
# Аватарка с биографией
|
||||||
photoId = None if not user.get("avatar_id") else int(user.get("avatar_id"))
|
photoId = None if not user.get("avatar_id") else int(user.get("avatar_id"))
|
||||||
avatar_url = None if not photoId else self.config.avatar_base_url + photoId
|
avatar_url = None if not photoId else self.config.avatar_base_url + str(photoId)
|
||||||
description = None if not user.get("description") else user.get("description")
|
description = None if not user.get("description") else user.get("description")
|
||||||
|
|
||||||
# Получаем данные контакта
|
# Получаем данные контакта
|
||||||
@@ -129,7 +129,7 @@ class SearchProcessors(BaseProcessor):
|
|||||||
|
|
||||||
# Аватарка с биографией
|
# Аватарка с биографией
|
||||||
photoId = None if not user.get("avatar_id") else int(user.get("avatar_id"))
|
photoId = None if not user.get("avatar_id") else int(user.get("avatar_id"))
|
||||||
avatar_url = None if not photoId else self.config.avatar_base_url + photoId
|
avatar_url = None if not photoId else self.config.avatar_base_url + str(photoId)
|
||||||
description = None if not user.get("description") else user.get("description")
|
description = None if not user.get("description") else user.get("description")
|
||||||
|
|
||||||
# Получаем данные контакта
|
# Получаем данные контакта
|
||||||
@@ -223,12 +223,12 @@ class SearchProcessors(BaseProcessor):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Получаем последнее сообщение из чата
|
# Получаем последнее сообщение из избранного
|
||||||
message, messageTime = await self.tools.get_last_message(
|
message, messageTime = await self.tools.get_last_message(
|
||||||
senderId, self.db_pool, protocol_type=self.type
|
-senderId, self.db_pool, protocol_type=self.type
|
||||||
)
|
)
|
||||||
|
|
||||||
# ID избранного
|
# ID избранного для клиента
|
||||||
chatId = senderId ^ senderId
|
chatId = senderId ^ senderId
|
||||||
|
|
||||||
# Добавляем чат в список
|
# Добавляем чат в список
|
||||||
|
|||||||
@@ -168,6 +168,54 @@ class OnemeMobile:
|
|||||||
seq,
|
seq,
|
||||||
writer,
|
writer,
|
||||||
)
|
)
|
||||||
|
case self.opcodes.ASSETS_GET:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone,
|
||||||
|
self.processors.assets_get,
|
||||||
|
payload,
|
||||||
|
seq,
|
||||||
|
writer,
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_GET_BY_IDS:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone,
|
||||||
|
self.processors.assets_get_by_ids,
|
||||||
|
payload,
|
||||||
|
seq,
|
||||||
|
writer,
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_ADD:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone,
|
||||||
|
self.processors.assets_add,
|
||||||
|
payload,
|
||||||
|
seq,
|
||||||
|
writer,
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_REMOVE:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone,
|
||||||
|
self.processors.assets_remove,
|
||||||
|
payload,
|
||||||
|
seq,
|
||||||
|
writer,
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_MOVE:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone,
|
||||||
|
self.processors.assets_move,
|
||||||
|
payload,
|
||||||
|
seq,
|
||||||
|
writer,
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_LIST_MODIFY:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone,
|
||||||
|
self.processors.assets_list_modify,
|
||||||
|
payload,
|
||||||
|
seq,
|
||||||
|
writer,
|
||||||
|
)
|
||||||
case self.opcodes.VIDEO_CHAT_HISTORY:
|
case self.opcodes.VIDEO_CHAT_HISTORY:
|
||||||
await self.auth_required(
|
await self.auth_required(
|
||||||
userPhone,
|
userPhone,
|
||||||
@@ -311,6 +359,15 @@ class OnemeMobile:
|
|||||||
writer,
|
writer,
|
||||||
userId,
|
userId,
|
||||||
)
|
)
|
||||||
|
case self.opcodes.CONTACT_ADD_BY_PHONE:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone,
|
||||||
|
self.processors.contact_add_by_phone,
|
||||||
|
payload,
|
||||||
|
seq,
|
||||||
|
writer,
|
||||||
|
userId,
|
||||||
|
)
|
||||||
case self.opcodes.CONTACT_PRESENCE:
|
case self.opcodes.CONTACT_PRESENCE:
|
||||||
await self.auth_required(
|
await self.auth_required(
|
||||||
userPhone,
|
userPhone,
|
||||||
|
|||||||
@@ -151,6 +151,54 @@ class OnemeWS:
|
|||||||
seq,
|
seq,
|
||||||
websocket,
|
websocket,
|
||||||
)
|
)
|
||||||
|
case self.opcodes.ASSETS_GET:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone,
|
||||||
|
self.processors.assets_get,
|
||||||
|
payload,
|
||||||
|
seq,
|
||||||
|
websocket,
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_GET_BY_IDS:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone,
|
||||||
|
self.processors.assets_get_by_ids,
|
||||||
|
payload,
|
||||||
|
seq,
|
||||||
|
websocket,
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_ADD:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone,
|
||||||
|
self.processors.assets_add,
|
||||||
|
payload,
|
||||||
|
seq,
|
||||||
|
websocket,
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_REMOVE:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone,
|
||||||
|
self.processors.assets_remove,
|
||||||
|
payload,
|
||||||
|
seq,
|
||||||
|
websocket,
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_MOVE:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone,
|
||||||
|
self.processors.assets_move,
|
||||||
|
payload,
|
||||||
|
seq,
|
||||||
|
websocket,
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_LIST_MODIFY:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone,
|
||||||
|
self.processors.assets_list_modify,
|
||||||
|
payload,
|
||||||
|
seq,
|
||||||
|
websocket,
|
||||||
|
)
|
||||||
case self.opcodes.VIDEO_CHAT_HISTORY:
|
case self.opcodes.VIDEO_CHAT_HISTORY:
|
||||||
await self.auth_required(
|
await self.auth_required(
|
||||||
userPhone,
|
userPhone,
|
||||||
@@ -294,6 +342,15 @@ class OnemeWS:
|
|||||||
websocket,
|
websocket,
|
||||||
userId,
|
userId,
|
||||||
)
|
)
|
||||||
|
case self.opcodes.CONTACT_ADD_BY_PHONE:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone,
|
||||||
|
self.processors.contact_add_by_phone,
|
||||||
|
payload,
|
||||||
|
seq,
|
||||||
|
websocket,
|
||||||
|
userId,
|
||||||
|
)
|
||||||
case self.opcodes.CONTACT_PRESENCE:
|
case self.opcodes.CONTACT_PRESENCE:
|
||||||
await self.auth_required(
|
await self.auth_required(
|
||||||
userPhone,
|
userPhone,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class TTController(ControllerBase):
|
|||||||
|
|
||||||
# Выбираем протокол в зависимости от типа подключения
|
# Выбираем протокол в зависимости от типа подключения
|
||||||
proto = self.proto_web if is_web else self.proto_tcp
|
proto = self.proto_web if is_web else self.proto_tcp
|
||||||
|
packet = None
|
||||||
|
|
||||||
# Не отправляем событие самому себе
|
# Не отправляем событие самому себе
|
||||||
if writer == eventData.get("writer"):
|
if writer == eventData.get("writer"):
|
||||||
@@ -94,7 +95,9 @@ class TTController(ControllerBase):
|
|||||||
cmd=0, seq=1, opcode=self.opcodes.NOTIF_PRESENCE, payload=payload
|
cmd=0, seq=1, opcode=self.opcodes.NOTIF_PRESENCE, payload=payload
|
||||||
)
|
)
|
||||||
|
|
||||||
# Отправляем пакет
|
if not packet:
|
||||||
|
return
|
||||||
|
|
||||||
if is_web:
|
if is_web:
|
||||||
await writer.send(packet)
|
await writer.send(packet)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -54,6 +54,32 @@ class AssetsPayloadModel(pydantic.BaseModel):
|
|||||||
type: str = None
|
type: str = None
|
||||||
userId: int = None
|
userId: int = None
|
||||||
|
|
||||||
|
class AssetsGetPayloadModel(pydantic.BaseModel):
|
||||||
|
type: str
|
||||||
|
count: int = 100
|
||||||
|
query: str = None
|
||||||
|
|
||||||
|
class AssetsGetByIdsPayloadModel(pydantic.BaseModel):
|
||||||
|
type: str
|
||||||
|
ids: list
|
||||||
|
|
||||||
|
class AssetsAddPayloadModel(pydantic.BaseModel):
|
||||||
|
type: str
|
||||||
|
id: int = None
|
||||||
|
|
||||||
|
class AssetsRemovePayloadModel(pydantic.BaseModel):
|
||||||
|
type: str
|
||||||
|
ids: list
|
||||||
|
|
||||||
|
class AssetsMovePayloadModel(pydantic.BaseModel):
|
||||||
|
type: str
|
||||||
|
id: int
|
||||||
|
position: int
|
||||||
|
|
||||||
|
class AssetsListModifyPayloadModel(pydantic.BaseModel):
|
||||||
|
type: str
|
||||||
|
ids: list
|
||||||
|
|
||||||
class GetCallTokenPayloadModel(pydantic.BaseModel):
|
class GetCallTokenPayloadModel(pydantic.BaseModel):
|
||||||
userId: int
|
userId: int
|
||||||
value: str
|
value: str
|
||||||
@@ -76,7 +102,7 @@ class ContactPresencePayloadModel(pydantic.BaseModel):
|
|||||||
class ContactUpdatePayloadModel(pydantic.BaseModel):
|
class ContactUpdatePayloadModel(pydantic.BaseModel):
|
||||||
action: str
|
action: str
|
||||||
contactId: int
|
contactId: int
|
||||||
firstName: str
|
firstName: str = None
|
||||||
lastName: str = None
|
lastName: str = None
|
||||||
|
|
||||||
class TypingPayloadModel(pydantic.BaseModel):
|
class TypingPayloadModel(pydantic.BaseModel):
|
||||||
@@ -94,4 +120,20 @@ class MessageModel(pydantic.BaseModel):
|
|||||||
class SendMessagePayloadModel(pydantic.BaseModel):
|
class SendMessagePayloadModel(pydantic.BaseModel):
|
||||||
userId: int = None
|
userId: int = None
|
||||||
chatId: int = None
|
chatId: int = None
|
||||||
message: MessageModel
|
message: MessageModel
|
||||||
|
|
||||||
|
class AuthConfirmRegisterPayloadModel(pydantic.BaseModel):
|
||||||
|
token: str
|
||||||
|
name: str
|
||||||
|
tokenType: str
|
||||||
|
deviceType: str
|
||||||
|
deviceId: str = None
|
||||||
|
|
||||||
|
@pydantic.field_validator('name')
|
||||||
|
def validate_name(cls, v):
|
||||||
|
v = v.strip()
|
||||||
|
if not v:
|
||||||
|
raise ValueError('name must not be empty')
|
||||||
|
if len(v) > 59:
|
||||||
|
raise ValueError('name too long')
|
||||||
|
return v
|
||||||
|
|||||||
@@ -1,34 +1,167 @@
|
|||||||
import pydantic
|
import pydantic
|
||||||
import time
|
import time
|
||||||
from classes.baseprocessor import BaseProcessor
|
from classes.baseprocessor import BaseProcessor
|
||||||
from tamtam.models import AssetsPayloadModel
|
from tamtam.models import (
|
||||||
|
AssetsPayloadModel,
|
||||||
|
AssetsGetPayloadModel,
|
||||||
|
AssetsGetByIdsPayloadModel,
|
||||||
|
AssetsAddPayloadModel,
|
||||||
|
AssetsRemovePayloadModel,
|
||||||
|
AssetsMovePayloadModel,
|
||||||
|
AssetsListModifyPayloadModel,
|
||||||
|
)
|
||||||
|
|
||||||
class AssetsProcessors(BaseProcessor):
|
class AssetsProcessors(BaseProcessor):
|
||||||
async def assets_update(self, payload, seq, writer):
|
async def assets_update(self, payload, seq, writer):
|
||||||
"""Обработчик запроса ассетов клиента на сервере"""
|
|
||||||
# Валидируем данные пакета
|
|
||||||
try:
|
try:
|
||||||
AssetsPayloadModel.model_validate(payload)
|
AssetsPayloadModel.model_validate(payload)
|
||||||
except pydantic.ValidationError as error:
|
except pydantic.ValidationError as error:
|
||||||
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
await self._send_error(seq, self.opcodes.ASSETS_UPDATE, self.error_types.INVALID_PAYLOAD, writer)
|
await self._send_error(seq, self.opcodes.ASSETS_UPDATE, self.error_types.INVALID_PAYLOAD, writer)
|
||||||
return
|
return
|
||||||
|
|
||||||
# TODO: сейчас это заглушка, а попозже нужно сделать полноценную реализацию
|
|
||||||
|
|
||||||
# Данные пакета
|
response = {
|
||||||
payload = {
|
|
||||||
"sync": int(time.time() * 1000),
|
"sync": int(time.time() * 1000),
|
||||||
"stickerSetsUpdates": {},
|
"stickerSetsUpdates": {},
|
||||||
"stickersUpdates": {},
|
"stickersUpdates": {},
|
||||||
"sections": [],
|
"stickersOrder": [
|
||||||
"stickersOrder": []
|
"RECENT",
|
||||||
|
"FAVORITE_STICKERS",
|
||||||
|
"FAVORITE_STICKER_SETS",
|
||||||
|
"TOP",
|
||||||
|
"NEW",
|
||||||
|
"NEW_STICKER_SETS",
|
||||||
|
],
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"id": "RECENT",
|
||||||
|
"type": "RECENTS",
|
||||||
|
"recentsList": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "FAVORITE_STICKERS",
|
||||||
|
"type": "STICKERS",
|
||||||
|
"stickers": [],
|
||||||
|
"marker": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "FAVORITE_STICKER_SETS",
|
||||||
|
"type": "STICKER_SETS",
|
||||||
|
"stickerSets": [],
|
||||||
|
"marker": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "TOP",
|
||||||
|
"type": "STICKERS",
|
||||||
|
"stickers": [],
|
||||||
|
"marker": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "NEW",
|
||||||
|
"type": "STICKERS",
|
||||||
|
"stickers": [],
|
||||||
|
"marker": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "NEW_STICKER_SETS",
|
||||||
|
"type": "STICKER_SETS",
|
||||||
|
"stickerSets": [],
|
||||||
|
"marker": None,
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Собираем пакет
|
|
||||||
packet = self.proto.pack_packet(
|
packet = self.proto.pack_packet(
|
||||||
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_UPDATE, payload=payload
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_UPDATE, payload=response
|
||||||
)
|
)
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
# Отправляем
|
async def assets_get(self, payload, seq, writer):
|
||||||
await self._send(writer, packet)
|
try:
|
||||||
|
data = AssetsGetPayloadModel.model_validate(payload)
|
||||||
|
except pydantic.ValidationError as error:
|
||||||
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
|
await self._send_error(seq, self.opcodes.ASSETS_GET, self.error_types.INVALID_PAYLOAD, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
asset_type = data.type
|
||||||
|
if asset_type == "STICKER_SET":
|
||||||
|
response = {"stickerSets": [], "marker": None}
|
||||||
|
else:
|
||||||
|
response = {"stickers": [], "marker": None}
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_GET, payload=response
|
||||||
|
)
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
async def assets_get_by_ids(self, payload, seq, writer):
|
||||||
|
try:
|
||||||
|
data = AssetsGetByIdsPayloadModel.model_validate(payload)
|
||||||
|
except pydantic.ValidationError as error:
|
||||||
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
|
await self._send_error(seq, self.opcodes.ASSETS_GET_BY_IDS, self.error_types.INVALID_PAYLOAD, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
asset_type = data.type
|
||||||
|
if asset_type == "STICKER_SET":
|
||||||
|
response = {"stickerSets": []}
|
||||||
|
else:
|
||||||
|
response = {"stickers": []}
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_GET_BY_IDS, payload=response
|
||||||
|
)
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
async def assets_add(self, payload, seq, writer):
|
||||||
|
try:
|
||||||
|
AssetsAddPayloadModel.model_validate(payload)
|
||||||
|
except pydantic.ValidationError as error:
|
||||||
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
|
await self._send_error(seq, self.opcodes.ASSETS_ADD, self.error_types.INVALID_PAYLOAD, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_ADD, payload={}
|
||||||
|
)
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
async def assets_remove(self, payload, seq, writer):
|
||||||
|
try:
|
||||||
|
AssetsRemovePayloadModel.model_validate(payload)
|
||||||
|
except pydantic.ValidationError as error:
|
||||||
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
|
await self._send_error(seq, self.opcodes.ASSETS_REMOVE, self.error_types.INVALID_PAYLOAD, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_REMOVE, payload={}
|
||||||
|
)
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
async def assets_move(self, payload, seq, writer):
|
||||||
|
try:
|
||||||
|
AssetsMovePayloadModel.model_validate(payload)
|
||||||
|
except pydantic.ValidationError as error:
|
||||||
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
|
await self._send_error(seq, self.opcodes.ASSETS_MOVE, self.error_types.INVALID_PAYLOAD, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_MOVE, payload={}
|
||||||
|
)
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
async def assets_list_modify(self, payload, seq, writer):
|
||||||
|
try:
|
||||||
|
AssetsListModifyPayloadModel.model_validate(payload)
|
||||||
|
except pydantic.ValidationError as error:
|
||||||
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
|
await self._send_error(seq, self.opcodes.ASSETS_LIST_MODIFY, self.error_types.INVALID_PAYLOAD, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.ASSETS_LIST_MODIFY, payload={}
|
||||||
|
)
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import time
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from classes.baseprocessor import BaseProcessor
|
from classes.baseprocessor import BaseProcessor
|
||||||
|
from common.sms import send_sms_code
|
||||||
from tamtam.models import (
|
from tamtam.models import (
|
||||||
RequestCodePayloadModel,
|
RequestCodePayloadModel,
|
||||||
VerifyCodePayloadModel,
|
VerifyCodePayloadModel,
|
||||||
FinalAuthPayloadModel,
|
FinalAuthPayloadModel,
|
||||||
|
AuthConfirmRegisterPayloadModel,
|
||||||
LoginPayloadModel,
|
LoginPayloadModel,
|
||||||
)
|
)
|
||||||
from tamtam.config import TTConfig
|
from tamtam.config import TTConfig
|
||||||
@@ -17,6 +19,172 @@ class AuthProcessors(BaseProcessor):
|
|||||||
super().__init__(db_pool, clients, send_event, type)
|
super().__init__(db_pool, clients, send_event, type)
|
||||||
self.server_config = TTConfig().SERVER_CONFIG
|
self.server_config = TTConfig().SERVER_CONFIG
|
||||||
|
|
||||||
|
async def _finish_auth(self, payload, seq, writer, cursor, phone, hashed_token, hashed_login, account, deviceType, deviceName, ip, login):
|
||||||
|
"""Завершение существующего пользователя"""
|
||||||
|
# Валидируем данные пакета
|
||||||
|
try:
|
||||||
|
FinalAuthPayloadModel.model_validate(payload)
|
||||||
|
except Exception as e:
|
||||||
|
await self._send_error(seq, self.opcodes.AUTH_CONFIRM,
|
||||||
|
self.error_types.INVALID_PAYLOAD, writer)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Удаляем токен
|
||||||
|
await cursor.execute("DELETE FROM auth_tokens WHERE token_hash = %s", (hashed_token,))
|
||||||
|
|
||||||
|
# Создаем сессию
|
||||||
|
await cursor.execute(
|
||||||
|
"INSERT INTO tokens (phone, token_hash, device_type, device_name, location, time) VALUES (%s, %s, %s, %s, %s, %s)",
|
||||||
|
(
|
||||||
|
phone,
|
||||||
|
hashed_login,
|
||||||
|
deviceType,
|
||||||
|
deviceName,
|
||||||
|
self.tools.get_geo(
|
||||||
|
ip=ip, db_path=self.config.geo_db_path
|
||||||
|
),
|
||||||
|
int(time.time() * 1000)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Аватарка с биографией
|
||||||
|
photo_id = None if not account.get("avatar_id") else int(account.get("avatar_id"))
|
||||||
|
avatar_url = None if not photo_id else self.config.avatar_base_url + str(photo_id)
|
||||||
|
description = None if not account.get("description") else account.get("description")
|
||||||
|
|
||||||
|
# Собираем данные пакета
|
||||||
|
return {
|
||||||
|
"userToken": str(account.get("id")),
|
||||||
|
"profile": self.tools.generate_profile_tt(
|
||||||
|
id=account.get("id"),
|
||||||
|
phone=int(account.get("phone")),
|
||||||
|
avatarUrl=avatar_url,
|
||||||
|
photoId=photo_id,
|
||||||
|
updateTime=int(account.get("updatetime")),
|
||||||
|
firstName=account.get("firstname"),
|
||||||
|
lastName=account.get("lastname"),
|
||||||
|
options=json.loads(account.get("options")),
|
||||||
|
description=description,
|
||||||
|
username=account.get("username")
|
||||||
|
),
|
||||||
|
"tokenType": "LOGIN",
|
||||||
|
"token": login
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _finish_reg(self, payload, seq, writer, cursor, phone, hashed_token, hashed_login, deviceType, deviceName, ip, login):
|
||||||
|
"""Регистрация пользователя во время авторизации"""
|
||||||
|
# Валидируем данные пакета
|
||||||
|
try:
|
||||||
|
AuthConfirmRegisterPayloadModel.model_validate(payload)
|
||||||
|
except Exception as e:
|
||||||
|
await self._send_error(seq, self.opcodes.AUTH_CONFIRM,
|
||||||
|
self.error_types.INVALID_PAYLOAD, writer)
|
||||||
|
return None
|
||||||
|
|
||||||
|
name = payload.get("name", "").strip()
|
||||||
|
|
||||||
|
now_ms = int(time.time() * 1000)
|
||||||
|
now_s = int(time.time())
|
||||||
|
|
||||||
|
# Генерируем ID пользователя
|
||||||
|
user_id = await self.tools.generate_user_id(self.db_pool)
|
||||||
|
|
||||||
|
# Создаем пользователя
|
||||||
|
|
||||||
|
# NOTE: На бумаге у нас как бы полная поддержка ТТ (ну, все функции, в которые может макс),
|
||||||
|
# а клиенты тамтама не знают, что такое фамилия в аккаунтах тамтама (оно предназначено только для ОК)
|
||||||
|
# по этому просто не писать указывать фамилию в бд, ее клиент и так не отдаст
|
||||||
|
|
||||||
|
await cursor.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO users
|
||||||
|
(id, phone, telegram_id, firstname, lastname, username,
|
||||||
|
profileoptions, options, accountstatus, updatetime, lastseen)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
user_id,
|
||||||
|
phone,
|
||||||
|
None,
|
||||||
|
name,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
json.dumps([]),
|
||||||
|
json.dumps(["TT", "ONEME"]),
|
||||||
|
0,
|
||||||
|
str(now_ms),
|
||||||
|
str(now_s),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Добавляем данные аккаунта
|
||||||
|
await cursor.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO user_data
|
||||||
|
(phone, user_config, chat_config)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
phone,
|
||||||
|
json.dumps(self.static.USER_SETTINGS),
|
||||||
|
json.dumps({}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Добавляем дефолтную папку
|
||||||
|
await cursor.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO user_folders
|
||||||
|
(id, phone, title, sort_order)
|
||||||
|
VALUES ('all.chat.folder', %s, 'Все', 0)
|
||||||
|
""",
|
||||||
|
(phone,),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Удаляем токен
|
||||||
|
await cursor.execute("DELETE FROM auth_tokens WHERE token_hash = %s", (hashed_token,))
|
||||||
|
|
||||||
|
# Создаем сессию
|
||||||
|
await cursor.execute(
|
||||||
|
"INSERT INTO tokens (phone, token_hash, device_type, device_name, location, time) VALUES (%s, %s, %s, %s, %s, %s)",
|
||||||
|
(
|
||||||
|
phone,
|
||||||
|
hashed_login,
|
||||||
|
deviceType or "ANDROID",
|
||||||
|
deviceName or "Unknown",
|
||||||
|
self.tools.get_geo(
|
||||||
|
ip=ip, db_path=self.config.geo_db_path
|
||||||
|
),
|
||||||
|
now_ms,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Генерируем профиль
|
||||||
|
profile = self.tools.generate_profile_tt(
|
||||||
|
id=user_id,
|
||||||
|
phone=int(phone),
|
||||||
|
avatarUrl=None,
|
||||||
|
photoId=None,
|
||||||
|
updateTime=now_ms,
|
||||||
|
firstName=name,
|
||||||
|
lastName="",
|
||||||
|
options=["TT", "ONEME"],
|
||||||
|
description=None,
|
||||||
|
username=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.logger.info(
|
||||||
|
f"Новый пользователь зарегистрирован: phone={phone} id={user_id} name={name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Собираем данные пакета
|
||||||
|
return {
|
||||||
|
"userToken": "0",
|
||||||
|
"profile": profile,
|
||||||
|
"tokenType": "LOGIN",
|
||||||
|
"token": login,
|
||||||
|
}
|
||||||
|
|
||||||
async def auth_request(self, payload, seq, writer):
|
async def auth_request(self, payload, seq, writer):
|
||||||
"""Обработчик запроса кода"""
|
"""Обработчик запроса кода"""
|
||||||
# Валидируем данные пакета
|
# Валидируем данные пакета
|
||||||
@@ -30,29 +198,51 @@ class AuthProcessors(BaseProcessor):
|
|||||||
# Извлекаем телефон из пакета
|
# Извлекаем телефон из пакета
|
||||||
phone = re.sub(r'\D', '', payload.get("phone", ""))
|
phone = re.sub(r'\D', '', payload.get("phone", ""))
|
||||||
|
|
||||||
# Генерируем токен с кодом
|
# Генерируем токен
|
||||||
code = f"{secrets.randbelow(1_000_000):06d}"
|
|
||||||
token = secrets.token_urlsafe(128)
|
token = secrets.token_urlsafe(128)
|
||||||
|
|
||||||
# Хешируем
|
|
||||||
code_hash = hashlib.sha256(code.encode()).hexdigest()
|
|
||||||
token_hash = hashlib.sha256(token.encode()).hexdigest()
|
token_hash = hashlib.sha256(token.encode()).hexdigest()
|
||||||
|
|
||||||
# Срок жизни токена (5 минут)
|
# Срок жизни токена (5 минут)
|
||||||
expires = int(time.time()) + 300
|
expires = int(time.time()) + 300
|
||||||
|
|
||||||
# Ищем пользователя, и если он существует, сохраняем токен
|
user_exists = False
|
||||||
|
|
||||||
|
# Ищем пользователя
|
||||||
async with self.db_pool.acquire() as conn:
|
async with self.db_pool.acquire() as conn:
|
||||||
async with conn.cursor() as cursor:
|
async with conn.cursor() as cursor:
|
||||||
await cursor.execute("SELECT * FROM users WHERE phone = %s", (phone,))
|
await cursor.execute("SELECT * FROM users WHERE phone = %s", (phone,))
|
||||||
user = await cursor.fetchone()
|
user = await cursor.fetchone()
|
||||||
|
|
||||||
# Если пользователь существует, сохраняем токен
|
# Получаем код через SMS шлюз или генерируем локально
|
||||||
|
local_fallback_code = False
|
||||||
|
if self.config.sms_gateway_url:
|
||||||
|
code = await send_sms_code(self.config.sms_gateway_url, phone)
|
||||||
|
|
||||||
|
if code is None:
|
||||||
|
code = f"{secrets.randbelow(1_000_000):06d}"
|
||||||
|
local_fallback_code = True
|
||||||
|
else:
|
||||||
|
code = f"{secrets.randbelow(1_000_000):06d}"
|
||||||
|
local_fallback_code = True
|
||||||
|
|
||||||
|
# Хешируем
|
||||||
|
code_hash = hashlib.sha256(code.encode()).hexdigest()
|
||||||
|
|
||||||
|
# Сохраняем токен
|
||||||
|
async with self.db_pool.acquire() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
if user:
|
if user:
|
||||||
|
user_exists = True
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"INSERT INTO auth_tokens (phone, token_hash, code_hash, expires, state) VALUES (%s, %s, %s, %s, %s)",
|
"INSERT INTO auth_tokens (phone, token_hash, code_hash, expires, state) VALUES (%s, %s, %s, %s, %s)",
|
||||||
(phone, token_hash, code_hash, expires, "started")
|
(phone, token_hash, code_hash, expires, "started")
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
# Пользователь не найден - сохраняем токен в register
|
||||||
|
await cursor.execute(
|
||||||
|
"INSERT INTO auth_tokens (phone, token_hash, code_hash, expires, state) VALUES (%s, %s, %s, %s, %s)",
|
||||||
|
(phone, token_hash, code_hash, expires, "register")
|
||||||
|
)
|
||||||
|
|
||||||
# Данные пакета
|
# Данные пакета
|
||||||
payload = {
|
payload = {
|
||||||
@@ -71,7 +261,7 @@ class AuthProcessors(BaseProcessor):
|
|||||||
|
|
||||||
# Отправляем
|
# Отправляем
|
||||||
await self._send(writer, packet)
|
await self._send(writer, packet)
|
||||||
self.logger.debug(f"Код для {phone}: {code}")
|
self.logger.debug(f"Код для {phone}: {code} (существующий={user_exists})")
|
||||||
|
|
||||||
async def auth(self, payload, seq, writer):
|
async def auth(self, payload, seq, writer):
|
||||||
"""Обработчик проверки кода"""
|
"""Обработчик проверки кода"""
|
||||||
@@ -112,13 +302,32 @@ class AuthProcessors(BaseProcessor):
|
|||||||
self.error_types.INVALID_CODE, writer)
|
self.error_types.INVALID_CODE, writer)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Если это новый пользователь - переводим токен в verified
|
||||||
|
# и отдаём клиенту NEW токен, чтобы он показал экран ввода имени
|
||||||
|
if stored_token.get("state") == "register":
|
||||||
|
await cursor.execute(
|
||||||
|
"UPDATE auth_tokens SET state = %s WHERE token_hash = %s",
|
||||||
|
("verified", hashed_token)
|
||||||
|
)
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK,
|
||||||
|
seq=seq,
|
||||||
|
opcode=self.opcodes.AUTH,
|
||||||
|
payload={
|
||||||
|
"tokenAttrs": {"NEW": {"token": token}},
|
||||||
|
"tokenTypes": {"NEW": token},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await self._send(writer, packet)
|
||||||
|
return
|
||||||
|
|
||||||
# Ищем аккаунт
|
# Ищем аккаунт
|
||||||
await cursor.execute("SELECT * FROM users WHERE phone = %s", (stored_token.get("phone"),))
|
await cursor.execute("SELECT * FROM users WHERE phone = %s", (stored_token.get("phone"),))
|
||||||
account = await cursor.fetchone()
|
account = await cursor.fetchone()
|
||||||
|
|
||||||
# Обновляем состояние токена
|
# Обновляем состояние токена
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"UPDATE auth_tokens set state = %s WHERE token_hash = %s",
|
"UPDATE auth_tokens SET state = %s WHERE token_hash = %s",
|
||||||
("verified", hashed_token)
|
("verified", hashed_token)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -159,15 +368,7 @@ class AuthProcessors(BaseProcessor):
|
|||||||
await self._send(writer, packet)
|
await self._send(writer, packet)
|
||||||
|
|
||||||
async def auth_confirm(self, payload, seq, writer, deviceType, deviceName, ip):
|
async def auth_confirm(self, payload, seq, writer, deviceType, deviceName, ip):
|
||||||
"""Обработчик финальной аутентификации"""
|
"""Обработчик финальной аутентификации / регистрации"""
|
||||||
# Валидируем данные пакета
|
|
||||||
try:
|
|
||||||
FinalAuthPayloadModel.model_validate(payload)
|
|
||||||
except Exception as e:
|
|
||||||
await self._send_error(seq, self.opcodes.AUTH_CONFIRM,
|
|
||||||
self.error_types.INVALID_PAYLOAD, writer)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Извлекаем данные из пакета
|
# Извлекаем данные из пакета
|
||||||
token = payload.get("token")
|
token = payload.get("token")
|
||||||
|
|
||||||
@@ -184,10 +385,9 @@ class AuthProcessors(BaseProcessor):
|
|||||||
login = secrets.token_urlsafe(128)
|
login = secrets.token_urlsafe(128)
|
||||||
hashed_login = hashlib.sha256(login.encode()).hexdigest()
|
hashed_login = hashlib.sha256(login.encode()).hexdigest()
|
||||||
|
|
||||||
# Ищем токен с кодом
|
# Ищем токен
|
||||||
async with self.db_pool.acquire() as conn:
|
async with self.db_pool.acquire() as conn:
|
||||||
async with conn.cursor() as cursor:
|
async with conn.cursor() as cursor:
|
||||||
# Ищем токен
|
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"SELECT * FROM auth_tokens WHERE token_hash = %s AND expires > UNIX_TIMESTAMP()",
|
"SELECT * FROM auth_tokens WHERE token_hash = %s AND expires > UNIX_TIMESTAMP()",
|
||||||
(hashed_token,)
|
(hashed_token,)
|
||||||
@@ -199,63 +399,36 @@ class AuthProcessors(BaseProcessor):
|
|||||||
self.error_types.INVALID_TOKEN, writer)
|
self.error_types.INVALID_TOKEN, writer)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Если авторизация только началась - отдаем ошибку
|
# Если авторизация только началась (код ещё не проверен) - отдаем ошибку
|
||||||
if stored_token.get("state") == "started":
|
if stored_token.get("state") == "started" or stored_token.get("state") == "register":
|
||||||
await self._send_error(seq, self.opcodes.AUTH_CONFIRM,
|
await self._send_error(seq, self.opcodes.AUTH_CONFIRM,
|
||||||
self.error_types.INVALID_TOKEN, writer)
|
self.error_types.INVALID_TOKEN, writer)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Ищем аккаунт
|
phone = stored_token.get("phone")
|
||||||
await cursor.execute("SELECT * FROM users WHERE phone = %s", (stored_token.get("phone"),))
|
|
||||||
|
# Проверяем, существует ли пользователь
|
||||||
|
await cursor.execute("SELECT * FROM users WHERE phone = %s", (phone,))
|
||||||
account = await cursor.fetchone()
|
account = await cursor.fetchone()
|
||||||
|
|
||||||
# Удаляем токен
|
# Если пользователь есть, производим создание сессии
|
||||||
await cursor.execute("DELETE FROM auth_tokens WHERE token_hash = %s", (hashed_token,))
|
if account:
|
||||||
|
resp_payload = await self._finish_auth(
|
||||||
# Создаем сессию
|
payload, seq, writer, cursor, phone, hashed_token,
|
||||||
await cursor.execute(
|
hashed_login, account, deviceType, deviceName, ip, login
|
||||||
"INSERT INTO tokens (phone, token_hash, device_type, device_name, location, time) VALUES (%s, %s, %s, %s, %s, %s)",
|
)
|
||||||
(
|
else: # в ином случае производим регистрацию
|
||||||
stored_token.get("phone"),
|
resp_payload = await self._finish_reg(
|
||||||
hashed_login,
|
payload, seq, writer, cursor, phone, hashed_token,
|
||||||
deviceType,
|
hashed_login, deviceType, deviceName, ip, login
|
||||||
deviceName,
|
|
||||||
self.tools.get_geo(
|
|
||||||
ip=ip, db_path=self.config.geo_db_path
|
|
||||||
),
|
|
||||||
int(time.time() * 1000)
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
# Аватарка с биографией
|
if resp_payload is None:
|
||||||
photo_id = None if not account.get("avatar_id") else int(account.get("avatar_id"))
|
return
|
||||||
avatar_url = None if not photo_id else self.config.avatar_base_url + str(photo_id)
|
|
||||||
description = None if not account.get("description") else account.get("description")
|
|
||||||
|
|
||||||
# Собираем данные пакета
|
|
||||||
payload = {
|
|
||||||
# Я хз че сюда вставлять)
|
|
||||||
# ребята из одноклассников, может быть вы подскажете?
|
|
||||||
"userToken": str(account.get("id")),
|
|
||||||
"profile": self.tools.generate_profile_tt(
|
|
||||||
id=account.get("id"),
|
|
||||||
phone=int(account.get("phone")),
|
|
||||||
avatarUrl=avatar_url,
|
|
||||||
photoId=photo_id,
|
|
||||||
updateTime=int(account.get("updatetime")),
|
|
||||||
firstName=account.get("firstname"),
|
|
||||||
lastName=account.get("lastname"),
|
|
||||||
options=json.loads(account.get("options")),
|
|
||||||
description=description,
|
|
||||||
username=account.get("username")
|
|
||||||
),
|
|
||||||
"tokenType": "LOGIN",
|
|
||||||
"token": login
|
|
||||||
}
|
|
||||||
|
|
||||||
# Создаем пакет
|
# Создаем пакет
|
||||||
packet = self.proto.pack_packet(
|
packet = self.proto.pack_packet(
|
||||||
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.AUTH_CONFIRM, payload=payload
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.AUTH_CONFIRM, payload=resp_payload
|
||||||
)
|
)
|
||||||
|
|
||||||
# Отправляем
|
# Отправляем
|
||||||
@@ -270,7 +443,7 @@ class AuthProcessors(BaseProcessor):
|
|||||||
self.logger.error(f"Возникли ошибки при валидации пакета: {e}")
|
self.logger.error(f"Возникли ошибки при валидации пакета: {e}")
|
||||||
await self._send_error(seq, self.opcodes.LOGIN,
|
await self._send_error(seq, self.opcodes.LOGIN,
|
||||||
self.error_types.INVALID_PAYLOAD, writer)
|
self.error_types.INVALID_PAYLOAD, writer)
|
||||||
return
|
return None, None, None
|
||||||
|
|
||||||
# Чаты, где состоит пользователь
|
# Чаты, где состоит пользователь
|
||||||
chats = []
|
chats = []
|
||||||
@@ -291,7 +464,7 @@ class AuthProcessors(BaseProcessor):
|
|||||||
if token_data is None:
|
if token_data is None:
|
||||||
await self._send_error(seq, self.opcodes.LOGIN,
|
await self._send_error(seq, self.opcodes.LOGIN,
|
||||||
self.error_types.INVALID_TOKEN, writer)
|
self.error_types.INVALID_TOKEN, writer)
|
||||||
return
|
return None, None, None
|
||||||
|
|
||||||
# Ищем аккаунт пользователя в бд
|
# Ищем аккаунт пользователя в бд
|
||||||
await cursor.execute("SELECT * FROM users WHERE phone = %s", (token_data.get("phone"),))
|
await cursor.execute("SELECT * FROM users WHERE phone = %s", (token_data.get("phone"),))
|
||||||
@@ -303,8 +476,8 @@ class AuthProcessors(BaseProcessor):
|
|||||||
|
|
||||||
# Ищем все чаты, где состоит пользователь
|
# Ищем все чаты, где состоит пользователь
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"SELECT * FROM chat_participants WHERE user_id = %s",
|
"SELECT * FROM chat_participants WHERE user_id = %s",
|
||||||
(user.get('id'))
|
(user.get('id'),)
|
||||||
)
|
)
|
||||||
user_chats = await cursor.fetchall()
|
user_chats = await cursor.fetchall()
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ class ContactsProcessors(BaseProcessor):
|
|||||||
)
|
)
|
||||||
# а если уже существует, отправляем ошибку
|
# а если уже существует, отправляем ошибку
|
||||||
else:
|
else:
|
||||||
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_ALREADY_EXISTS, writer)
|
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_ALREADY_ADDED, writer)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Генерируем профиль
|
# Генерируем профиль
|
||||||
@@ -161,6 +161,119 @@ class ContactsProcessors(BaseProcessor):
|
|||||||
|
|
||||||
await self._send(writer, packet)
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
elif action == "BLOCK":
|
||||||
|
async with self.db_pool.acquire() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
# Проверяем, существует ли контакт
|
||||||
|
await cursor.execute(
|
||||||
|
"SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s",
|
||||||
|
(userId, contactId)
|
||||||
|
)
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
|
||||||
|
# Обновляем существующий контакт, если такой есть
|
||||||
|
if row:
|
||||||
|
await cursor.execute(
|
||||||
|
"UPDATE contacts SET is_blocked = TRUE WHERE owner_id = %s AND contact_id = %s",
|
||||||
|
(userId, contactId)
|
||||||
|
)
|
||||||
|
else: # В ином случае добавляем новую запись в бд
|
||||||
|
await cursor.execute("SELECT * FROM users WHERE id = %s", (contactId,))
|
||||||
|
user = await cursor.fetchone()
|
||||||
|
if not user:
|
||||||
|
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.USER_NOT_FOUND, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
await cursor.execute(
|
||||||
|
"INSERT INTO contacts (owner_id, contact_id, custom_firstname, custom_lastname, is_blocked) VALUES (%s, %s, %s, %s, TRUE)",
|
||||||
|
(userId, contactId, firstName, lastName)
|
||||||
|
)
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_UPDATE, payload=None
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
elif action == "UNBLOCK":
|
||||||
|
# Разблокируем контакт
|
||||||
|
async with self.db_pool.acquire() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
# Проверяем, существует ли контакт
|
||||||
|
await cursor.execute(
|
||||||
|
"SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s",
|
||||||
|
(userId, contactId)
|
||||||
|
)
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
|
||||||
|
# Обновляем контакт, если он есть
|
||||||
|
if row:
|
||||||
|
await cursor.execute(
|
||||||
|
"UPDATE contacts SET is_blocked = FALSE WHERE owner_id = %s AND contact_id = %s",
|
||||||
|
(userId, contactId)
|
||||||
|
)
|
||||||
|
else: # В ином случае отправляем ошибку
|
||||||
|
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_NOT_FOUND, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_UPDATE, payload=None
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
|
elif action == "UPDATE":
|
||||||
|
async with self.db_pool.acquire() as conn:
|
||||||
|
async with conn.cursor() as cursor:
|
||||||
|
# Проверяем, существует ли контакт
|
||||||
|
await cursor.execute(
|
||||||
|
"SELECT * FROM contacts WHERE owner_id = %s AND contact_id = %s",
|
||||||
|
(userId, contactId)
|
||||||
|
)
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
|
||||||
|
# Если контакта нет, отдаем ошибку
|
||||||
|
if not row:
|
||||||
|
await self._send_error(seq, self.opcodes.CONTACT_UPDATE, self.error_types.CONTACT_NOT_FOUND, writer)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Обновляем контакт
|
||||||
|
await cursor.execute(
|
||||||
|
"UPDATE contacts SET custom_firstname = %s, custom_lastname = %s WHERE owner_id = %s AND contact_id = %s",
|
||||||
|
(firstName, lastName, userId, contactId)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Получаем данные пользователя
|
||||||
|
await cursor.execute("SELECT * FROM users WHERE id = %s", (contactId,))
|
||||||
|
user = await cursor.fetchone()
|
||||||
|
|
||||||
|
# Генерируем профиль
|
||||||
|
photo_id = None if not user.get("avatar_id") else int(user.get("avatar_id"))
|
||||||
|
avatar_url = None if not photo_id else self.config.avatar_base_url + str(photo_id)
|
||||||
|
|
||||||
|
contact = self.tools.generate_profile_tt(
|
||||||
|
id=user.get("id"),
|
||||||
|
phone=int(user.get("phone")),
|
||||||
|
avatarUrl=avatar_url,
|
||||||
|
photoId=photo_id,
|
||||||
|
updateTime=int(user.get("updatetime")),
|
||||||
|
firstName=user.get("firstname"),
|
||||||
|
lastName=user.get("lastname"),
|
||||||
|
options=json.loads(user.get("options")),
|
||||||
|
description=user.get("description"),
|
||||||
|
username=user.get("username")
|
||||||
|
),
|
||||||
|
|
||||||
|
response_payload = {
|
||||||
|
"contact": contact
|
||||||
|
}
|
||||||
|
|
||||||
|
packet = self.proto.pack_packet(
|
||||||
|
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.CONTACT_UPDATE, payload=response_payload
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._send(writer, packet)
|
||||||
|
|
||||||
async def contact_presence(self, payload, seq, writer):
|
async def contact_presence(self, payload, seq, writer):
|
||||||
"""Обработчик получения статуса контактов"""
|
"""Обработчик получения статуса контактов"""
|
||||||
# Валидируем данные пакета
|
# Валидируем данные пакета
|
||||||
|
|||||||
@@ -105,6 +105,14 @@ class MessagesProcessors(BaseProcessor):
|
|||||||
await self._send_error(seq, self.opcodes.MSG_SEND, self.error_types.CHAT_NOT_ACCESS, writer)
|
await self._send_error(seq, self.opcodes.MSG_SEND, self.error_types.CHAT_NOT_ACCESS, writer)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Проверяем блокировку собеседника
|
||||||
|
if chat.get("type") == "DIALOG":
|
||||||
|
contactid = [p for p in participants if p != int(senderId)][0]
|
||||||
|
# Проверяем, заблокировал ли отправитель собеседника
|
||||||
|
if await self.tools.contact_is_blocked(contactid, senderId, db_pool):
|
||||||
|
await self._send_error(seq, self.opcodes.MSG_SEND, self.error_types.CONTACT_BLOCKED, writer)
|
||||||
|
return
|
||||||
|
|
||||||
# Добавляем сообщение в историю
|
# Добавляем сообщение в историю
|
||||||
messageId, lastMessageId, messageTime = await self.tools.insert_message(
|
messageId, lastMessageId, messageTime = await self.tools.insert_message(
|
||||||
chatId=chatId,
|
chatId=chatId,
|
||||||
@@ -135,7 +143,7 @@ class MessagesProcessors(BaseProcessor):
|
|||||||
participant,
|
participant,
|
||||||
{
|
{
|
||||||
"eventType": "new_msg",
|
"eventType": "new_msg",
|
||||||
"chatId": 0 if chatId == senderId else chatId,
|
"chatId": chatId,
|
||||||
"message": bodyMessage,
|
"message": bodyMessage,
|
||||||
"prevMessageId": lastMessageId,
|
"prevMessageId": lastMessageId,
|
||||||
"time": messageTime,
|
"time": messageTime,
|
||||||
@@ -145,7 +153,7 @@ class MessagesProcessors(BaseProcessor):
|
|||||||
|
|
||||||
# Данные пакета
|
# Данные пакета
|
||||||
payload = {
|
payload = {
|
||||||
"chatId": 0 if chatId == senderId else chatId,
|
"chatId": chatId,
|
||||||
"message": bodyMessage,
|
"message": bodyMessage,
|
||||||
"unread": 0,
|
"unread": 0,
|
||||||
"mark": messageTime
|
"mark": messageTime
|
||||||
|
|||||||
@@ -122,6 +122,30 @@ class TamTamMobile:
|
|||||||
await self.auth_required(
|
await self.auth_required(
|
||||||
userPhone, self.processors.assets_update, payload, seq, writer
|
userPhone, self.processors.assets_update, payload, seq, writer
|
||||||
)
|
)
|
||||||
|
case self.opcodes.ASSETS_GET:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone, self.processors.assets_get, payload, seq, writer
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_GET_BY_IDS:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone, self.processors.assets_get_by_ids, payload, seq, writer
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_ADD:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone, self.processors.assets_add, payload, seq, writer
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_REMOVE:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone, self.processors.assets_remove, payload, seq, writer
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_MOVE:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone, self.processors.assets_move, payload, seq, writer
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_LIST_MODIFY:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone, self.processors.assets_list_modify, payload, seq, writer
|
||||||
|
)
|
||||||
case self.opcodes.VIDEO_CHAT_HISTORY:
|
case self.opcodes.VIDEO_CHAT_HISTORY:
|
||||||
await self.auth_required(
|
await self.auth_required(
|
||||||
userPhone, self.processors.video_chat_history, payload, seq, writer
|
userPhone, self.processors.video_chat_history, payload, seq, writer
|
||||||
|
|||||||
@@ -109,6 +109,30 @@ class TamTamWS:
|
|||||||
await self.auth_required(
|
await self.auth_required(
|
||||||
userPhone, self.processors.assets_update, payload, seq, websocket
|
userPhone, self.processors.assets_update, payload, seq, websocket
|
||||||
)
|
)
|
||||||
|
case self.opcodes.ASSETS_GET:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone, self.processors.assets_get, payload, seq, websocket
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_GET_BY_IDS:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone, self.processors.assets_get_by_ids, payload, seq, websocket
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_ADD:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone, self.processors.assets_add, payload, seq, websocket
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_REMOVE:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone, self.processors.assets_remove, payload, seq, websocket
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_MOVE:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone, self.processors.assets_move, payload, seq, websocket
|
||||||
|
)
|
||||||
|
case self.opcodes.ASSETS_LIST_MODIFY:
|
||||||
|
await self.auth_required(
|
||||||
|
userPhone, self.processors.assets_list_modify, payload, seq, websocket
|
||||||
|
)
|
||||||
case self.opcodes.VIDEO_CHAT_HISTORY:
|
case self.opcodes.VIDEO_CHAT_HISTORY:
|
||||||
await self.auth_required(
|
await self.auth_required(
|
||||||
userPhone, self.processors.video_chat_history, payload, seq, websocket
|
userPhone, self.processors.video_chat_history, payload, seq, websocket
|
||||||
|
|||||||
@@ -88,10 +88,14 @@ class TelegramBot:
|
|||||||
username = (message.from_user.username or f"user{int(time.time() * 1000)}")[:60]
|
username = (message.from_user.username or f"user{int(time.time() * 1000)}")[:60]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Генерируем ID пользователя
|
||||||
|
user_id = await self.tools.generate_user_id(self.db_pool)
|
||||||
|
|
||||||
# Создаем юзера
|
# Создаем юзера
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
self.sql_queries.INSERT_USER,
|
self.sql_queries.INSERT_USER,
|
||||||
(
|
(
|
||||||
|
user_id, # id
|
||||||
new_phone, # phone
|
new_phone, # phone
|
||||||
tg_id, # telegram_id
|
tg_id, # telegram_id
|
||||||
firstname, # firstname
|
firstname, # firstname
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
CREATE TABLE `users` (
|
CREATE TABLE `users` (
|
||||||
`id` INT NOT NULL AUTO_INCREMENT,
|
`id` INT NOT NULL,
|
||||||
`phone` VARCHAR(20) UNIQUE,
|
`phone` VARCHAR(20) UNIQUE,
|
||||||
`telegram_id` VARCHAR(64) UNIQUE,
|
`telegram_id` VARCHAR(64),
|
||||||
`firstname` VARCHAR(59) NOT NULL,
|
`firstname` VARCHAR(59) NOT NULL,
|
||||||
`lastname` VARCHAR(59),
|
`lastname` VARCHAR(59),
|
||||||
`description` VARCHAR(400),
|
`description` VARCHAR(400),
|
||||||
@@ -51,7 +51,7 @@ CREATE TABLE `chats` (
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE `messages` (
|
CREATE TABLE `messages` (
|
||||||
`id` INT NOT NULL AUTO_INCREMENT,
|
`id` BIGINT NOT NULL,
|
||||||
`chat_id` INT NOT NULL,
|
`chat_id` INT NOT NULL,
|
||||||
`sender` INT NOT NULL,
|
`sender` INT NOT NULL,
|
||||||
`time` VARCHAR(32) NOT NULL,
|
`time` VARCHAR(32) NOT NULL,
|
||||||
|
|||||||
Reference in New Issue
Block a user