Compare commits

..

15 Commits

Author SHA1 Message Date
Inception Time 880275eacb Изменение политики патчей 2026-07-10 04:27:50 +03:00
Alexey Polyakov fc6ec5eec5 W.I.P.: OpenMAX 0.1.0 (#28)
* Различные фиксы (#14)

* Исключаем только ошибку валидации

* Небольшие правки вебсокета тамтама

* Теперь номера брутить чутка сложнее

* Авторизация теперь проверяется для некоторых команд

* Теперь проверяется orign у вебсокета тамтама

* Дополнил пример env

* Починил немного сокет тамтама

* Теперь все должно работать исправно

* Документация: описал про SESSION_INIT

* Update deviceId description in tamtam_ws.md

* feat: 16 опкод для обновления профиля

* feat: 23 опкод для регистрации, смс шлюз, докер

* SQLite: Ошибка

И вообще не юзайте sqlite пожалуйста, он баганный

* Серверный конфиг снова сломался

* Починил изменение профиля

* История сообщений (вроде как даже работает)

* Дополнил немного 48 опкод, и починил отдачу истории в избранном (избранное все равно не работает)

* Доделал авторизацию в ТамТаме

* Швырнул архитектуру, чтобы позже объединить контроллеры веба и сокета в одно, а также разделить процессоры

* Удалил tamtam.proto, поскольку нигде больше не используется

* Поделил процессоры в таме

* Коды стран

* Генерируем айди, за вместо того, чтобы писать его попорядку

* Вынес список участников чата в отдельную таблицу

* Теперь мы не должны отправлять 128 опкод самому себе. вроде

* ТамТам: реализация 32 опкода. Надеюсь, будет работать

* ТамТам: обработчик 5 и 1 опкода

* ТамТам: починил 32 опкод и добавил серверный конфиг. Избранное больше не добавляется в список чатов
MAX: починил 32 опкод

* TT: oops

* MAX: заглушка в 158 опкоде

* Common: поправил 1 прикол

* TamTam: websocket transport for web version

* TamTam && MAX: история (в мохе она вроде теперь получше работает)

* Правка контроллеров небольшая

* MAX: Разделил процессоры

* TG Bot: переписал на aiogram

* Common: фикс года

* Начальная реализация транспорта ws для max web и прочие улучшения

* Common: добавил сжатие lz4

* Исправление конфликтов

* TT: фикс вебсокета

* Fix OpenMAX mobile compatibility and Telegram auth fallback (#30)

* Fix OpenMAX mobile compatibility and Telegram auth fallback

* Common: Убрал скобку в конфиге

---------

Co-authored-by: Alexey Polyakov <starwear3000@mail.ru>

* MAX: заглушка для баннеров, правка пакета со списком жалоб, отдача контактов и прочие улучшения

* oops

* MAX: Рабочие баннеры

* MAX: обновление настроек приватности

* MAX: пуши через firebase (особо не тестил, вроде работает)

* MAX: Рефактор папок

* MAX: фикс версий < 25.8.0

* MAX: отдача черного списка

* Теперь ID пользователей идут по порядку, как и раньше

* MAX: создание папок

* MAX: oops

* MAX: добавление/удаление контактов, статусы

* MAX & TT: теперь в качестве страны локации используется настоящая страна пользователя, а также зафиксировал версии библиотек в зависимостях

* TT: ну вроде шире поддержка, а вообще обратная совместимость с максом клас

* oops

* TG Bot: возможность включения белого списка

* Решение проблемы обработки sigterm

* nothing

* fix

* TG Bot: fix

* TG Bot: fix username

* MAX && TT: общение в таме, и корректировки под веб морду

* MAX: Почистил серверный конфиг

* nothing

* MAX: web fix

* MAX: web fix 2

* oops

* nothing

* MAX: теперь для избранного не сравниваем айди с нулём

* Вынес sqlite в отдельный модуль

* MAX: блокировка и разблокировка контакта

* oops

* TT: тоже самое что в макс

* MAX && TT: обновление контакта

* MAX: добавление контакта по номеру телефона

* MAX: исправление уязвимости с избранными

* Генерируем айди пользователей рандомно (опять, да)

* update sql scheme

* Фикс сборки избранного

* Попытка починить историю (спойлер, нихуя не получилось)

* MAX: история таки заработала — cid/link/reactionInfo обязательны в схеме

Десктопный MAX подключается через TCP (mobile-протокол) и парсит
msgpack по фиксированной схеме. Если в сообщении выпадает любое из
полей — клиент молча обрывает соединение. После 87cfc19 как раз
такие условные `if elements: ...` / `if link: ...` (а link и
reaction_info там всегда были `{}`, то есть falsy) вырезали поля
из ответа CHAT_HISTORY и MSG_SEND, чем и сломали историю.

- src/common/tools.py: новый build_message_dict() — единая сборка
  тела сообщения, где все поля (id, cid, time, type, sender, text,
  attaches, elements, reactionInfo, link) присутствуют ВСЕГДА.
  get_last_message переписан через него.
- src/oneme/processors/history.py: chat_history использует
  build_message_dict вместо ручной логики с условными if-ками.
- src/oneme/processors/messages.py: msg_send.bodyMessage теперь
  отдает cid / reactionInfo / link даже пустыми и приводит id
  к int для mobile, str для web.

Цепная польза: auth.py LOGIN bootstrap (через generate_chats →
get_last_message) и search.py тоже теперь шлют корректную схему.

* MAX: bootstrap-история в LOGIN — клиент перестал думать что всё уже синканулось

В ответе LOGIN сервер слал messages: {} и chatMarker: 0. Десктопный
клиент в этом случае считает, что локальная история уже синхронизирована
со старого запуска, и НЕ отправляет CHAT_HISTORY (49) при открытии чата.
В окне видно только lastMessage из chats[], а вся реальная переписка —
ничерта.

- src/common/tools.py: collect_bootstrap_history(chatIds, ...) —
  собирает карту {chatId: [последние N сообщений]}, в т.ч. избранное
  под клиентским id = senderId ^ senderId.
- src/oneme/processors/auth.py: подсовываем эту карту в
  payload.messages, chatMarker = текущее время вместо 0.

* fix(chat history): фикс

* fix(chat history): фикс

* fix(chat history): фикс

* fix(chat history): фикс

* fix(chat history): фикс

* fix(chat history): фикс

* fix(chat history): фикс

* MAX & TT: теперь полноценный чёрный список

* TT: регистрация через клиент

* Вроде как нормальные заглушки под ассеты

* maybe fixes

---------

Co-authored-by: WowInceptionGood <143893762+WowInceptionGood@users.noreply.github.com>
Co-authored-by: zavolo <me@kirillka.ru>
Co-authored-by: Aleksandr Kosachev <38947781+gohryt@users.noreply.github.com>
2026-07-08 00:11:48 +03:00
WowInceptionGood c53c3cd195 Документация 2026-03-27 17:34:24 +03:00
Alexey Polyakov 77da4a1b61 И так тоже будет лучше 2026-03-22 08:41:49 +03:00
WowInceptionGood f9b6b2c75e README: Думаю так лучше 2026-03-22 00:07:10 +03:00
WowInceptionGood 930a95453f Документация будет перенесена в другой репозиторий в ближайшее время
+ Обновил README ещё.
2026-03-22 00:06:19 +03:00
relyay 2776051b17 Grammar fix in readme.md (#27) 2026-03-16 18:59:08 +03:00
WowInceptionGood 1d4cb2d248 Из-за недавних ошибок 2026-03-16 00:12:27 +03:00
WowInceptionGood ba6191d7e3 faq: Используйте сторонние сервера на свой страх и риск 2026-03-15 21:36:13 +03:00
Alexey Polyakov 48c74153ec Update server link format in faq/servers.md 2026-03-15 21:32:29 +03:00
Alexey Polyakov ea9e4761bc Add mox.nyako.tk server information 2026-03-15 21:28:38 +03:00
Anatoliy Esherkin e6d693efbd Create servers.md (#23) 2026-03-15 11:04:44 +03:00
Anatoliy Esherkin dae26f02a8 Открытые сервера (#25) 2026-03-15 11:04:32 +03:00
Anatoliy Esherkin ca652aab03 Патч IPA (#24)
* Патч IPA

* Update patch_ipa.md

* Update readme.md

---------

Co-authored-by: Alexey Polyakov <starwear3000@mail.ru>
2026-03-15 11:03:47 +03:00
artemchik12 2ce4ac64e2 Что делать на случай ошибки при сборке (#20)
* Что делать на случай ошибки при сборке

* Update patch_apk.md

* Update patch_apk.md

---------

Co-authored-by: Alexey Polyakov <starwear3000@mail.ru>
2026-03-13 18:28:21 +03:00
14 changed files with 74 additions and 40 deletions
-1
View File
@@ -1 +0,0 @@
Проект OpenMAX не принимает правки, написанные с использованием искусственного интеллекта и нейросетевых технологий
+8
View File
@@ -0,0 +1,8 @@
# Патч MAX для IOS
1) Распаковываете IPA
2) Находите файл MAX в Payload/Max.app
3) Открываете hex-редактор и находите api.oneme.ru и меняете на свой
При желании можете поменять другие URL
URL не должен привышать количество символов которое было у изначального URL!
4) Открываете IPA как архив и добавляете патченный файл MAX в Payload/Max.app
+3 -2
View File
@@ -1,7 +1,8 @@
# Навигация по faq
## Работа с сервером
[Установка сервера](install.md)
- [Установка сервера](install.md)
## Патчинг клиентов
[Патч apk](patch_apk.md)
- [Патч мобильного клиента для Android](patch_apk.md)
- [Патч мобильного клиента для IOS](patch_ipa.md)
+11
View File
@@ -0,0 +1,11 @@
> [!Caution]
>
> На данной странице представлены инстансы от сторонних разработчиков.
>
> Используйте на свой страх и риск
>
# Открытые сервера
* [JustMAX](https://t.me/justmax_official)
* [mox.nyako.tk](https://t.me/opengrame/296), порт 443 (регистрация по реальному номеру)
+5 -3
View File
@@ -1,6 +1,6 @@
> [!Caution]
>
> Проект находится на ранней стадии разработки и вероятно полон багов.
> Проект находится на ранней стадии разработки и, вероятно, полон багов.
>
> Использование в профессиональных средах не рекомендовано.
@@ -13,7 +13,7 @@ https://t.me/openmax_alerts
# Требования
- Python 3.12+ (поддержка версий ниже не гарантирована)
- MariaDB, MySQL или SQLite
- MariaDB, MySQL или SQLite (использование последнего не рекомендуется, так как поддержка ещё в разработке)
- Уметь патчить клиент MAX или собирать Komet из исходного кода (естественно с заменой сервера)
- Сертификат и приватный ключ X.509 (для тестирования сервера можно сгенерировать самоподписанный: ```openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 365```)
@@ -22,4 +22,6 @@ https://t.me/openmax_alerts
Клиент может быть практически любым, главное условие - чтобы он был совместим с официальным сервером (`api.oneme.ru` / `api.tamtam.chat`).
# Дополнительная информация
[Faq](faq/readme.md)
- [FAQ](faq/readme.md)
- [Документация проекта](https://github.com/openmax-server/docs)
- [Публичные сервера](faq/servers.md)
+3
View File
@@ -49,6 +49,9 @@ class SQLiteConnectionCompat:
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)
+23 -19
View File
@@ -39,17 +39,17 @@ class Tools:
def generate_profile(
self,
id=1,
phone=70000000000,
id=None,
phone=None,
avatarUrl=None,
photoId=None,
updateTime=0,
firstName="Test",
lastName="Account",
options=[],
updateTime=None,
firstName=None,
lastName=None,
options=None,
description=None,
accountStatus=0,
profileOptions=[],
accountStatus=None,
profileOptions=None,
includeProfileOptions=True,
username=None,
@@ -108,31 +108,32 @@ class Tools:
def generate_profile_tt(
self,
id=1,
phone=70000000000,
id=None,
phone=None,
avatarUrl=None,
photoId=None,
updateTime=0,
firstName="Test",
lastName="Account",
options=[],
updateTime=None,
firstName=None,
lastName=None,
options=None,
description=None,
username=None,
custom_firstname=None,
custom_lastname=None
custom_lastname=None,
blocked=None
):
# Так как TT не поддерживает фамилию, и если нам ее не передали в функцию
# то используем только имя, чтобы избежать None в фамилии
if firstName and lastName:
name = f"{firstName} {lastName}",
name = f"{firstName} {lastName}"
else:
name = firstName
# Используем такой же костыль, как и выше
if custom_firstname:
custom_name = custom_firstname
elif custom_firstname and custom_lastname:
if custom_firstname and custom_lastname:
custom_name = f"{custom_firstname} {custom_lastname}"
elif custom_firstname:
custom_name = custom_firstname
else:
custom_name = None
@@ -163,6 +164,9 @@ class Tools:
{"name": custom_name, "type": "CUSTOM"}
)
if blocked:
contact["status"] = "BLOCKED"
return contact
def generate_chat(
+4 -1
View File
@@ -22,6 +22,7 @@ class OnemeController(ControllerBase):
# Выбираем протокол в зависимости от типа подключения
proto = self.proto_web if is_web else self.proto_tcp
packet = None
# Не отправляем событие самому себе
if writer == eventData.get("writer"):
@@ -94,7 +95,9 @@ class OnemeController(ControllerBase):
cmd=0, seq=1, opcode=self.opcodes.NOTIF_PRESENCE, payload=payload
)
# Отправляем пакет
if not packet:
return
if is_web:
await writer.send(packet)
else:
+5 -5
View File
@@ -297,7 +297,7 @@ class AuthProcessors(BaseProcessor):
photoId = (
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 = (
None if not account.get("description") else account.get("description")
)
@@ -521,7 +521,7 @@ class AuthProcessors(BaseProcessor):
await self._send_error(
seq, self.opcodes.LOGIN, self.error_types.INVALID_PAYLOAD, writer
)
return
return None, None, None
# Чаты, где состоит пользователь
chats = []
@@ -545,7 +545,7 @@ class AuthProcessors(BaseProcessor):
await self._send_error(
seq, self.opcodes.LOGIN, self.error_types.INVALID_TOKEN, writer
)
return
return None, None, None
# Ищем аккаунт пользователя в бд
await cursor.execute(
@@ -563,7 +563,7 @@ class AuthProcessors(BaseProcessor):
# Ищем все чаты, где состоит пользователь
await cursor.execute(
"SELECT * FROM chat_participants WHERE user_id = %s",
(user.get("id")),
(user.get("id"),),
)
user_chats = await cursor.fetchall()
@@ -578,7 +578,7 @@ class AuthProcessors(BaseProcessor):
# Аватарка с биографией
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")
if self._check_legacy_version(appVersion):
+1 -1
View File
@@ -153,7 +153,7 @@ class MainProcessors(BaseProcessor):
# Аватарка с биографией
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")
# Генерируем профиль
+2 -2
View File
@@ -36,7 +36,7 @@ class SearchProcessors(BaseProcessor):
if user:
# Аватарка с биографией
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")
# Получаем данные контакта
@@ -129,7 +129,7 @@ class SearchProcessors(BaseProcessor):
# Аватарка с биографией
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")
# Получаем данные контакта
+4 -1
View File
@@ -22,6 +22,7 @@ class TTController(ControllerBase):
# Выбираем протокол в зависимости от типа подключения
proto = self.proto_web if is_web else self.proto_tcp
packet = None
# Не отправляем событие самому себе
if writer == eventData.get("writer"):
@@ -94,7 +95,9 @@ class TTController(ControllerBase):
cmd=0, seq=1, opcode=self.opcodes.NOTIF_PRESENCE, payload=payload
)
# Отправляем пакет
if not packet:
return
if is_web:
await writer.send(packet)
else:
+3 -3
View File
@@ -443,7 +443,7 @@ class AuthProcessors(BaseProcessor):
self.logger.error(f"Возникли ошибки при валидации пакета: {e}")
await self._send_error(seq, self.opcodes.LOGIN,
self.error_types.INVALID_PAYLOAD, writer)
return
return None, None, None
# Чаты, где состоит пользователь
chats = []
@@ -464,7 +464,7 @@ class AuthProcessors(BaseProcessor):
if token_data is None:
await self._send_error(seq, self.opcodes.LOGIN,
self.error_types.INVALID_TOKEN, writer)
return
return None, None, None
# Ищем аккаунт пользователя в бд
await cursor.execute("SELECT * FROM users WHERE phone = %s", (token_data.get("phone"),))
@@ -477,7 +477,7 @@ class AuthProcessors(BaseProcessor):
# Ищем все чаты, где состоит пользователь
await cursor.execute(
"SELECT * FROM chat_participants WHERE user_id = %s",
(user.get('id'))
(user.get('id'),)
)
user_chats = await cursor.fetchall()