- feat(pylon): Refactor code

- feat(idm): Rename IDM-2 to IDM
  - feat(idm): Open IDM
3 internal commit(s)

GitOrigin-RevId: e302e9b5cda18cca1adc4ae8a3d906714d222106
This commit is contained in:
the-superpirate 2021-04-12 20:47:21 +03:00
parent a51463c027
commit 9ce67ec590
68 changed files with 1230 additions and 737 deletions

70
idm/api/BUILD.bazel Normal file
View File

@ -0,0 +1,70 @@
load("@io_bazel_rules_docker//python3:image.bzl", "py3_image")
load("@io_bazel_rules_docker//container:container.bzl", "container_push")
load("@pip_modules//:requirements.bzl", "requirement")
alias(
name = "binary",
actual = ":image.binary",
)
py3_image(
name = "image",
srcs = glob(
[
"*.py",
"configs/**/*.py",
"daemons/**/*.py",
"models/**",
"proto/**",
"services/**",
],
exclude = [
"**/__pycache__/**",
"**/*.pyc",
"**/README",
"**/*.mako",
"proto/**/*.py",
],
),
base = "//images/production:base-python-image",
data = [
"configs/base.yaml",
"configs/logging.yaml",
],
layers = [
requirement("grpcio"),
requirement("pypika"),
requirement("uvloop"),
"//idm/api/proto:idm_grpc_py",
"//idm/api/proto:idm_proto_py",
"//library/aiogrpctools",
requirement("aiokit"),
"//library/aiopostgres",
"//library/configurator",
"//library/telegram",
requirement("izihawa_utils"),
],
main = "main.py",
srcs_version = "PY3ONLY",
visibility = ["//visibility:public"],
)
container_push(
name = "push-testing",
format = "Docker",
image = ":image",
registry = "registry.example.com",
repository = "idm-api",
tag = "testing",
)
container_push(
name = "push-latest",
format = "Docker",
image = ":image",
registry = "registry.example.com",
repository = "idm-api",
tag = "latest",
)

0
idm/api/__init__.py Normal file
View File

View File

@ -9,7 +9,7 @@ py_library(
requirement("cachetools"), requirement("cachetools"),
requirement("lru-dict"), requirement("lru-dict"),
requirement("tenacity"), requirement("tenacity"),
"//idm/api2/proto:idm_grpc_py", "//idm/api/proto:idm_grpc_py",
"//idm/api2/proto:idm_proto_py", "//idm/api/proto:idm_proto_py",
], ],
) )

View File

@ -0,0 +1,3 @@
from .aioclient import IdmApiGrpcClient
__all__ = ['IdmApiGrpcClient']

View File

@ -4,13 +4,13 @@ from grpc.experimental.aio import (
AioRpcError, AioRpcError,
insecure_channel, insecure_channel,
) )
from idm.api2.proto.chats_service_pb2 import ( from idm.api.proto.chat_manager_service_pb2 import (
CreateChatRequest, CreateChatRequest,
GetChatRequest, GetChatRequest,
ListChatsRequest, ListChatsRequest,
UpdateChatRequest, UpdateChatRequest,
) )
from idm.api2.proto.chats_service_pb2_grpc import ChatsStub from idm.api.proto.chat_manager_service_pb2_grpc import ChatManagerStub
from lru import LRU from lru import LRU
from tenacity import ( from tenacity import (
retry, retry,
@ -20,7 +20,7 @@ from tenacity import (
) )
class IdmApi2GrpcClient(AioThing): class IdmApiGrpcClient(AioThing):
def __init__( def __init__(
self, self,
base_url, base_url,
@ -33,7 +33,7 @@ class IdmApi2GrpcClient(AioThing):
('grpc.min_reconnect_backoff_ms', 1000), ('grpc.min_reconnect_backoff_ms', 1000),
('grpc.max_reconnect_backoff_ms', 2000), ('grpc.max_reconnect_backoff_ms', 2000),
]) ])
self.chats_stub = ChatsStub(self.channel) self.chat_manager_stub = ChatManagerStub(self.channel)
self.cache = LRU(4096) self.cache = LRU(4096)
async def start(self): async def start(self):
@ -49,7 +49,7 @@ class IdmApi2GrpcClient(AioThing):
language, language,
request_id: str = None, request_id: str = None,
): ):
response = await self.chats_stub.create_chat( response = await self.chat_manager_stub.create_chat(
CreateChatRequest( CreateChatRequest(
chat_id=chat_id, chat_id=chat_id,
username=username, username=username,
@ -74,7 +74,7 @@ class IdmApi2GrpcClient(AioThing):
chat_id, chat_id,
request_id: str = None, request_id: str = None,
): ):
response = await self.chats_stub.get_chat( response = await self.chat_manager_stub.get_chat(
GetChatRequest(chat_id=chat_id), GetChatRequest(chat_id=chat_id),
metadata=( metadata=(
('request-id', request_id), ('request-id', request_id),
@ -87,7 +87,7 @@ class IdmApi2GrpcClient(AioThing):
request_id: str = None, request_id: str = None,
banned_at_moment=None, banned_at_moment=None,
): ):
response = await self.chats_stub.list_chats( response = await self.chat_manager_stub.list_chats(
ListChatsRequest(banned_at_moment=banned_at_moment), ListChatsRequest(banned_at_moment=banned_at_moment),
metadata=( metadata=(
('request-id', request_id), ('request-id', request_id),
@ -106,7 +106,7 @@ class IdmApi2GrpcClient(AioThing):
ban_message=None, ban_message=None,
is_admin=None, is_admin=None,
): ):
response = await self.chats_stub.update_chat( response = await self.chat_manager_stub.update_chat(
UpdateChatRequest( UpdateChatRequest(
chat_id=chat_id, chat_id=chat_id,
language=language, language=language,

17
idm/api/configs/base.yaml Normal file
View File

@ -0,0 +1,17 @@
---
application:
debug: true
service_name: idm-api
database:
port: 5432
grpc:
address: 0.0.0.0
port: 9090
log_path: '/var/log/idm-api/{{ ENV_TYPE }}'
telegram:
# Telegram App Hash from https://my.telegram.org/
app_hash: '{{ APP_HASH }}'
# Telegram App ID from https://my.telegram.org/
app_id: 00000
database:
session_id: '/usr/lib/idm-api/{{ ENV_TYPE }}/session.db'

View File

@ -0,0 +1,63 @@
---
logging:
disable_existing_loggers: false
formatters:
base:
class: library.logging.formatters.BaseFormatter
default:
class: library.logging.formatters.DefaultFormatter
none:
class: logging.Formatter
traceback:
class: library.logging.formatters.TracebackFormatter
handlers:
debug:
class: logging.StreamHandler
formatter: default
level: DEBUG
stream: 'ext://sys.stderr'
error:
class: library.logging.handlers.BaseFileHandler
filename: '{{ log_path }}/error.log'
formatter: default
level: ERROR
statbox:
class: library.logging.handlers.BaseFileHandler
filename: '{{ log_path }}/statbox.log'
formatter: default
level: INFO
traceback:
class: library.logging.handlers.BaseFileHandler
filename: '{{ log_path }}/traceback.log'
formatter: traceback
level: ERROR
warning:
class: library.logging.handlers.BaseFileHandler
filename: '{{ log_path }}/warning.log'
formatter: default
level: WARNING
loggers:
debug:
handlers:
- debug
propagate: false
error:
handlers:
- error
- traceback
- warning
propagate: false
statbox:
handlers:
- statbox
propagate: false
telethon:
handlers:
- error
propagate: false
root:
handlers:
- debug
level: DEBUG
version: 1

View File

View File

@ -0,0 +1,87 @@
import asyncio
import logging
import string
from aiokit import AioThing
from library.telegram.base import BaseTelegramClient
from telethon.tl.functions.channels import GetParticipantsRequest
from telethon.tl.types import ChannelParticipantsSearch
class AdminLogReader(AioThing):
channel_name = '@nexus_search'
def __init__(self, telegram_config):
super().__init__()
self.subscriptors = set()
self.loading = False
self.telegram_client = BaseTelegramClient(
app_id=telegram_config['app_id'],
app_hash=telegram_config['app_hash'],
database=telegram_config['database'],
flood_sleep_threshold=25,
)
self.last_max_id = 0
def statbox(self, **kwargs):
logging.getLogger('statbox').info({'mode': 'admin_log_reader', **kwargs})
async def skip_admin_log(self):
async for event in self.telegram_client.iter_admin_log(self.channel_name, limit=1):
self.last_max_id = event.id
self.statbox(action='skipped_admin_log', max_id=self.last_max_id)
async def process_admin_log(self, sleep=1.0):
self.loading = True
try:
while 1:
async for event in self.telegram_client.iter_admin_log(
self.channel_name, join=True, invite=True, leave=True, min_id=self.last_max_id
):
is_subscribed = event.joined or event.joined_invite
if is_subscribed:
self.subscriptors.add(event.user_id)
elif event.user_id in self.subscriptors:
self.subscriptors.remove(event.user_id)
self.last_max_id = event.id
await asyncio.sleep(sleep)
except asyncio.CancelledError:
pass
finally:
self.loading = False
async def _fetch_users(self, query):
max_batch_size = 200
participants = await self.telegram_client(
GetParticipantsRequest(
channel=self.channel_name,
filter=ChannelParticipantsSearch(query),
offset=0,
limit=max_batch_size,
hash=0,
)
)
for user in participants.users:
self.subscriptors.add(user.id)
# There is a possibility that more users exist if we hit maximum count of users
# So, we are making a recursion to reveal it
if len(participants.users) == max_batch_size:
for new_letter in string.ascii_lowercase + string.digits:
await self._fetch_users(query + new_letter)
async def load_channel_users(self):
self.statbox(action='load_channel_users')
await self._fetch_users('')
self.statbox(action='loaded_channel_users', subscriptors=len(self.subscriptors))
async def start(self):
await self.telegram_client.start_and_wait()
await self.skip_admin_log()
await self.load_channel_users()
asyncio.create_task(self.process_admin_log())
def is_subscribed(self, user_id):
return not self.loading or user_id in self.subscriptors

54
idm/api/main.py Normal file
View File

@ -0,0 +1,54 @@
import asyncio
import uvloop
from aiopg.sa import create_engine
from idm.api.configs import get_config
from idm.api.daemons.admin_log_reader import AdminLogReader
from idm.api.services.chat_manager import ChatManagerService
from library.aiogrpctools import AioGrpcServer
from library.aiopostgres.pool_holder import AioPostgresPoolHolder
from library.configurator import Configurator
from library.logging import configure_logging
class GrpcServer(AioGrpcServer):
def __init__(self, config: Configurator):
super().__init__(address=config['grpc']['address'], port=config['grpc']['port'])
self.pool_holder = AioPostgresPoolHolder(
fn=create_engine,
database=config['database']['database'],
user=config['database']['username'],
password=config['database']['password'],
host=config['database']['host'],
port=config['database']['port'],
timeout=30,
pool_recycle=60,
maxsize=4,
)
self.admin_log_reader = AdminLogReader(
telegram_config=config['telegram'],
)
self.chat_manager_service = ChatManagerService(
server=self.server,
service_name=config['application']['service_name'],
pool_holder=self.pool_holder,
admin_log_reader=self.admin_log_reader,
)
self.starts.append(self.admin_log_reader)
self.waits.extend([self.chat_manager_service, self.pool_holder])
async def create_app(config: Configurator):
grpc_server = GrpcServer(config)
await grpc_server.start_and_wait()
def main():
uvloop.install()
config = get_config()
configure_logging(config)
asyncio.get_event_loop().run_until_complete(create_app(config))
if __name__ == '__main__':
main()

View File

@ -6,8 +6,7 @@ package(default_visibility = ["//visibility:public"])
proto_library( proto_library(
name = "idm_proto", name = "idm_proto",
srcs = [ srcs = [
"chats_service.proto", "chat_manager_service.proto",
"location.proto",
], ],
deps = [ deps = [
"@com_google_protobuf//:wrappers_proto", "@com_google_protobuf//:wrappers_proto",

View File

@ -0,0 +1,50 @@
syntax = "proto3";
package idm.api.proto;
message Chat {
int64 chat_id = 1;
string username = 2;
string language = 3;
bool is_system_messaging_enabled = 4;
bool is_discovery_enabled = 5;
int32 ban_until = 6;
string ban_message = 7;
bool is_admin = 8;
bool is_subscribed = 9;
int64 created_at = 10;
}
message Chats {
repeated Chat chats = 1;
}
message CreateChatRequest {
int64 chat_id = 1;
string username = 2;
string language = 3;
}
message GetChatRequest {
int64 chat_id = 1;
}
message ListChatsRequest {
optional int32 banned_at_moment = 1;
}
message UpdateChatRequest {
int64 chat_id = 1;
optional string language = 2;
optional bool is_system_messaging_enabled = 3;
optional bool is_discovery_enabled = 4;
optional int32 ban_until = 5;
optional string ban_message = 6;
optional bool is_admin = 7;
}
service ChatManager {
rpc create_chat(CreateChatRequest) returns (Chat) {};
rpc get_chat(GetChatRequest) returns (Chat) {};
rpc list_chats(ListChatsRequest) returns (Chats) {};
rpc update_chat(UpdateChatRequest) returns (Chat) {};
}

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: idm/api2/proto/chats_service.proto # source: idm/api/proto/chat_manager_service.proto
"""Generated protocol buffer code.""" """Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message from google.protobuf import message as _message
@ -12,96 +12,94 @@ from google.protobuf import symbol_database as _symbol_database
_sym_db = _symbol_database.Default() _sym_db = _symbol_database.Default()
from idm.api2.proto import \
location_pb2 as idm_dot_api2_dot_proto_dot_location__pb2
DESCRIPTOR = _descriptor.FileDescriptor( DESCRIPTOR = _descriptor.FileDescriptor(
name='idm/api2/proto/chats_service.proto', name='idm/api/proto/chat_manager_service.proto',
package='idm.api2.proto', package='idm.api.proto',
syntax='proto3', syntax='proto3',
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
serialized_pb=b'\n\"idm/api2/proto/chats_service.proto\x12\x0eidm.api2.proto\x1a\x1didm/api2/proto/location.proto\"\xe2\x01\n\x08\x43hatData\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x10\n\x08username\x18\x02 \x01(\t\x12\x10\n\x08language\x18\x03 \x01(\t\x12#\n\x1bis_system_messaging_enabled\x18\x04 \x01(\x08\x12\x1c\n\x14is_discovery_enabled\x18\x05 \x01(\x08\x12\x11\n\tban_until\x18\x06 \x01(\x05\x12\x13\n\x0b\x62\x61n_message\x18\x07 \x01(\t\x12\x10\n\x08is_admin\x18\x08 \x01(\x08\x12\x15\n\ris_subscribed\x18\n \x01(\x08\x12\x12\n\ncreated_at\x18\x0b \x01(\x03\"4\n\tChatsData\x12\'\n\x05\x63hats\x18\x01 \x03(\x0b\x32\x18.idm.api2.proto.ChatData\"H\n\x11\x43reateChatRequest\x12\x0f\n\x07\x63hat_id\x18\x01 \x01(\x03\x12\x10\n\x08username\x18\x02 \x01(\t\x12\x10\n\x08language\x18\x03 \x01(\t\"!\n\x0eGetChatRequest\x12\x0f\n\x07\x63hat_id\x18\x01 \x01(\x03\"H\n\x10ListChatsRequest\x12\x1a\n\x10\x62\x61nned_at_moment\x18\x01 \x01(\x05H\x00\x42\x18\n\x16\x62\x61nned_at_moment_oneof\"\xce\x02\n\x11UpdateChatRequest\x12\x0f\n\x07\x63hat_id\x18\x01 \x01(\x03\x12\x12\n\x08language\x18\x02 \x01(\tH\x00\x12%\n\x1bis_system_messaging_enabled\x18\x03 \x01(\x08H\x01\x12\x1e\n\x14is_discovery_enabled\x18\x04 \x01(\x08H\x02\x12\x13\n\tban_until\x18\x05 \x01(\x05H\x03\x12\x15\n\x0b\x62\x61n_message\x18\x06 \x01(\tH\x04\x12\x12\n\x08is_admin\x18\x07 \x01(\x08H\x05\x42\x10\n\x0elanguage_oneofB#\n!is_system_messaging_enabled_oneofB\x1c\n\x1ais_discovery_enabled_oneofB\x11\n\x0f\x62\x61n_until_oneofB\x13\n\x11\x62\x61n_message_oneofB\x10\n\x0eis_admin_oneof2\xb8\x02\n\x05\x43hats\x12L\n\x0b\x63reate_chat\x12!.idm.api2.proto.CreateChatRequest\x1a\x18.idm.api2.proto.ChatData\"\x00\x12\x46\n\x08get_chat\x12\x1e.idm.api2.proto.GetChatRequest\x1a\x18.idm.api2.proto.ChatData\"\x00\x12K\n\nlist_chats\x12 .idm.api2.proto.ListChatsRequest\x1a\x19.idm.api2.proto.ChatsData\"\x00\x12L\n\x0bupdate_chat\x12!.idm.api2.proto.UpdateChatRequest\x1a\x18.idm.api2.proto.ChatData\"\x00\x62\x06proto3' serialized_pb=b'\n(idm/api/proto/chat_manager_service.proto\x12\ridm.api.proto\"\xe3\x01\n\x04\x43hat\x12\x0f\n\x07\x63hat_id\x18\x01 \x01(\x03\x12\x10\n\x08username\x18\x02 \x01(\t\x12\x10\n\x08language\x18\x03 \x01(\t\x12#\n\x1bis_system_messaging_enabled\x18\x04 \x01(\x08\x12\x1c\n\x14is_discovery_enabled\x18\x05 \x01(\x08\x12\x11\n\tban_until\x18\x06 \x01(\x05\x12\x13\n\x0b\x62\x61n_message\x18\x07 \x01(\t\x12\x10\n\x08is_admin\x18\x08 \x01(\x08\x12\x15\n\ris_subscribed\x18\t \x01(\x08\x12\x12\n\ncreated_at\x18\n \x01(\x03\"+\n\x05\x43hats\x12\"\n\x05\x63hats\x18\x01 \x03(\x0b\x32\x13.idm.api.proto.Chat\"H\n\x11\x43reateChatRequest\x12\x0f\n\x07\x63hat_id\x18\x01 \x01(\x03\x12\x10\n\x08username\x18\x02 \x01(\t\x12\x10\n\x08language\x18\x03 \x01(\t\"!\n\x0eGetChatRequest\x12\x0f\n\x07\x63hat_id\x18\x01 \x01(\x03\"F\n\x10ListChatsRequest\x12\x1d\n\x10\x62\x61nned_at_moment\x18\x01 \x01(\x05H\x00\x88\x01\x01\x42\x13\n\x11_banned_at_moment\"\xc2\x02\n\x11UpdateChatRequest\x12\x0f\n\x07\x63hat_id\x18\x01 \x01(\x03\x12\x15\n\x08language\x18\x02 \x01(\tH\x00\x88\x01\x01\x12(\n\x1bis_system_messaging_enabled\x18\x03 \x01(\x08H\x01\x88\x01\x01\x12!\n\x14is_discovery_enabled\x18\x04 \x01(\x08H\x02\x88\x01\x01\x12\x16\n\tban_until\x18\x05 \x01(\x05H\x03\x88\x01\x01\x12\x18\n\x0b\x62\x61n_message\x18\x06 \x01(\tH\x04\x88\x01\x01\x12\x15\n\x08is_admin\x18\x07 \x01(\x08H\x05\x88\x01\x01\x42\x0b\n\t_languageB\x1e\n\x1c_is_system_messaging_enabledB\x17\n\x15_is_discovery_enabledB\x0c\n\n_ban_untilB\x0e\n\x0c_ban_messageB\x0b\n\t_is_admin2\xa6\x02\n\x0b\x43hatManager\x12\x46\n\x0b\x63reate_chat\x12 .idm.api.proto.CreateChatRequest\x1a\x13.idm.api.proto.Chat\"\x00\x12@\n\x08get_chat\x12\x1d.idm.api.proto.GetChatRequest\x1a\x13.idm.api.proto.Chat\"\x00\x12\x45\n\nlist_chats\x12\x1f.idm.api.proto.ListChatsRequest\x1a\x14.idm.api.proto.Chats\"\x00\x12\x46\n\x0bupdate_chat\x12 .idm.api.proto.UpdateChatRequest\x1a\x13.idm.api.proto.Chat\"\x00\x62\x06proto3'
, )
dependencies=[idm_dot_api2_dot_proto_dot_location__pb2.DESCRIPTOR,])
_CHATDATA = _descriptor.Descriptor( _CHAT = _descriptor.Descriptor(
name='ChatData', name='Chat',
full_name='idm.api2.proto.ChatData', full_name='idm.api.proto.Chat',
filename=None, filename=None,
file=DESCRIPTOR, file=DESCRIPTOR,
containing_type=None, containing_type=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[ fields=[
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='id', full_name='idm.api2.proto.ChatData.id', index=0, name='chat_id', full_name='idm.api.proto.Chat.chat_id', index=0,
number=1, type=3, cpp_type=2, label=1, number=1, type=3, cpp_type=2, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='username', full_name='idm.api2.proto.ChatData.username', index=1, name='username', full_name='idm.api.proto.Chat.username', index=1,
number=2, type=9, cpp_type=9, label=1, number=2, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'), has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='language', full_name='idm.api2.proto.ChatData.language', index=2, name='language', full_name='idm.api.proto.Chat.language', index=2,
number=3, type=9, cpp_type=9, label=1, number=3, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'), has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='is_system_messaging_enabled', full_name='idm.api2.proto.ChatData.is_system_messaging_enabled', index=3, name='is_system_messaging_enabled', full_name='idm.api.proto.Chat.is_system_messaging_enabled', index=3,
number=4, type=8, cpp_type=7, label=1, number=4, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False, has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='is_discovery_enabled', full_name='idm.api2.proto.ChatData.is_discovery_enabled', index=4, name='is_discovery_enabled', full_name='idm.api.proto.Chat.is_discovery_enabled', index=4,
number=5, type=8, cpp_type=7, label=1, number=5, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False, has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='ban_until', full_name='idm.api2.proto.ChatData.ban_until', index=5, name='ban_until', full_name='idm.api.proto.Chat.ban_until', index=5,
number=6, type=5, cpp_type=1, label=1, number=6, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='ban_message', full_name='idm.api2.proto.ChatData.ban_message', index=6, name='ban_message', full_name='idm.api.proto.Chat.ban_message', index=6,
number=7, type=9, cpp_type=9, label=1, number=7, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'), has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='is_admin', full_name='idm.api2.proto.ChatData.is_admin', index=7, name='is_admin', full_name='idm.api.proto.Chat.is_admin', index=7,
number=8, type=8, cpp_type=7, label=1, number=8, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False, has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='is_subscribed', full_name='idm.api2.proto.ChatData.is_subscribed', index=8, name='is_subscribed', full_name='idm.api.proto.Chat.is_subscribed', index=8,
number=10, type=8, cpp_type=7, label=1, number=9, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False, has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='created_at', full_name='idm.api2.proto.ChatData.created_at', index=9, name='created_at', full_name='idm.api.proto.Chat.created_at', index=9,
number=11, type=3, cpp_type=2, label=1, number=10, type=3, cpp_type=2, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
@ -118,21 +116,21 @@ _CHATDATA = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=86, serialized_start=60,
serialized_end=312, serialized_end=287,
) )
_CHATSDATA = _descriptor.Descriptor( _CHATS = _descriptor.Descriptor(
name='ChatsData', name='Chats',
full_name='idm.api2.proto.ChatsData', full_name='idm.api.proto.Chats',
filename=None, filename=None,
file=DESCRIPTOR, file=DESCRIPTOR,
containing_type=None, containing_type=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[ fields=[
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='chats', full_name='idm.api2.proto.ChatsData.chats', index=0, name='chats', full_name='idm.api.proto.Chats.chats', index=0,
number=1, type=11, cpp_type=10, label=3, number=1, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[], has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
@ -150,35 +148,35 @@ _CHATSDATA = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=314, serialized_start=289,
serialized_end=366, serialized_end=332,
) )
_CREATECHATREQUEST = _descriptor.Descriptor( _CREATECHATREQUEST = _descriptor.Descriptor(
name='CreateChatRequest', name='CreateChatRequest',
full_name='idm.api2.proto.CreateChatRequest', full_name='idm.api.proto.CreateChatRequest',
filename=None, filename=None,
file=DESCRIPTOR, file=DESCRIPTOR,
containing_type=None, containing_type=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[ fields=[
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='chat_id', full_name='idm.api2.proto.CreateChatRequest.chat_id', index=0, name='chat_id', full_name='idm.api.proto.CreateChatRequest.chat_id', index=0,
number=1, type=3, cpp_type=2, label=1, number=1, type=3, cpp_type=2, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='username', full_name='idm.api2.proto.CreateChatRequest.username', index=1, name='username', full_name='idm.api.proto.CreateChatRequest.username', index=1,
number=2, type=9, cpp_type=9, label=1, number=2, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'), has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='language', full_name='idm.api2.proto.CreateChatRequest.language', index=2, name='language', full_name='idm.api.proto.CreateChatRequest.language', index=2,
number=3, type=9, cpp_type=9, label=1, number=3, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'), has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
@ -196,21 +194,21 @@ _CREATECHATREQUEST = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=368, serialized_start=334,
serialized_end=440, serialized_end=406,
) )
_GETCHATREQUEST = _descriptor.Descriptor( _GETCHATREQUEST = _descriptor.Descriptor(
name='GetChatRequest', name='GetChatRequest',
full_name='idm.api2.proto.GetChatRequest', full_name='idm.api.proto.GetChatRequest',
filename=None, filename=None,
file=DESCRIPTOR, file=DESCRIPTOR,
containing_type=None, containing_type=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[ fields=[
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='chat_id', full_name='idm.api2.proto.GetChatRequest.chat_id', index=0, name='chat_id', full_name='idm.api.proto.GetChatRequest.chat_id', index=0,
number=1, type=3, cpp_type=2, label=1, number=1, type=3, cpp_type=2, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
@ -228,21 +226,21 @@ _GETCHATREQUEST = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=442, serialized_start=408,
serialized_end=475, serialized_end=441,
) )
_LISTCHATSREQUEST = _descriptor.Descriptor( _LISTCHATSREQUEST = _descriptor.Descriptor(
name='ListChatsRequest', name='ListChatsRequest',
full_name='idm.api2.proto.ListChatsRequest', full_name='idm.api.proto.ListChatsRequest',
filename=None, filename=None,
file=DESCRIPTOR, file=DESCRIPTOR,
containing_type=None, containing_type=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[ fields=[
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='banned_at_moment', full_name='idm.api2.proto.ListChatsRequest.banned_at_moment', index=0, name='banned_at_moment', full_name='idm.api.proto.ListChatsRequest.banned_at_moment', index=0,
number=1, type=5, cpp_type=1, label=1, number=1, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
@ -260,68 +258,68 @@ _LISTCHATSREQUEST = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
_descriptor.OneofDescriptor( _descriptor.OneofDescriptor(
name='banned_at_moment_oneof', full_name='idm.api2.proto.ListChatsRequest.banned_at_moment_oneof', name='_banned_at_moment', full_name='idm.api.proto.ListChatsRequest._banned_at_moment',
index=0, containing_type=None, index=0, containing_type=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[]), fields=[]),
], ],
serialized_start=477, serialized_start=443,
serialized_end=549, serialized_end=513,
) )
_UPDATECHATREQUEST = _descriptor.Descriptor( _UPDATECHATREQUEST = _descriptor.Descriptor(
name='UpdateChatRequest', name='UpdateChatRequest',
full_name='idm.api2.proto.UpdateChatRequest', full_name='idm.api.proto.UpdateChatRequest',
filename=None, filename=None,
file=DESCRIPTOR, file=DESCRIPTOR,
containing_type=None, containing_type=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[ fields=[
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='chat_id', full_name='idm.api2.proto.UpdateChatRequest.chat_id', index=0, name='chat_id', full_name='idm.api.proto.UpdateChatRequest.chat_id', index=0,
number=1, type=3, cpp_type=2, label=1, number=1, type=3, cpp_type=2, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='language', full_name='idm.api2.proto.UpdateChatRequest.language', index=1, name='language', full_name='idm.api.proto.UpdateChatRequest.language', index=1,
number=2, type=9, cpp_type=9, label=1, number=2, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'), has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='is_system_messaging_enabled', full_name='idm.api2.proto.UpdateChatRequest.is_system_messaging_enabled', index=2, name='is_system_messaging_enabled', full_name='idm.api.proto.UpdateChatRequest.is_system_messaging_enabled', index=2,
number=3, type=8, cpp_type=7, label=1, number=3, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False, has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='is_discovery_enabled', full_name='idm.api2.proto.UpdateChatRequest.is_discovery_enabled', index=3, name='is_discovery_enabled', full_name='idm.api.proto.UpdateChatRequest.is_discovery_enabled', index=3,
number=4, type=8, cpp_type=7, label=1, number=4, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False, has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='ban_until', full_name='idm.api2.proto.UpdateChatRequest.ban_until', index=4, name='ban_until', full_name='idm.api.proto.UpdateChatRequest.ban_until', index=4,
number=5, type=5, cpp_type=1, label=1, number=5, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0, has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='ban_message', full_name='idm.api2.proto.UpdateChatRequest.ban_message', index=5, name='ban_message', full_name='idm.api.proto.UpdateChatRequest.ban_message', index=5,
number=6, type=9, cpp_type=9, label=1, number=6, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'), has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='is_admin', full_name='idm.api2.proto.UpdateChatRequest.is_admin', index=6, name='is_admin', full_name='idm.api.proto.UpdateChatRequest.is_admin', index=6,
number=7, type=8, cpp_type=7, label=1, number=7, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False, has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
@ -339,167 +337,167 @@ _UPDATECHATREQUEST = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
_descriptor.OneofDescriptor( _descriptor.OneofDescriptor(
name='language_oneof', full_name='idm.api2.proto.UpdateChatRequest.language_oneof', name='_language', full_name='idm.api.proto.UpdateChatRequest._language',
index=0, containing_type=None, index=0, containing_type=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[]), fields=[]),
_descriptor.OneofDescriptor( _descriptor.OneofDescriptor(
name='is_system_messaging_enabled_oneof', full_name='idm.api2.proto.UpdateChatRequest.is_system_messaging_enabled_oneof', name='_is_system_messaging_enabled', full_name='idm.api.proto.UpdateChatRequest._is_system_messaging_enabled',
index=1, containing_type=None, index=1, containing_type=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[]), fields=[]),
_descriptor.OneofDescriptor( _descriptor.OneofDescriptor(
name='is_discovery_enabled_oneof', full_name='idm.api2.proto.UpdateChatRequest.is_discovery_enabled_oneof', name='_is_discovery_enabled', full_name='idm.api.proto.UpdateChatRequest._is_discovery_enabled',
index=2, containing_type=None, index=2, containing_type=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[]), fields=[]),
_descriptor.OneofDescriptor( _descriptor.OneofDescriptor(
name='ban_until_oneof', full_name='idm.api2.proto.UpdateChatRequest.ban_until_oneof', name='_ban_until', full_name='idm.api.proto.UpdateChatRequest._ban_until',
index=3, containing_type=None, index=3, containing_type=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[]), fields=[]),
_descriptor.OneofDescriptor( _descriptor.OneofDescriptor(
name='ban_message_oneof', full_name='idm.api2.proto.UpdateChatRequest.ban_message_oneof', name='_ban_message', full_name='idm.api.proto.UpdateChatRequest._ban_message',
index=4, containing_type=None, index=4, containing_type=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[]), fields=[]),
_descriptor.OneofDescriptor( _descriptor.OneofDescriptor(
name='is_admin_oneof', full_name='idm.api2.proto.UpdateChatRequest.is_admin_oneof', name='_is_admin', full_name='idm.api.proto.UpdateChatRequest._is_admin',
index=5, containing_type=None, index=5, containing_type=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[]), fields=[]),
], ],
serialized_start=552, serialized_start=516,
serialized_end=886, serialized_end=838,
) )
_CHATSDATA.fields_by_name['chats'].message_type = _CHATDATA _CHATS.fields_by_name['chats'].message_type = _CHAT
_LISTCHATSREQUEST.oneofs_by_name['banned_at_moment_oneof'].fields.append( _LISTCHATSREQUEST.oneofs_by_name['_banned_at_moment'].fields.append(
_LISTCHATSREQUEST.fields_by_name['banned_at_moment']) _LISTCHATSREQUEST.fields_by_name['banned_at_moment'])
_LISTCHATSREQUEST.fields_by_name['banned_at_moment'].containing_oneof = _LISTCHATSREQUEST.oneofs_by_name['banned_at_moment_oneof'] _LISTCHATSREQUEST.fields_by_name['banned_at_moment'].containing_oneof = _LISTCHATSREQUEST.oneofs_by_name['_banned_at_moment']
_UPDATECHATREQUEST.oneofs_by_name['language_oneof'].fields.append( _UPDATECHATREQUEST.oneofs_by_name['_language'].fields.append(
_UPDATECHATREQUEST.fields_by_name['language']) _UPDATECHATREQUEST.fields_by_name['language'])
_UPDATECHATREQUEST.fields_by_name['language'].containing_oneof = _UPDATECHATREQUEST.oneofs_by_name['language_oneof'] _UPDATECHATREQUEST.fields_by_name['language'].containing_oneof = _UPDATECHATREQUEST.oneofs_by_name['_language']
_UPDATECHATREQUEST.oneofs_by_name['is_system_messaging_enabled_oneof'].fields.append( _UPDATECHATREQUEST.oneofs_by_name['_is_system_messaging_enabled'].fields.append(
_UPDATECHATREQUEST.fields_by_name['is_system_messaging_enabled']) _UPDATECHATREQUEST.fields_by_name['is_system_messaging_enabled'])
_UPDATECHATREQUEST.fields_by_name['is_system_messaging_enabled'].containing_oneof = _UPDATECHATREQUEST.oneofs_by_name['is_system_messaging_enabled_oneof'] _UPDATECHATREQUEST.fields_by_name['is_system_messaging_enabled'].containing_oneof = _UPDATECHATREQUEST.oneofs_by_name['_is_system_messaging_enabled']
_UPDATECHATREQUEST.oneofs_by_name['is_discovery_enabled_oneof'].fields.append( _UPDATECHATREQUEST.oneofs_by_name['_is_discovery_enabled'].fields.append(
_UPDATECHATREQUEST.fields_by_name['is_discovery_enabled']) _UPDATECHATREQUEST.fields_by_name['is_discovery_enabled'])
_UPDATECHATREQUEST.fields_by_name['is_discovery_enabled'].containing_oneof = _UPDATECHATREQUEST.oneofs_by_name['is_discovery_enabled_oneof'] _UPDATECHATREQUEST.fields_by_name['is_discovery_enabled'].containing_oneof = _UPDATECHATREQUEST.oneofs_by_name['_is_discovery_enabled']
_UPDATECHATREQUEST.oneofs_by_name['ban_until_oneof'].fields.append( _UPDATECHATREQUEST.oneofs_by_name['_ban_until'].fields.append(
_UPDATECHATREQUEST.fields_by_name['ban_until']) _UPDATECHATREQUEST.fields_by_name['ban_until'])
_UPDATECHATREQUEST.fields_by_name['ban_until'].containing_oneof = _UPDATECHATREQUEST.oneofs_by_name['ban_until_oneof'] _UPDATECHATREQUEST.fields_by_name['ban_until'].containing_oneof = _UPDATECHATREQUEST.oneofs_by_name['_ban_until']
_UPDATECHATREQUEST.oneofs_by_name['ban_message_oneof'].fields.append( _UPDATECHATREQUEST.oneofs_by_name['_ban_message'].fields.append(
_UPDATECHATREQUEST.fields_by_name['ban_message']) _UPDATECHATREQUEST.fields_by_name['ban_message'])
_UPDATECHATREQUEST.fields_by_name['ban_message'].containing_oneof = _UPDATECHATREQUEST.oneofs_by_name['ban_message_oneof'] _UPDATECHATREQUEST.fields_by_name['ban_message'].containing_oneof = _UPDATECHATREQUEST.oneofs_by_name['_ban_message']
_UPDATECHATREQUEST.oneofs_by_name['is_admin_oneof'].fields.append( _UPDATECHATREQUEST.oneofs_by_name['_is_admin'].fields.append(
_UPDATECHATREQUEST.fields_by_name['is_admin']) _UPDATECHATREQUEST.fields_by_name['is_admin'])
_UPDATECHATREQUEST.fields_by_name['is_admin'].containing_oneof = _UPDATECHATREQUEST.oneofs_by_name['is_admin_oneof'] _UPDATECHATREQUEST.fields_by_name['is_admin'].containing_oneof = _UPDATECHATREQUEST.oneofs_by_name['_is_admin']
DESCRIPTOR.message_types_by_name['ChatData'] = _CHATDATA DESCRIPTOR.message_types_by_name['Chat'] = _CHAT
DESCRIPTOR.message_types_by_name['ChatsData'] = _CHATSDATA DESCRIPTOR.message_types_by_name['Chats'] = _CHATS
DESCRIPTOR.message_types_by_name['CreateChatRequest'] = _CREATECHATREQUEST DESCRIPTOR.message_types_by_name['CreateChatRequest'] = _CREATECHATREQUEST
DESCRIPTOR.message_types_by_name['GetChatRequest'] = _GETCHATREQUEST DESCRIPTOR.message_types_by_name['GetChatRequest'] = _GETCHATREQUEST
DESCRIPTOR.message_types_by_name['ListChatsRequest'] = _LISTCHATSREQUEST DESCRIPTOR.message_types_by_name['ListChatsRequest'] = _LISTCHATSREQUEST
DESCRIPTOR.message_types_by_name['UpdateChatRequest'] = _UPDATECHATREQUEST DESCRIPTOR.message_types_by_name['UpdateChatRequest'] = _UPDATECHATREQUEST
_sym_db.RegisterFileDescriptor(DESCRIPTOR) _sym_db.RegisterFileDescriptor(DESCRIPTOR)
ChatData = _reflection.GeneratedProtocolMessageType('ChatData', (_message.Message,), { Chat = _reflection.GeneratedProtocolMessageType('Chat', (_message.Message,), {
'DESCRIPTOR' : _CHATDATA, 'DESCRIPTOR' : _CHAT,
'__module__' : 'idm.api2.proto.chats_service_pb2' '__module__' : 'idm.api.proto.chat_manager_service_pb2'
# @@protoc_insertion_point(class_scope:idm.api2.proto.ChatData) # @@protoc_insertion_point(class_scope:idm.api.proto.Chat)
}) })
_sym_db.RegisterMessage(ChatData) _sym_db.RegisterMessage(Chat)
ChatsData = _reflection.GeneratedProtocolMessageType('ChatsData', (_message.Message,), { Chats = _reflection.GeneratedProtocolMessageType('Chats', (_message.Message,), {
'DESCRIPTOR' : _CHATSDATA, 'DESCRIPTOR' : _CHATS,
'__module__' : 'idm.api2.proto.chats_service_pb2' '__module__' : 'idm.api.proto.chat_manager_service_pb2'
# @@protoc_insertion_point(class_scope:idm.api2.proto.ChatsData) # @@protoc_insertion_point(class_scope:idm.api.proto.Chats)
}) })
_sym_db.RegisterMessage(ChatsData) _sym_db.RegisterMessage(Chats)
CreateChatRequest = _reflection.GeneratedProtocolMessageType('CreateChatRequest', (_message.Message,), { CreateChatRequest = _reflection.GeneratedProtocolMessageType('CreateChatRequest', (_message.Message,), {
'DESCRIPTOR' : _CREATECHATREQUEST, 'DESCRIPTOR' : _CREATECHATREQUEST,
'__module__' : 'idm.api2.proto.chats_service_pb2' '__module__' : 'idm.api.proto.chat_manager_service_pb2'
# @@protoc_insertion_point(class_scope:idm.api2.proto.CreateChatRequest) # @@protoc_insertion_point(class_scope:idm.api.proto.CreateChatRequest)
}) })
_sym_db.RegisterMessage(CreateChatRequest) _sym_db.RegisterMessage(CreateChatRequest)
GetChatRequest = _reflection.GeneratedProtocolMessageType('GetChatRequest', (_message.Message,), { GetChatRequest = _reflection.GeneratedProtocolMessageType('GetChatRequest', (_message.Message,), {
'DESCRIPTOR' : _GETCHATREQUEST, 'DESCRIPTOR' : _GETCHATREQUEST,
'__module__' : 'idm.api2.proto.chats_service_pb2' '__module__' : 'idm.api.proto.chat_manager_service_pb2'
# @@protoc_insertion_point(class_scope:idm.api2.proto.GetChatRequest) # @@protoc_insertion_point(class_scope:idm.api.proto.GetChatRequest)
}) })
_sym_db.RegisterMessage(GetChatRequest) _sym_db.RegisterMessage(GetChatRequest)
ListChatsRequest = _reflection.GeneratedProtocolMessageType('ListChatsRequest', (_message.Message,), { ListChatsRequest = _reflection.GeneratedProtocolMessageType('ListChatsRequest', (_message.Message,), {
'DESCRIPTOR' : _LISTCHATSREQUEST, 'DESCRIPTOR' : _LISTCHATSREQUEST,
'__module__' : 'idm.api2.proto.chats_service_pb2' '__module__' : 'idm.api.proto.chat_manager_service_pb2'
# @@protoc_insertion_point(class_scope:idm.api2.proto.ListChatsRequest) # @@protoc_insertion_point(class_scope:idm.api.proto.ListChatsRequest)
}) })
_sym_db.RegisterMessage(ListChatsRequest) _sym_db.RegisterMessage(ListChatsRequest)
UpdateChatRequest = _reflection.GeneratedProtocolMessageType('UpdateChatRequest', (_message.Message,), { UpdateChatRequest = _reflection.GeneratedProtocolMessageType('UpdateChatRequest', (_message.Message,), {
'DESCRIPTOR' : _UPDATECHATREQUEST, 'DESCRIPTOR' : _UPDATECHATREQUEST,
'__module__' : 'idm.api2.proto.chats_service_pb2' '__module__' : 'idm.api.proto.chat_manager_service_pb2'
# @@protoc_insertion_point(class_scope:idm.api2.proto.UpdateChatRequest) # @@protoc_insertion_point(class_scope:idm.api.proto.UpdateChatRequest)
}) })
_sym_db.RegisterMessage(UpdateChatRequest) _sym_db.RegisterMessage(UpdateChatRequest)
_CHATS = _descriptor.ServiceDescriptor( _CHATMANAGER = _descriptor.ServiceDescriptor(
name='Chats', name='ChatManager',
full_name='idm.api2.proto.Chats', full_name='idm.api.proto.ChatManager',
file=DESCRIPTOR, file=DESCRIPTOR,
index=0, index=0,
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
serialized_start=889, serialized_start=841,
serialized_end=1201, serialized_end=1135,
methods=[ methods=[
_descriptor.MethodDescriptor( _descriptor.MethodDescriptor(
name='create_chat', name='create_chat',
full_name='idm.api2.proto.Chats.create_chat', full_name='idm.api.proto.ChatManager.create_chat',
index=0, index=0,
containing_service=None, containing_service=None,
input_type=_CREATECHATREQUEST, input_type=_CREATECHATREQUEST,
output_type=_CHATDATA, output_type=_CHAT,
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
), ),
_descriptor.MethodDescriptor( _descriptor.MethodDescriptor(
name='get_chat', name='get_chat',
full_name='idm.api2.proto.Chats.get_chat', full_name='idm.api.proto.ChatManager.get_chat',
index=1, index=1,
containing_service=None, containing_service=None,
input_type=_GETCHATREQUEST, input_type=_GETCHATREQUEST,
output_type=_CHATDATA, output_type=_CHAT,
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
), ),
_descriptor.MethodDescriptor( _descriptor.MethodDescriptor(
name='list_chats', name='list_chats',
full_name='idm.api2.proto.Chats.list_chats', full_name='idm.api.proto.ChatManager.list_chats',
index=2, index=2,
containing_service=None, containing_service=None,
input_type=_LISTCHATSREQUEST, input_type=_LISTCHATSREQUEST,
output_type=_CHATSDATA, output_type=_CHATS,
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
), ),
_descriptor.MethodDescriptor( _descriptor.MethodDescriptor(
name='update_chat', name='update_chat',
full_name='idm.api2.proto.Chats.update_chat', full_name='idm.api.proto.ChatManager.update_chat',
index=3, index=3,
containing_service=None, containing_service=None,
input_type=_UPDATECHATREQUEST, input_type=_UPDATECHATREQUEST,
output_type=_CHATDATA, output_type=_CHAT,
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
), ),
]) ])
_sym_db.RegisterServiceDescriptor(_CHATS) _sym_db.RegisterServiceDescriptor(_CHATMANAGER)
DESCRIPTOR.services_by_name['Chats'] = _CHATS DESCRIPTOR.services_by_name['ChatManager'] = _CHATMANAGER
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@ -1,11 +1,12 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services.""" """Client and server classes corresponding to protobuf-defined services."""
import grpc import grpc
from idm.api2.proto import \ from idm.api.proto import \
chats_service_pb2 as idm_dot_api2_dot_proto_dot_chats__service__pb2 chat_manager_service_pb2 as \
idm_dot_api_dot_proto_dot_chat__manager__service__pb2
class ChatsStub(object): class ChatManagerStub(object):
"""Missing associated documentation comment in .proto file.""" """Missing associated documentation comment in .proto file."""
def __init__(self, channel): def __init__(self, channel):
@ -15,28 +16,28 @@ class ChatsStub(object):
channel: A grpc.Channel. channel: A grpc.Channel.
""" """
self.create_chat = channel.unary_unary( self.create_chat = channel.unary_unary(
'/idm.api2.proto.Chats/create_chat', '/idm.api.proto.ChatManager/create_chat',
request_serializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.CreateChatRequest.SerializeToString, request_serializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.CreateChatRequest.SerializeToString,
response_deserializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.ChatData.FromString, response_deserializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.Chat.FromString,
) )
self.get_chat = channel.unary_unary( self.get_chat = channel.unary_unary(
'/idm.api2.proto.Chats/get_chat', '/idm.api.proto.ChatManager/get_chat',
request_serializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.GetChatRequest.SerializeToString, request_serializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.GetChatRequest.SerializeToString,
response_deserializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.ChatData.FromString, response_deserializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.Chat.FromString,
) )
self.list_chats = channel.unary_unary( self.list_chats = channel.unary_unary(
'/idm.api2.proto.Chats/list_chats', '/idm.api.proto.ChatManager/list_chats',
request_serializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.ListChatsRequest.SerializeToString, request_serializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.ListChatsRequest.SerializeToString,
response_deserializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.ChatsData.FromString, response_deserializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.Chats.FromString,
) )
self.update_chat = channel.unary_unary( self.update_chat = channel.unary_unary(
'/idm.api2.proto.Chats/update_chat', '/idm.api.proto.ChatManager/update_chat',
request_serializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.UpdateChatRequest.SerializeToString, request_serializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.UpdateChatRequest.SerializeToString,
response_deserializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.ChatData.FromString, response_deserializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.Chat.FromString,
) )
class ChatsServicer(object): class ChatManagerServicer(object):
"""Missing associated documentation comment in .proto file.""" """Missing associated documentation comment in .proto file."""
def create_chat(self, request, context): def create_chat(self, request, context):
@ -64,36 +65,36 @@ class ChatsServicer(object):
raise NotImplementedError('Method not implemented!') raise NotImplementedError('Method not implemented!')
def add_ChatsServicer_to_server(servicer, server): def add_ChatManagerServicer_to_server(servicer, server):
rpc_method_handlers = { rpc_method_handlers = {
'create_chat': grpc.unary_unary_rpc_method_handler( 'create_chat': grpc.unary_unary_rpc_method_handler(
servicer.create_chat, servicer.create_chat,
request_deserializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.CreateChatRequest.FromString, request_deserializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.CreateChatRequest.FromString,
response_serializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.ChatData.SerializeToString, response_serializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.Chat.SerializeToString,
), ),
'get_chat': grpc.unary_unary_rpc_method_handler( 'get_chat': grpc.unary_unary_rpc_method_handler(
servicer.get_chat, servicer.get_chat,
request_deserializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.GetChatRequest.FromString, request_deserializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.GetChatRequest.FromString,
response_serializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.ChatData.SerializeToString, response_serializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.Chat.SerializeToString,
), ),
'list_chats': grpc.unary_unary_rpc_method_handler( 'list_chats': grpc.unary_unary_rpc_method_handler(
servicer.list_chats, servicer.list_chats,
request_deserializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.ListChatsRequest.FromString, request_deserializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.ListChatsRequest.FromString,
response_serializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.ChatsData.SerializeToString, response_serializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.Chats.SerializeToString,
), ),
'update_chat': grpc.unary_unary_rpc_method_handler( 'update_chat': grpc.unary_unary_rpc_method_handler(
servicer.update_chat, servicer.update_chat,
request_deserializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.UpdateChatRequest.FromString, request_deserializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.UpdateChatRequest.FromString,
response_serializer=idm_dot_api2_dot_proto_dot_chats__service__pb2.ChatData.SerializeToString, response_serializer=idm_dot_api_dot_proto_dot_chat__manager__service__pb2.Chat.SerializeToString,
), ),
} }
generic_handler = grpc.method_handlers_generic_handler( generic_handler = grpc.method_handlers_generic_handler(
'idm.api2.proto.Chats', rpc_method_handlers) 'idm.api.proto.ChatManager', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,)) server.add_generic_rpc_handlers((generic_handler,))
# This class is part of an EXPERIMENTAL API. # This class is part of an EXPERIMENTAL API.
class Chats(object): class ChatManager(object):
"""Missing associated documentation comment in .proto file.""" """Missing associated documentation comment in .proto file."""
@staticmethod @staticmethod
@ -107,9 +108,9 @@ class Chats(object):
wait_for_ready=None, wait_for_ready=None,
timeout=None, timeout=None,
metadata=None): metadata=None):
return grpc.experimental.unary_unary(request, target, '/idm.api2.proto.Chats/create_chat', return grpc.experimental.unary_unary(request, target, '/idm.api.proto.ChatManager/create_chat',
idm_dot_api2_dot_proto_dot_chats__service__pb2.CreateChatRequest.SerializeToString, idm_dot_api_dot_proto_dot_chat__manager__service__pb2.CreateChatRequest.SerializeToString,
idm_dot_api2_dot_proto_dot_chats__service__pb2.ChatData.FromString, idm_dot_api_dot_proto_dot_chat__manager__service__pb2.Chat.FromString,
options, channel_credentials, options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata) insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
@ -124,9 +125,9 @@ class Chats(object):
wait_for_ready=None, wait_for_ready=None,
timeout=None, timeout=None,
metadata=None): metadata=None):
return grpc.experimental.unary_unary(request, target, '/idm.api2.proto.Chats/get_chat', return grpc.experimental.unary_unary(request, target, '/idm.api.proto.ChatManager/get_chat',
idm_dot_api2_dot_proto_dot_chats__service__pb2.GetChatRequest.SerializeToString, idm_dot_api_dot_proto_dot_chat__manager__service__pb2.GetChatRequest.SerializeToString,
idm_dot_api2_dot_proto_dot_chats__service__pb2.ChatData.FromString, idm_dot_api_dot_proto_dot_chat__manager__service__pb2.Chat.FromString,
options, channel_credentials, options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata) insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
@ -141,9 +142,9 @@ class Chats(object):
wait_for_ready=None, wait_for_ready=None,
timeout=None, timeout=None,
metadata=None): metadata=None):
return grpc.experimental.unary_unary(request, target, '/idm.api2.proto.Chats/list_chats', return grpc.experimental.unary_unary(request, target, '/idm.api.proto.ChatManager/list_chats',
idm_dot_api2_dot_proto_dot_chats__service__pb2.ListChatsRequest.SerializeToString, idm_dot_api_dot_proto_dot_chat__manager__service__pb2.ListChatsRequest.SerializeToString,
idm_dot_api2_dot_proto_dot_chats__service__pb2.ChatsData.FromString, idm_dot_api_dot_proto_dot_chat__manager__service__pb2.Chats.FromString,
options, channel_credentials, options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata) insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
@ -158,8 +159,8 @@ class Chats(object):
wait_for_ready=None, wait_for_ready=None,
timeout=None, timeout=None,
metadata=None): metadata=None):
return grpc.experimental.unary_unary(request, target, '/idm.api2.proto.Chats/update_chat', return grpc.experimental.unary_unary(request, target, '/idm.api.proto.ChatManager/update_chat',
idm_dot_api2_dot_proto_dot_chats__service__pb2.UpdateChatRequest.SerializeToString, idm_dot_api_dot_proto_dot_chat__manager__service__pb2.UpdateChatRequest.SerializeToString,
idm_dot_api2_dot_proto_dot_chats__service__pb2.ChatData.FromString, idm_dot_api_dot_proto_dot_chat__manager__service__pb2.Chat.FromString,
options, channel_credentials, options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata) insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

View File

@ -0,0 +1,109 @@
from grpc import StatusCode
from idm.api.proto.chat_manager_service_pb2 import Chat as ChatPb
from idm.api.proto.chat_manager_service_pb2 import Chats as ChatsPb
from idm.api.proto.chat_manager_service_pb2_grpc import (
ChatManagerServicer,
add_ChatManagerServicer_to_server,
)
from library.aiogrpctools.base import (
BaseService,
aiogrpc_request_wrapper,
)
from pypika import (
PostgreSQLQuery,
Table,
)
class ChatManagerService(ChatManagerServicer, BaseService):
chats_table = Table('chats')
def __init__(self, server, service_name, pool_holder, admin_log_reader):
super().__init__(service_name=service_name)
self.server = server
self.pool_holder = pool_holder
self.admin_log_reader = admin_log_reader
async def start(self):
add_ChatManagerServicer_to_server(self, self.server)
def enrich_chat(self, chat_pb: ChatPb):
chat_pb.is_subscribed = self.admin_log_reader.is_subscribed(chat_pb.chat_id)
return chat_pb
@aiogrpc_request_wrapper()
async def create_chat(self, request, context, metadata):
chat = ChatPb(
chat_id=request.chat_id,
language=request.language,
username=request.username,
is_system_messaging_enabled=True,
is_discovery_enabled=True,
)
query = (
PostgreSQLQuery
.into(self.chats_table)
.columns(
self.chats_table.chat_id,
self.chats_table.language,
self.chats_table.username,
self.chats_table.is_system_messaging_enabled,
self.chats_table.is_discovery_enabled,
)
.insert(
chat.chat_id,
chat.language,
chat.username,
chat.is_system_messaging_enabled,
chat.is_discovery_enabled,
)
.returning('*')
).get_sql()
async with self.pool_holder.pool.acquire() as session:
result = await session.execute(query)
chat = await result.fetchone()
return self.enrich_chat(ChatPb(**chat))
@aiogrpc_request_wrapper()
async def get_chat(self, request, context, metadata):
query = (
PostgreSQLQuery
.from_(self.chats_table)
.select('*')
.where(self.chats_table.chat_id == request.chat_id)
).get_sql()
async with self.pool_holder.pool.acquire() as session:
result = await session.execute(query)
chat = await result.fetchone()
if chat is None:
await context.abort(StatusCode.NOT_FOUND, 'not_found')
return self.enrich_chat(ChatPb(**chat))
@aiogrpc_request_wrapper()
async def list_chats(self, request, context, metadata):
query = (
PostgreSQLQuery
.from_(self.chats_table)
.select('*')
.where(self.chats_table.ban_until > request.banned_at_moment)
.limit(10)
).get_sql()
async with self.pool_holder.pool.acquire() as session:
results = await session.execute(query)
chats = await results.fetchall()
return ChatsPb(
chats=list(map(lambda x: self.enrich_chat(ChatPb(**x)), chats))
)
@aiogrpc_request_wrapper()
async def update_chat(self, request, context, metadata):
query = PostgreSQLQuery.update(self.chats_table)
for field in request.DESCRIPTOR.fields:
if field.containing_oneof and request.HasField(field.name):
field_value = getattr(request, field.name)
query = query.set(field.name, field_value)
query = query.where(self.chats_table.chat_id == request.chat_id).returning('*').get_sql()
async with self.pool_holder.pool.acquire() as session:
result = await session.execute(query)
chat = await result.fetchone()
return self.enrich_chat(ChatPb(**chat))

View File

@ -1,3 +0,0 @@
from .aioclient import IdmApi2GrpcClient
__all__ = ['IdmApi2GrpcClient']

View File

@ -1,66 +0,0 @@
syntax = "proto3";
package idm.api2.proto;
import "idm/api2/proto/location.proto";
message ChatData {
int64 id = 1;
string username = 2;
string language = 3;
bool is_system_messaging_enabled = 4;
bool is_discovery_enabled = 5;
int32 ban_until = 6;
string ban_message = 7;
bool is_admin = 8;
bool is_subscribed = 10;
int64 created_at = 11;
}
message ChatsData {
repeated ChatData chats = 1;
}
message CreateChatRequest {
int64 chat_id = 1;
string username = 2;
string language = 3;
}
message GetChatRequest {
int64 chat_id = 1;
}
message ListChatsRequest {
oneof banned_at_moment_oneof {
int32 banned_at_moment = 1;
}
}
message UpdateChatRequest {
int64 chat_id = 1;
oneof language_oneof {
string language = 2;
}
oneof is_system_messaging_enabled_oneof {
bool is_system_messaging_enabled = 3;
}
oneof is_discovery_enabled_oneof {
bool is_discovery_enabled = 4;
}
oneof ban_until_oneof {
int32 ban_until = 5;
}
oneof ban_message_oneof {
string ban_message = 6;
}
oneof is_admin_oneof {
bool is_admin = 7;
}
}
service Chats {
rpc create_chat(CreateChatRequest) returns (ChatData) {};
rpc get_chat(GetChatRequest) returns (ChatData) {};
rpc list_chats(ListChatsRequest) returns (ChatsData) {};
rpc update_chat(UpdateChatRequest) returns (ChatData) {};
}

View File

@ -1,7 +0,0 @@
syntax = "proto3";
package idm.api2.proto;
message Location {
float lat = 1;
float lon = 2;
}

View File

@ -1,78 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: idm/api2/proto/location.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='idm/api2/proto/location.proto',
package='idm.api2.proto',
syntax='proto3',
serialized_options=None,
create_key=_descriptor._internal_create_key,
serialized_pb=b'\n\x1didm/api2/proto/location.proto\x12\x0eidm.api2.proto\"$\n\x08Location\x12\x0b\n\x03lat\x18\x01 \x01(\x02\x12\x0b\n\x03lon\x18\x02 \x01(\x02\x62\x06proto3'
)
_LOCATION = _descriptor.Descriptor(
name='Location',
full_name='idm.api2.proto.Location',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='lat', full_name='idm.api2.proto.Location.lat', index=0,
number=1, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='lon', full_name='idm.api2.proto.Location.lon', index=1,
number=2, type=2, cpp_type=6, label=1,
has_default_value=False, default_value=float(0),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=49,
serialized_end=85,
)
DESCRIPTOR.message_types_by_name['Location'] = _LOCATION
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
Location = _reflection.GeneratedProtocolMessageType('Location', (_message.Message,), {
'DESCRIPTOR' : _LOCATION,
'__module__' : 'idm.api2.proto.location_pb2'
# @@protoc_insertion_point(class_scope:idm.api2.proto.Location)
})
_sym_db.RegisterMessage(Location)
# @@protoc_insertion_point(module_scope)

View File

@ -1,4 +0,0 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc

View File

@ -11,14 +11,8 @@ def images_install():
container_pull( container_pull(
name = "ubuntu", name = "ubuntu",
digest = "sha256:5403064f94b617f7975a19ba4d1a1299fd584397f6ee4393d0e16744ed11aab1", digest = "sha256:45ff0162921e61c004010a67db1bee7d039a677bed0cb294e61f2b47346d42b3",
registry = "index.docker.io", registry = "index.docker.io",
repository = "library/ubuntu", repository = "library/ubuntu",
tag = "20.04", tag = "20.10",
)
container_pull(
name = "jupyter",
registry = "index.docker.io",
repository = "jupyter/tensorflow-notebook",
tag = "latest",
) )

View File

@ -13,7 +13,7 @@ download_pkgs(
"libev4", "libev4",
"libgomp1", "libgomp1",
"libgoogle-perftools-dev", "libgoogle-perftools-dev",
"libprotobuf17", "libprotobuf23",
"libssl1.1", "libssl1.1",
], ],
) )

View File

@ -31,7 +31,7 @@ def error_log(e, level=logging.ERROR, **fields):
e = e.as_internal_dict() e = e.as_internal_dict()
e.update(fields) e.update(fields)
elif fields: elif fields:
e = {'error': str(e), **fields} e = {'error': repr(e), **fields}
logging.getLogger('error').log( logging.getLogger('error').log(
msg=e, msg=e,
level=level level=level

View File

@ -136,7 +136,7 @@ class RequestContext:
self.request_id = request_id or RequestContext.generate_request_id(request_id_length) self.request_id = request_id or RequestContext.generate_request_id(request_id_length)
self.default_fields = { self.default_fields = {
'bot_name': self.bot_name, 'bot_name': self.bot_name,
'chat_id': self.chat.id, 'chat_id': self.chat.chat_id,
'request_id': self.request_id, 'request_id': self.request_id,
} }

View File

@ -30,7 +30,7 @@ py3_image(
requirement("python_socks"), requirement("python_socks"),
requirement("tenacity"), requirement("tenacity"),
requirement("uvloop"), requirement("uvloop"),
"//idm/api2/aioclient", "//idm/api/aioclient",
requirement("aiobaseclient"), requirement("aiobaseclient"),
requirement("aiocrossref"), requirement("aiocrossref"),
requirement("aiokit"), requirement("aiokit"),

View File

@ -1,5 +1,5 @@
from aiokit import AioRootThing from aiokit import AioRootThing
from idm.api2.aioclient import IdmApi2GrpcClient from idm.api.aioclient import IdmApiGrpcClient
from izihawa_utils.importlib import import_object from izihawa_utils.importlib import import_object
from library.telegram.base import BaseTelegramClient from library.telegram.base import BaseTelegramClient
from nexus.bot.promotioner import Promotioner from nexus.bot.promotioner import Promotioner
@ -21,7 +21,7 @@ class TelegramApplication(AioRootThing):
) )
self.hub_client = HubGrpcClient(base_url=self.config['hub']['url']) self.hub_client = HubGrpcClient(base_url=self.config['hub']['url'])
self.idm_client = IdmApi2GrpcClient(base_url=self.config['idm']['url']) self.idm_client = IdmApiGrpcClient(base_url=self.config['idm']['url'])
self.meta_api_client = MetaApiGrpcClient(base_url=self.config['meta_api']['url']) self.meta_api_client = MetaApiGrpcClient(base_url=self.config['meta_api']['url'])
self.promotioner = Promotioner(promotions=self.config['promotions']) self.promotioner = Promotioner(promotions=self.config['promotions'])

View File

@ -6,7 +6,7 @@ from typing import Union
from grpc import StatusCode from grpc import StatusCode
from grpc.experimental.aio import AioRpcError from grpc.experimental.aio import AioRpcError
from idm.api2.proto.chats_service_pb2 import ChatData as Chat from idm.api.proto.chat_manager_service_pb2 import Chat as ChatPb
from izihawa_utils.exceptions import BaseError from izihawa_utils.exceptions import BaseError
from izihawa_utils.random import random_string from izihawa_utils.random import random_string
from library.logging import error_log from library.logging import error_log
@ -40,12 +40,12 @@ def get_language(event: events.ChatAction, chat):
return chat.lang_code return chat.lang_code
def is_banned(chat: Chat) -> bool: def is_banned(chat: ChatPb) -> bool:
return chat.ban_until is not None and datetime.utcnow().timestamp() < chat.ban_until return chat.ban_until is not None and datetime.utcnow().timestamp() < chat.ban_until
def is_subscribed(chat: Chat) -> bool: def is_subscribed(chat: ChatPb) -> bool:
return chat.is_subscribed or chat.id < 0 or chat.created_at > time.time() - 10 * 60 return chat.is_subscribed or chat.chat_id < 0 or chat.created_at > time.time() - 10 * 60
class ReadOnlyModeError(BaseError): class ReadOnlyModeError(BaseError):
@ -97,7 +97,7 @@ class BaseHandler(ABC):
session_id=session_id, session_id=session_id,
position=position, position=position,
request_id=request_context.request_id, request_id=request_context.request_id,
user_id=request_context.chat.id, user_id=request_context.chat.chat_id,
) )
async def resolve_scimag( async def resolve_scimag(
@ -136,7 +136,7 @@ class BaseHandler(ABC):
page_size=16, page_size=16,
request_id=request_context.request_id, request_id=request_context.request_id,
session_id=session_id, session_id=session_id,
user_id=request_context.chat.id, user_id=request_context.chat.chat_id,
) )
duplicates = [ duplicates = [
scored_document.typed_document.scitech scored_document.typed_document.scitech
@ -209,7 +209,7 @@ class BaseHandler(ABC):
) )
return chat return chat
async def _check_ban(self, event: events.ChatAction, request_context: RequestContext, chat: Chat): async def _check_ban(self, event: events.ChatAction, request_context: RequestContext, chat: ChatPb):
if is_banned(chat): if is_banned(chat):
if chat.ban_message is not None: if chat.ban_message is not None:
async with safe_execution( async with safe_execution(
@ -245,7 +245,7 @@ class BaseHandler(ABC):
) )
raise events.StopPropagation() raise events.StopPropagation()
async def _check_subscription(self, event: events.ChatAction, request_context: RequestContext, chat: Chat): async def _check_subscription(self, event: events.ChatAction, request_context: RequestContext, chat: ChatPb):
if ( if (
self.application.config['application']['is_subscription_required'] self.application.config['application']['is_subscription_required']
and self.is_subscription_required_for_handler and self.is_subscription_required_for_handler
@ -261,7 +261,7 @@ class BaseHandler(ABC):
).format(related_channel=self.application.config['telegram']['related_channel'])) ).format(related_channel=self.application.config['telegram']['related_channel']))
raise events.StopPropagation() raise events.StopPropagation()
def _has_access(self, chat: Chat) -> bool: def _has_access(self, chat: ChatPb) -> bool:
return True return True
async def _process_chat(self, event: events.ChatAction, request_id: str): async def _process_chat(self, event: events.ChatAction, request_id: str):
@ -271,8 +271,8 @@ class BaseHandler(ABC):
error_log(e) error_log(e)
event_chat = await event.get_chat() event_chat = await event.get_chat()
username = get_username(event, event_chat) username = get_username(event, event_chat)
chat = Chat( chat = ChatPb(
id=event.chat_id, chat_id=event.chat_id,
is_system_messaging_enabled=True, is_system_messaging_enabled=True,
is_discovery_enabled=True, is_discovery_enabled=True,
language='en', language='en',
@ -307,7 +307,7 @@ class BaseHandler(ABC):
await self._check_ban(event=event, request_context=request_context, chat=chat) await self._check_ban(event=event, request_context=request_context, chat=chat)
if self.should_reset_last_widget: if self.should_reset_last_widget:
self.reset_last_widget(request_context.chat.id) self.reset_last_widget(request_context.chat.chat_id)
async with safe_execution( async with safe_execution(
request_context=request_context, request_context=request_context,

View File

@ -19,7 +19,7 @@ class DownloadHandler(BaseCallbackQueryHandler):
document_id = int(event.pattern_match.group(3)) document_id = int(event.pattern_match.group(3))
position = int(event.pattern_match.group(4).decode()) position = int(event.pattern_match.group(4).decode())
self.application.user_manager.last_widget[request_context.chat.id] = None self.application.user_manager.last_widget[request_context.chat.chat_id] = None
request_context.add_default_fields(mode='download', session_id=session_id) request_context.add_default_fields(mode='download', session_id=session_id)
request_context.statbox(action='get', query=str(document_id), position=position) request_context.statbox(action='get', query=str(document_id), position=position)
@ -48,4 +48,4 @@ class DownloadHandler(BaseCallbackQueryHandler):
) )
else: else:
await remove_button(event, '⬇️', and_empty_too=True) await remove_button(event, '⬇️', and_empty_too=True)
self.application.user_manager.last_widget[request_context.chat.id] = None self.application.user_manager.last_widget[request_context.chat.chat_id] = None

View File

@ -41,7 +41,7 @@ class ReferencingToHandler(BaseCallbackQueryHandler):
serp, buttons = await document_list_widget.render() serp, buttons = await document_list_widget.render()
return await self.application.telegram_client.edit_message( return await self.application.telegram_client.edit_message(
request_context.chat.id, request_context.chat.chat_id,
message_id, message_id,
serp, serp,
buttons=buttons, buttons=buttons,

View File

@ -20,7 +20,7 @@ class RollHandler(BaseHandler):
language=request_context.chat.language, language=request_context.chat.language,
session_id=session_id, session_id=session_id,
request_id=request_context.request_id, request_id=request_context.request_id,
user_id=request_context.chat.id, user_id=request_context.chat.chat_id,
) )
scitech_view = await self.resolve_scitech( scitech_view = await self.resolve_scitech(
document_id=roll_response_pb.document_id, document_id=roll_response_pb.document_id,
@ -35,7 +35,7 @@ class RollHandler(BaseHandler):
) )
actions = [ actions = [
self.application.telegram_client.send_message( self.application.telegram_client.send_message(
request_context.chat.id, request_context.chat.chat_id,
view, view,
buttons=buttons, buttons=buttons,
), ),

View File

@ -52,7 +52,7 @@ class BaseSearchHandler(BaseHandler):
except AioRpcError as e: except AioRpcError as e:
actions = [ actions = [
self.application.telegram_client.delete_messages( self.application.telegram_client.delete_messages(
request_context.chat.id, request_context.chat.chat_id,
[message_id], [message_id],
) )
] ]
@ -118,7 +118,7 @@ class BaseSearchHandler(BaseHandler):
) )
return await asyncio.gather( return await asyncio.gather(
self.application.telegram_client.edit_message( self.application.telegram_client.edit_message(
request_context.chat.id, request_context.chat.chat_id,
message_id, message_id,
view, view,
buttons=buttons, buttons=buttons,
@ -127,7 +127,7 @@ class BaseSearchHandler(BaseHandler):
serp, buttons = await search_widget.render() serp, buttons = await search_widget.render()
return await self.application.telegram_client.edit_message( return await self.application.telegram_client.edit_message(
request_context.chat.id, request_context.chat.chat_id,
message_id, message_id,
serp, serp,
buttons=buttons, buttons=buttons,
@ -147,7 +147,7 @@ class SearchHandler(BaseSearchHandler):
'action': 'user_flood_ban', 'action': 'user_flood_ban',
'mode': 'search', 'mode': 'search',
'ban_timeout_seconds': ban_timeout, 'ban_timeout_seconds': ban_timeout,
'chat_id': request_context.chat.id, 'chat_id': request_context.chat.chat_id,
}) })
ban_reason = t( ban_reason = t(
'BAN_MESSAGE_TOO_MANY_REQUESTS', 'BAN_MESSAGE_TOO_MANY_REQUESTS',
@ -162,10 +162,10 @@ class SearchHandler(BaseSearchHandler):
)) ))
async def handler(self, event: events.ChatAction, request_context: RequestContext): async def handler(self, event: events.ChatAction, request_context: RequestContext):
ban_timeout = self.application.user_manager.check_search_ban_timeout(user_id=request_context.chat.id) ban_timeout = self.application.user_manager.check_search_ban_timeout(user_id=request_context.chat.chat_id)
if ban_timeout: if ban_timeout:
return await self.ban_handler(event, request_context, ban_timeout) return await self.ban_handler(event, request_context, ban_timeout)
self.application.user_manager.add_search_time(user_id=request_context.chat.id, search_time=time.time()) self.application.user_manager.add_search_time(user_id=request_context.chat.chat_id, search_time=time.time())
search_prefix = event.pattern_match.group(1) search_prefix = event.pattern_match.group(1)
query = event.pattern_match.group(2) query = event.pattern_match.group(2)
@ -178,7 +178,7 @@ class SearchHandler(BaseSearchHandler):
prefetch_message = await event.reply( prefetch_message = await event.reply(
t("SEARCHING", language=request_context.chat.language), t("SEARCHING", language=request_context.chat.language),
) )
self.application.user_manager.last_widget[request_context.chat.id] = prefetch_message.id self.application.user_manager.last_widget[request_context.chat.chat_id] = prefetch_message.id
try: try:
await self.do_search( await self.do_search(
event, request_context, prefetch_message, event, request_context, prefetch_message,

View File

@ -32,7 +32,7 @@ class StartHandler(BaseSearchHandler):
prefetch_message = await request_message.reply( prefetch_message = await request_message.reply(
t("SEARCHING", language=request_context.chat.language), t("SEARCHING", language=request_context.chat.language),
) )
self.application.user_manager.last_widget[request_context.chat.id] = prefetch_message.id self.application.user_manager.last_widget[request_context.chat.chat_id] = prefetch_message.id
await asyncio.gather( await asyncio.gather(
event.delete(), event.delete(),
self.do_search(event, request_context, prefetch_message, query=query, self.do_search(event, request_context, prefetch_message, query=query,

View File

@ -30,7 +30,7 @@ class ViewHandler(BaseHandler):
request_context.add_default_fields(mode='view', session_id=session_id) request_context.add_default_fields(mode='view', session_id=session_id)
request_context.statbox(action='view', query=str(document_id), position=position) request_context.statbox(action='view', query=str(document_id), position=position)
found_old_widget = old_message_id == self.application.user_manager.last_widget.get(request_context.chat.id) found_old_widget = old_message_id == self.application.user_manager.last_widget.get(request_context.chat.chat_id)
try: try:
if found_old_widget: if found_old_widget:
@ -41,11 +41,11 @@ class ViewHandler(BaseHandler):
functions.messages.GetMessagesRequest(id=[old_message_id]) functions.messages.GetMessagesRequest(id=[old_message_id])
)).messages[0] )).messages[0]
prefetch_message = await self.application.telegram_client.send_message( prefetch_message = await self.application.telegram_client.send_message(
request_context.chat.id, request_context.chat.chat_id,
t("SEARCHING", language=request_context.chat.language), t("SEARCHING", language=request_context.chat.language),
reply_to=old_message.reply_to_msg_id, reply_to=old_message.reply_to_msg_id,
) )
self.application.user_manager.last_widget[request_context.chat.id] = prefetch_message.id self.application.user_manager.last_widget[request_context.chat.chat_id] = prefetch_message.id
message_id = prefetch_message.id message_id = prefetch_message.id
link_preview = True link_preview = True
@ -81,7 +81,7 @@ class ViewHandler(BaseHandler):
) )
actions = [ actions = [
self.application.telegram_client.edit_message( self.application.telegram_client.edit_message(
request_context.chat.id, request_context.chat.chat_id,
message_id, message_id,
view, view,
buttons=buttons, buttons=buttons,
@ -92,7 +92,7 @@ class ViewHandler(BaseHandler):
if not found_old_widget: if not found_old_widget:
actions.append( actions.append(
self.application.telegram_client.delete_messages( self.application.telegram_client.delete_messages(
request_context.chat.id, request_context.chat.chat_id,
[old_message_id], [old_message_id],
) )
) )

View File

@ -26,7 +26,7 @@ class VoteHandler(BaseCallbackQueryHandler):
vote=VotePb( vote=VotePb(
document_id=document_id, document_id=document_id,
value=vote_value, value=vote_value,
voter_id=request_context.chat.id, voter_id=request_context.chat.chat_id,
), ),
) )
@ -43,7 +43,7 @@ class VoteHandler(BaseCallbackQueryHandler):
# ToDo: Generalize nexus.views.telegram.common.remove_button and use it here # ToDo: Generalize nexus.views.telegram.common.remove_button and use it here
return await asyncio.gather( return await asyncio.gather(
self.application.telegram_client.edit_message( self.application.telegram_client.edit_message(
request_context.chat.id, request_context.chat.chat_id,
message.id, message.id,
message.text, message.text,
buttons=None, buttons=None,

View File

@ -1,9 +1,9 @@
from idm.api2.proto.chats_service_pb2 import ChatData as Chat from idm.api.proto.chat_manager_service_pb2 import Chat as ChatPb
from nexus.bot.application import TelegramApplication from nexus.bot.application import TelegramApplication
class AdminWidget: class AdminWidget:
def __init__(self, application: TelegramApplication, chat: Chat): def __init__(self, application: TelegramApplication, chat: ChatPb):
self.application = application self.application = application
self.chat = chat self.chat = chat

View File

@ -1,25 +1,25 @@
import time import time
from idm.api2.proto.chats_service_pb2 import ChatData as Chat from idm.api.proto.chat_manager_service_pb2 import Chat as ChatPb
from nexus.bot.application import TelegramApplication from nexus.bot.application import TelegramApplication
class BanlistWidget: class BanlistWidget:
def __init__(self, application: TelegramApplication, chat: Chat): def __init__(self, application: TelegramApplication, chat: ChatPb):
self.application = application self.application = application
self.chat = chat self.chat = chat
async def render(self, chat_list: list[Chat]): async def render(self, chat_list: list[ChatPb]):
if not chat_list: if not chat_list:
return 'Nobody is banned' return 'Nobody is banned'
separator = '------------\n' separator = '------------\n'
return separator.join( return separator.join(
map( map(
lambda chat: ( lambda chat: (
f'```{chat.username} ({chat.id})\n' f'```{chat.username} ({chat.chat_id})\n'
f'Until: {time.ctime(chat.ban_until)}\n' f'Until: {time.ctime(chat.ban_until)}\n'
f'Message: {chat.ban_message}```\n' f'Message: {chat.ban_message}```\n'
f'/unban_{chat.id}\n' f'/unban_{chat.chat_id}\n'
), ),
chat_list chat_list
) )

View File

@ -1,6 +1,6 @@
from typing import Optional from typing import Optional
from idm.api2.proto.chats_service_pb2 import ChatData as Chat from idm.api.proto.chat_manager_service_pb2 import Chat as ChatPb
from nexus.bot.application import TelegramApplication from nexus.bot.application import TelegramApplication
from nexus.meta_api.proto.meta_search_service_pb2 import \ from nexus.meta_api.proto.meta_search_service_pb2 import \
ScoredDocument as ScoredDocumentPb ScoredDocument as ScoredDocumentPb
@ -14,7 +14,7 @@ class DocumentListWidget:
def __init__( def __init__(
self, self,
application: TelegramApplication, application: TelegramApplication,
chat: Chat, chat: ChatPb,
session_id: str, session_id: str,
message_id: int, message_id: int,
request_id: str, request_id: str,
@ -32,7 +32,7 @@ class DocumentListWidget:
@staticmethod @staticmethod
async def create( async def create(
application: TelegramApplication, application: TelegramApplication,
chat: Chat, chat: ChatPb,
session_id: str, session_id: str,
message_id: int, message_id: int,
request_id: str, request_id: str,
@ -58,7 +58,7 @@ class DocumentListWidget:
position=0, position=0,
request_id=self.request_id, request_id=self.request_id,
session_id=self.session_id, session_id=self.session_id,
user_id=self.chat.id, user_id=self.chat.chat_id,
) )
self._response = await self.application.meta_api_client.search( self._response = await self.application.meta_api_client.search(
schemas=('scimag',), schemas=('scimag',),
@ -66,7 +66,7 @@ class DocumentListWidget:
page=self.page, page=self.page,
request_id=self.request_id, request_id=self.request_id,
session_id=self.session_id, session_id=self.session_id,
user_id=self.chat.id, user_id=self.chat.chat_id,
) )
@property @property

View File

@ -1,6 +1,6 @@
from typing import Optional from typing import Optional
from idm.api2.proto.chats_service_pb2 import ChatData as Chat from idm.api.proto.chat_manager_service_pb2 import Chat as ChatPb
from nexus.bot.application import TelegramApplication from nexus.bot.application import TelegramApplication
from nexus.meta_api.proto.meta_search_service_pb2 import \ from nexus.meta_api.proto.meta_search_service_pb2 import \
ScoredDocument as ScoredDocumentPb ScoredDocument as ScoredDocumentPb
@ -21,7 +21,7 @@ class SearchWidget:
def __init__( def __init__(
self, self,
application: TelegramApplication, application: TelegramApplication,
chat: Chat, chat: ChatPb,
session_id: str, session_id: str,
message_id: int, message_id: int,
request_id: str, request_id: str,
@ -41,7 +41,7 @@ class SearchWidget:
@staticmethod @staticmethod
async def create( async def create(
application: TelegramApplication, application: TelegramApplication,
chat: Chat, chat: ChatPb,
session_id: str, session_id: str,
message_id: int, message_id: int,
request_id: str, request_id: str,
@ -70,7 +70,7 @@ class SearchWidget:
page_size=self.application.config['application']['page_size'], page_size=self.application.config['application']['page_size'],
request_id=self.request_id, request_id=self.request_id,
session_id=self.session_id, session_id=self.session_id,
user_id=self.chat.id, user_id=self.chat.chat_id,
language=self.chat.language, language=self.chat.language,
) )

View File

@ -1,6 +1,6 @@
from typing import Optional from typing import Optional
from idm.api2.proto.chats_service_pb2 import ChatData as Chat from idm.api.proto.chat_manager_service_pb2 import Chat as ChatPb
from nexus.bot.application import TelegramApplication from nexus.bot.application import TelegramApplication
from nexus.translations import t from nexus.translations import t
from telethon import Button from telethon import Button
@ -34,7 +34,7 @@ class SettingsWidget:
def __init__( def __init__(
self, self,
application: TelegramApplication, application: TelegramApplication,
chat: Chat, chat: ChatPb,
has_language_buttons: Optional[bool] = None, has_language_buttons: Optional[bool] = None,
is_group_mode: bool = False, is_group_mode: bool = False,
request_id: Optional[str] = None, request_id: Optional[str] = None,
@ -54,7 +54,7 @@ class SettingsWidget:
async def _switch_language(self, target_language: str): async def _switch_language(self, target_language: str):
self.chat = await self.application.idm_client.update_chat( self.chat = await self.application.idm_client.update_chat(
chat_id=self.chat.id, chat_id=self.chat.chat_id,
language=target_language, language=target_language,
request_id=self.request_id, request_id=self.request_id,
) )
@ -62,7 +62,7 @@ class SettingsWidget:
async def _switch_system_messaging(self, is_system_messaging_enabled: str): async def _switch_system_messaging(self, is_system_messaging_enabled: str):
self.chat = await self.application.idm_client.update_chat( self.chat = await self.application.idm_client.update_chat(
chat_id=self.chat.id, chat_id=self.chat.chat_id,
is_system_messaging_enabled=bool(int(is_system_messaging_enabled)), is_system_messaging_enabled=bool(int(is_system_messaging_enabled)),
request_id=self.request_id, request_id=self.request_id,
) )
@ -70,7 +70,7 @@ class SettingsWidget:
async def _switch_discovery(self, is_discovery_enabled: str): async def _switch_discovery(self, is_discovery_enabled: str):
self.chat = await self.application.idm_client.update_chat( self.chat = await self.application.idm_client.update_chat(
chat_id=self.chat.id, chat_id=self.chat.chat_id,
is_discovery_enabled=bool(int(is_discovery_enabled)), is_discovery_enabled=bool(int(is_discovery_enabled)),
request_id=self.request_id, request_id=self.request_id,
) )

View File

@ -17,10 +17,7 @@ py3_image(
base = "//images/production:base-python-image", base = "//images/production:base-python-image",
data = [ data = [
"configs/base.yaml", "configs/base.yaml",
"configs/development.yaml",
"configs/logging.yaml", "configs/logging.yaml",
"configs/production.yaml",
"configs/testing.yaml",
], ],
main = "main.py", main = "main.py",
srcs_version = "PY3ONLY", srcs_version = "PY3ONLY",
@ -37,7 +34,7 @@ py3_image(
requirement("python-socks"), requirement("python-socks"),
requirement("tenacity"), requirement("tenacity"),
requirement("uvloop"), requirement("uvloop"),
"//idm/api2/proto:idm_proto_py", "//idm/api/proto:idm_proto_py",
requirement("giogrobid"), requirement("giogrobid"),
"//library/aiogrpctools", "//library/aiogrpctools",
requirement("aiokit"), requirement("aiokit"),

View File

@ -1,66 +1,9 @@
# Nexus Search: Hub API # Nexus Search: Hub API
`Hub` is a daemon responsible for retrieving files and sending them to users. This version has cut `configs` `Hub` is a daemon responsible for retrieving files and sending them to users.
subdirectory due to hard reliance of configs on the network infrastructure you are using.
You have to write your own configs taking example below into account.
The bot requires two other essential parts: The bot requires two other essential parts:
- Postgres Database - Postgres Database
- IPFS Daemon - IPFS Daemon
or their substitutions or their substitutions
## Sample `configs/base.yaml`
```yaml
---
application:
# Look at the special Postgres `sharience` table to retrieve user-sent files
is_sharience_enabled: true
maintenance_picture_url:
# Used in logging
service_name: nexus-hub
# Store file hashes into operation log
should_store_hashes: true
database:
database: nexus
host:
password: '{{ DATABASE_PASSWORD }}'
username: '{{ DATABASE_USERNAME }}'
grobid:
url:
grpc:
# Listen address
address: 0.0.0.0
# Listen port
port: 9090
ipfs:
address:
port: 4001
log_path: '/var/log/nexus-hub/{{ ENV_TYPE }}'
meta_api:
url:
pylon:
# Proxy used in `pylon` retriever to download files
proxy: socks5://127.0.0.1:9050
# Proxy used in `pylon` retriever to get metadata
resolve_proxy: socks5://127.0.0.1:9050
telegram:
# Telegram App Hash from https://my.telegram.org/
app_hash: '{{ APP_HASH }}'
# Telegram App ID from https://my.telegram.org/
app_id: 00000
# External bot name shown in messages to users
bot_external_name: libgen_scihub_bot
# Internal bot name used in logging
bot_name: nexus-bot
bot_token: '{{ BOT_TOKEN }}'
# Telethon database for keeping cache
database:
session_id: nexus-hub
# Frequency of updating downloading progress
progress_throttle_seconds: 5
# Send files using stored telegram_file_id
should_use_telegram_file_id: true
```

View File

@ -6,7 +6,7 @@ py_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
requirement("grpcio"), requirement("grpcio"),
"//idm/api2/proto:idm_proto_py", "//idm/api/proto:idm_proto_py",
requirement("aiokit"), requirement("aiokit"),
"//nexus/hub/proto:hub_grpc_py", "//nexus/hub/proto:hub_grpc_py",
"//nexus/hub/proto:hub_proto_py", "//nexus/hub/proto:hub_proto_py",

View File

@ -2,7 +2,7 @@ from typing import Optional
from aiokit import AioThing from aiokit import AioThing
from grpc.experimental.aio import insecure_channel from grpc.experimental.aio import insecure_channel
from idm.api2.proto.chats_service_pb2 import ChatData as ChatDataPb from idm.api.proto.chat_manager_service_pb2 import Chat as ChatPb
from nexus.hub.proto.delivery_service_pb2 import \ from nexus.hub.proto.delivery_service_pb2 import \
StartDeliveryRequest as StartDeliveryRequestPb StartDeliveryRequest as StartDeliveryRequestPb
from nexus.hub.proto.delivery_service_pb2 import \ from nexus.hub.proto.delivery_service_pb2 import \
@ -42,7 +42,7 @@ class HubGrpcClient(AioThing):
async def start_delivery( async def start_delivery(
self, self,
typed_document_pb: TypedDocumentPb, typed_document_pb: TypedDocumentPb,
chat: ChatDataPb, chat: ChatPb,
request_id: Optional[str], request_id: Optional[str],
session_id: Optional[str], session_id: Optional[str],
) -> StartDeliveryResponsePb: ) -> StartDeliveryResponsePb:
@ -58,7 +58,7 @@ class HubGrpcClient(AioThing):
self, self,
telegram_document: bytes, telegram_document: bytes,
telegram_file_id: str, telegram_file_id: str,
chat: ChatDataPb, chat: ChatPb,
request_id: Optional[str] = None, request_id: Optional[str] = None,
session_id: Optional[str] = None, session_id: Optional[str] = None,
) -> SubmitResponsePb: ) -> SubmitResponsePb:

View File

@ -0,0 +1,51 @@
---
application:
# Enable special Postgres `sharience` table to retrieve user-sent files
is_sharience_enabled: true
# URL to the picture shown while maintenance
maintenance_picture_url:
# Used in logging
service_name: nexus-hub
# Store file hashes into operation log
should_store_hashes: true
database:
database: nexus
host:
password: '{{ DATABASE_PASSWORD }}'
username: '{{ DATABASE_USERNAME }}'
grobid:
url:
grpc:
# Listen address
address: 0.0.0.0
# Listen port
port: 9090
ipfs:
address:
port: 4001
log_path: '/var/log/nexus-hub/{{ ENV_TYPE }}'
meta_api:
url:
pylon:
# Proxy used in `pylon` retriever to download files
proxy: socks5://127.0.0.1:9050
# Proxy used in `pylon` retriever to get metadata
resolve_proxy: socks5://127.0.0.1:9050
telegram:
# Telegram App Hash from https://my.telegram.org/
app_hash: '{{ APP_HASH }}'
# Telegram App ID from https://my.telegram.org/
app_id: 00000
# External bot name shown in messages to users
bot_external_name: libgen_scihub_bot
# Internal bot name used in logging
bot_name: nexus-hub
bot_token: '{{ BOT_TOKEN }}'
# Telethon database for keeping cache
database:
session_id: nexus-hub
# Frequency of updating downloading progress
progress_throttle_seconds: 5
# Send files using stored telegram_file_id
should_use_telegram_file_id: true

View File

@ -0,0 +1,76 @@
---
logging:
disable_existing_loggers: false
formatters:
base:
class: library.logging.formatters.BaseFormatter
default:
class: library.logging.formatters.DefaultFormatter
traceback:
class: library.logging.formatters.TracebackFormatter
handlers:
debug:
class: logging.StreamHandler
formatter: default
level: DEBUG
stream: 'ext://sys.stderr'
error:
class: library.logging.handlers.BaseFileHandler
filename: '{{ log_path }}/error.log'
formatter: default
level: ERROR
operation:
class: library.logging.handlers.BaseFileHandler
filename: '{{ log_path }}/operation.log'
formatter: base
level: DEBUG
statbox:
class: library.logging.handlers.BaseFileHandler
filename: '{{ log_path }}/statbox.log'
formatter: default
level: INFO
traceback:
class: library.logging.handlers.BaseFileHandler
filename: '{{ log_path }}/traceback.log'
formatter: traceback
level: ERROR
warning:
class: library.logging.handlers.BaseFileHandler
filename: '{{ log_path }}/warning.log'
formatter: default
level: WARNING
loggers:
aiokafka:
handlers:
- error
propagate: false
debug:
handlers:
- debug
propagate: false
error:
handlers:
- traceback
- error
- warning
propagate: false
operation:
handlers:
- operation
propagate: false
statbox:
handlers:
- statbox
propagate: false
telethon:
handlers:
- error
propagate: false
root:
handlers:
- debug
- error
- warning
level: DEBUG
version: 1

View File

@ -9,7 +9,7 @@ proto_library(
"*.proto", "*.proto",
]), ]),
deps = [ deps = [
"//idm/api2/proto:idm_proto", "//idm/api/proto:idm_proto",
"//nexus/models/proto:models_proto", "//nexus/models/proto:models_proto",
"@com_google_protobuf//:wrappers_proto", "@com_google_protobuf//:wrappers_proto",
], ],

View File

@ -2,11 +2,11 @@ syntax = "proto3";
package nexus.hub.proto; package nexus.hub.proto;
import "nexus/models/proto/typed_document.proto"; import "nexus/models/proto/typed_document.proto";
import "idm/api2/proto/chats_service.proto"; import "idm/api/proto/chat_manager_service.proto";
message StartDeliveryRequest { message StartDeliveryRequest {
nexus.models.proto.TypedDocument typed_document = 1; nexus.models.proto.TypedDocument typed_document = 1;
idm.api2.proto.ChatData chat = 2; idm.api.proto.Chat chat = 2;
} }
message StartDeliveryResponse { message StartDeliveryResponse {

View File

@ -12,8 +12,9 @@ from google.protobuf import symbol_database as _symbol_database
_sym_db = _symbol_database.Default() _sym_db = _symbol_database.Default()
from idm.api2.proto import \ from idm.api.proto import \
chats_service_pb2 as idm_dot_api2_dot_proto_dot_chats__service__pb2 chat_manager_service_pb2 as \
idm_dot_api_dot_proto_dot_chat__manager__service__pb2
from nexus.models.proto import \ from nexus.models.proto import \
typed_document_pb2 as nexus_dot_models_dot_proto_dot_typed__document__pb2 typed_document_pb2 as nexus_dot_models_dot_proto_dot_typed__document__pb2
@ -23,9 +24,9 @@ DESCRIPTOR = _descriptor.FileDescriptor(
syntax='proto3', syntax='proto3',
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
serialized_pb=b'\n&nexus/hub/proto/delivery_service.proto\x12\x0fnexus.hub.proto\x1a\'nexus/models/proto/typed_document.proto\x1a\"idm/api2/proto/chats_service.proto\"y\n\x14StartDeliveryRequest\x12\x39\n\x0etyped_document\x18\x01 \x01(\x0b\x32!.nexus.models.proto.TypedDocument\x12&\n\x04\x63hat\x18\x02 \x01(\x0b\x32\x18.idm.api2.proto.ChatData\"\x99\x01\n\x15StartDeliveryResponse\x12=\n\x06status\x18\x01 \x01(\x0e\x32-.nexus.hub.proto.StartDeliveryResponse.Status\"A\n\x06Status\x12\x06\n\x02OK\x10\x00\x12\x16\n\x12TOO_MANY_DOWNLOADS\x10\x01\x12\x17\n\x13\x41LREADY_DOWNLOADING\x10\x03\x32m\n\x08\x44\x65livery\x12\x61\n\x0estart_delivery\x12%.nexus.hub.proto.StartDeliveryRequest\x1a&.nexus.hub.proto.StartDeliveryResponse\"\x00\x62\x06proto3' serialized_pb=b'\n&nexus/hub/proto/delivery_service.proto\x12\x0fnexus.hub.proto\x1a\'nexus/models/proto/typed_document.proto\x1a(idm/api/proto/chat_manager_service.proto\"t\n\x14StartDeliveryRequest\x12\x39\n\x0etyped_document\x18\x01 \x01(\x0b\x32!.nexus.models.proto.TypedDocument\x12!\n\x04\x63hat\x18\x02 \x01(\x0b\x32\x13.idm.api.proto.Chat\"\x99\x01\n\x15StartDeliveryResponse\x12=\n\x06status\x18\x01 \x01(\x0e\x32-.nexus.hub.proto.StartDeliveryResponse.Status\"A\n\x06Status\x12\x06\n\x02OK\x10\x00\x12\x16\n\x12TOO_MANY_DOWNLOADS\x10\x01\x12\x17\n\x13\x41LREADY_DOWNLOADING\x10\x02\x32m\n\x08\x44\x65livery\x12\x61\n\x0estart_delivery\x12%.nexus.hub.proto.StartDeliveryRequest\x1a&.nexus.hub.proto.StartDeliveryResponse\"\x00\x62\x06proto3'
, ,
dependencies=[nexus_dot_models_dot_proto_dot_typed__document__pb2.DESCRIPTOR,idm_dot_api2_dot_proto_dot_chats__service__pb2.DESCRIPTOR,]) dependencies=[nexus_dot_models_dot_proto_dot_typed__document__pb2.DESCRIPTOR,idm_dot_api_dot_proto_dot_chat__manager__service__pb2.DESCRIPTOR,])
@ -47,15 +48,15 @@ _STARTDELIVERYRESPONSE_STATUS = _descriptor.EnumDescriptor(
type=None, type=None,
create_key=_descriptor._internal_create_key), create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor( _descriptor.EnumValueDescriptor(
name='ALREADY_DOWNLOADING', index=2, number=3, name='ALREADY_DOWNLOADING', index=2, number=2,
serialized_options=None, serialized_options=None,
type=None, type=None,
create_key=_descriptor._internal_create_key), create_key=_descriptor._internal_create_key),
], ],
containing_type=None, containing_type=None,
serialized_options=None, serialized_options=None,
serialized_start=348, serialized_start=349,
serialized_end=413, serialized_end=414,
) )
_sym_db.RegisterEnumDescriptor(_STARTDELIVERYRESPONSE_STATUS) _sym_db.RegisterEnumDescriptor(_STARTDELIVERYRESPONSE_STATUS)
@ -94,8 +95,8 @@ _STARTDELIVERYREQUEST = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=136, serialized_start=142,
serialized_end=257, serialized_end=258,
) )
@ -127,12 +128,12 @@ _STARTDELIVERYRESPONSE = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=260, serialized_start=261,
serialized_end=413, serialized_end=414,
) )
_STARTDELIVERYREQUEST.fields_by_name['typed_document'].message_type = nexus_dot_models_dot_proto_dot_typed__document__pb2._TYPEDDOCUMENT _STARTDELIVERYREQUEST.fields_by_name['typed_document'].message_type = nexus_dot_models_dot_proto_dot_typed__document__pb2._TYPEDDOCUMENT
_STARTDELIVERYREQUEST.fields_by_name['chat'].message_type = idm_dot_api2_dot_proto_dot_chats__service__pb2._CHATDATA _STARTDELIVERYREQUEST.fields_by_name['chat'].message_type = idm_dot_api_dot_proto_dot_chat__manager__service__pb2._CHAT
_STARTDELIVERYRESPONSE.fields_by_name['status'].enum_type = _STARTDELIVERYRESPONSE_STATUS _STARTDELIVERYRESPONSE.fields_by_name['status'].enum_type = _STARTDELIVERYRESPONSE_STATUS
_STARTDELIVERYRESPONSE_STATUS.containing_type = _STARTDELIVERYRESPONSE _STARTDELIVERYRESPONSE_STATUS.containing_type = _STARTDELIVERYRESPONSE
DESCRIPTOR.message_types_by_name['StartDeliveryRequest'] = _STARTDELIVERYREQUEST DESCRIPTOR.message_types_by_name['StartDeliveryRequest'] = _STARTDELIVERYREQUEST
@ -162,8 +163,8 @@ _DELIVERY = _descriptor.ServiceDescriptor(
index=0, index=0,
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
serialized_start=415, serialized_start=416,
serialized_end=524, serialized_end=525,
methods=[ methods=[
_descriptor.MethodDescriptor( _descriptor.MethodDescriptor(
name='start_delivery', name='start_delivery',

View File

@ -1,12 +1,12 @@
syntax = "proto3"; syntax = "proto3";
package nexus.hub.proto; package nexus.hub.proto;
import "idm/api2/proto/chats_service.proto"; import "idm/api/proto/chat_manager_service.proto";
message SubmitRequest { message SubmitRequest {
bytes telegram_document = 1; bytes telegram_document = 1;
string telegram_file_id = 2; string telegram_file_id = 2;
idm.api2.proto.ChatData chat = 3; idm.api.proto.Chat chat = 3;
} }
message SubmitResponse { } message SubmitResponse { }

View File

@ -12,8 +12,9 @@ from google.protobuf import symbol_database as _symbol_database
_sym_db = _symbol_database.Default() _sym_db = _symbol_database.Default()
from idm.api2.proto import \ from idm.api.proto import \
chats_service_pb2 as idm_dot_api2_dot_proto_dot_chats__service__pb2 chat_manager_service_pb2 as \
idm_dot_api_dot_proto_dot_chat__manager__service__pb2
DESCRIPTOR = _descriptor.FileDescriptor( DESCRIPTOR = _descriptor.FileDescriptor(
name='nexus/hub/proto/submitter_service.proto', name='nexus/hub/proto/submitter_service.proto',
@ -21,9 +22,9 @@ DESCRIPTOR = _descriptor.FileDescriptor(
syntax='proto3', syntax='proto3',
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
serialized_pb=b'\n\'nexus/hub/proto/submitter_service.proto\x12\x0fnexus.hub.proto\x1a\"idm/api2/proto/chats_service.proto\"R\n\rSubmitRequest\x12\x19\n\x11telegram_document\x18\x01 \x01(\x0c\x12&\n\x04\x63hat\x18\x02 \x01(\x0b\x32\x18.idm.api2.proto.ChatData\"\x10\n\x0eSubmitResponse2X\n\tSubmitter\x12K\n\x06submit\x12\x1e.nexus.hub.proto.SubmitRequest\x1a\x1f.nexus.hub.proto.SubmitResponse\"\x00\x62\x06proto3' serialized_pb=b'\n\'nexus/hub/proto/submitter_service.proto\x12\x0fnexus.hub.proto\x1a(idm/api/proto/chat_manager_service.proto\"g\n\rSubmitRequest\x12\x19\n\x11telegram_document\x18\x01 \x01(\x0c\x12\x18\n\x10telegram_file_id\x18\x02 \x01(\t\x12!\n\x04\x63hat\x18\x03 \x01(\x0b\x32\x13.idm.api.proto.Chat\"\x10\n\x0eSubmitResponse2X\n\tSubmitter\x12K\n\x06submit\x12\x1e.nexus.hub.proto.SubmitRequest\x1a\x1f.nexus.hub.proto.SubmitResponse\"\x00\x62\x06proto3'
, ,
dependencies=[idm_dot_api2_dot_proto_dot_chats__service__pb2.DESCRIPTOR,]) dependencies=[idm_dot_api_dot_proto_dot_chat__manager__service__pb2.DESCRIPTOR,])
@ -44,8 +45,15 @@ _SUBMITREQUEST = _descriptor.Descriptor(
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor( _descriptor.FieldDescriptor(
name='chat', full_name='nexus.hub.proto.SubmitRequest.chat', index=1, name='telegram_file_id', full_name='nexus.hub.proto.SubmitRequest.telegram_file_id', index=1,
number=2, type=11, cpp_type=10, label=1, number=2, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='chat', full_name='nexus.hub.proto.SubmitRequest.chat', index=2,
number=3, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None, has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
@ -62,8 +70,8 @@ _SUBMITREQUEST = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=96, serialized_start=102,
serialized_end=178, serialized_end=205,
) )
@ -87,11 +95,11 @@ _SUBMITRESPONSE = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=180, serialized_start=207,
serialized_end=196, serialized_end=223,
) )
_SUBMITREQUEST.fields_by_name['chat'].message_type = idm_dot_api2_dot_proto_dot_chats__service__pb2._CHATDATA _SUBMITREQUEST.fields_by_name['chat'].message_type = idm_dot_api_dot_proto_dot_chat__manager__service__pb2._CHAT
DESCRIPTOR.message_types_by_name['SubmitRequest'] = _SUBMITREQUEST DESCRIPTOR.message_types_by_name['SubmitRequest'] = _SUBMITREQUEST
DESCRIPTOR.message_types_by_name['SubmitResponse'] = _SUBMITRESPONSE DESCRIPTOR.message_types_by_name['SubmitResponse'] = _SUBMITRESPONSE
_sym_db.RegisterFileDescriptor(DESCRIPTOR) _sym_db.RegisterFileDescriptor(DESCRIPTOR)
@ -119,8 +127,8 @@ _SUBMITTER = _descriptor.ServiceDescriptor(
index=0, index=0,
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
serialized_start=198, serialized_start=225,
serialized_end=286, serialized_end=313,
methods=[ methods=[
_descriptor.MethodDescriptor( _descriptor.MethodDescriptor(
name='submit', name='submit',

View File

@ -70,7 +70,7 @@ class BaseHubService(BaseService):
buttons=buttons, buttons=buttons,
caption=f"{document_view.generate_body(language=request_context.chat.language, limit=512)}\n" caption=f"{document_view.generate_body(language=request_context.chat.language, limit=512)}\n"
f"@{self.bot_external_name}", f"@{self.bot_external_name}",
entity=request_context.chat.id, entity=request_context.chat.chat_id,
file=file, file=file,
progress_callback=progress_callback progress_callback=progress_callback
) )

View File

@ -76,7 +76,7 @@ class DownloadTask:
) )
) )
self.delivery_service.user_manager.add_task(self.request_context.chat.id, self.document_view.id) self.delivery_service.user_manager.add_task(self.request_context.chat.chat_id, self.document_view.id)
self.delivery_service.downloadings.add(self) self.delivery_service.downloadings.add(self)
self.task.add_done_callback(self.done_callback) self.task.add_done_callback(self.done_callback)
@ -84,7 +84,7 @@ class DownloadTask:
def done_callback(self, f): def done_callback(self, f):
self.delivery_service.downloadings.remove(self) self.delivery_service.downloadings.remove(self)
self.delivery_service.user_manager.remove_task( self.delivery_service.user_manager.remove_task(
self.request_context.chat.id, self.request_context.chat.chat_id,
self.document_view.id, self.document_view.id,
) )
@ -93,7 +93,7 @@ class DownloadTask:
async def _on_fail(): async def _on_fail():
await self.delivery_service.telegram_client.send_message( await self.delivery_service.telegram_client.send_message(
request_context.chat.id, request_context.chat.chat_id,
t('MAINTENANCE', language=request_context.chat.language).format( t('MAINTENANCE', language=request_context.chat.language).format(
maintenance_picture_url=self.delivery_service.maintenance_picture_url maintenance_picture_url=self.delivery_service.maintenance_picture_url
), ),
@ -165,7 +165,7 @@ class DownloadTask:
progress_callback=progress_bar_upload.callback, progress_callback=progress_bar_upload.callback,
request_context=self.request_context, request_context=self.request_context,
session_id=self.session_id, session_id=self.session_id,
voting=not is_group_or_channel(self.request_context.chat.id), voting=not is_group_or_channel(self.request_context.chat.chat_id),
) )
request_context.statbox( request_context.statbox(
action='uploaded', action='uploaded',
@ -191,7 +191,7 @@ class DownloadTask:
finally: finally:
downloads_gauge.dec() downloads_gauge.dec()
messages = filter_none([progress_bar_download.message]) messages = filter_none([progress_bar_download.message])
await self.delivery_service.telegram_client.delete_messages(request_context.chat.id, messages) await self.delivery_service.telegram_client.delete_messages(request_context.chat.chat_id, messages)
async def process_resp(self, resp, progress_bar, collected, filesize): async def process_resp(self, resp, progress_bar, collected, filesize):
progress_bar.set_source(get_fancy_name(resp.source)) progress_bar.set_source(get_fancy_name(resp.source))
@ -206,7 +206,7 @@ class DownloadTask:
async def respond_not_found(self, request_context: RequestContext, document_view): async def respond_not_found(self, request_context: RequestContext, document_view):
return await self.delivery_service.telegram_client.send_message( return await self.delivery_service.telegram_client.send_message(
request_context.chat.id, request_context.chat.chat_id,
t("SOURCES_UNAVAILABLE", language=request_context.chat.language).format( t("SOURCES_UNAVAILABLE", language=request_context.chat.language).format(
document=document_view.get_robust_title() document=document_view.get_robust_title()
), ),
@ -244,6 +244,7 @@ class DownloadTask:
async for resp in self.delivery_service.pylon_client.by_doi( async for resp in self.delivery_service.pylon_client.by_doi(
doi=document_view.doi, doi=document_view.doi,
md5=document_view.md5, md5=document_view.md5,
error_log_func=self.request_context.error_log,
): ):
await self.process_resp( await self.process_resp(
resp=resp, resp=resp,
@ -252,11 +253,14 @@ class DownloadTask:
filesize=document_view.filesize, filesize=document_view.filesize,
) )
return bytes(collected) return bytes(collected)
except DownloadError: except DownloadError as e:
pass self.request_context.error_log(e)
if document_view.md5: if document_view.md5:
try: try:
async for resp in self.delivery_service.pylon_client.by_md5(md5=document_view.md5): async for resp in self.delivery_service.pylon_client.by_md5(
md5=document_view.md5,
error_log_func=self.request_context.error_log,
):
await self.process_resp( await self.process_resp(
resp=resp, resp=resp,
progress_bar=progress_bar, progress_bar=progress_bar,
@ -264,14 +268,14 @@ class DownloadTask:
filesize=document_view.filesize, filesize=document_view.filesize,
) )
return bytes(collected) return bytes(collected)
except DownloadError: except DownloadError as e:
pass self.request_context.error_log(e)
async def external_cancel(self): async def external_cancel(self):
self.task.cancel() self.task.cancel()
self.request_context.statbox(action='externally_canceled') self.request_context.statbox(action='externally_canceled')
await self.delivery_service.telegram_client.send_message( await self.delivery_service.telegram_client.send_message(
self.request_context.chat.id, self.request_context.chat.chat_id,
t("DOWNLOAD_CANCELED", language=self.request_context.chat.language).format( t("DOWNLOAD_CANCELED", language=self.request_context.chat.language).format(
document=self.document_view.get_robust_title() document=self.document_view.get_robust_title()
), ),
@ -366,15 +370,15 @@ class DeliveryService(DeliveryServicer, BaseHubService):
file=document_view.telegram_file_id, file=document_view.telegram_file_id,
session_id=metadata.get('session-id'), session_id=metadata.get('session-id'),
request_context=request_context, request_context=request_context,
voting=not is_group_or_channel(request_context.chat.id), voting=not is_group_or_channel(request_context.chat.chat_id),
) )
request_context.statbox(action='cache_hit', document_id=document_view.id) request_context.statbox(action='cache_hit', document_id=document_view.id)
except ValueError: except ValueError:
cache_hit = False cache_hit = False
if not cache_hit: if not cache_hit:
if self.user_manager.has_task(request.chat.id, document_view.id): if self.user_manager.has_task(request.chat.chat_id, document_view.id):
return StartDeliveryResponsePb(status=StartDeliveryResponsePb.Status.ALREADY_DOWNLOADING) return StartDeliveryResponsePb(status=StartDeliveryResponsePb.Status.ALREADY_DOWNLOADING)
if self.user_manager.hit_limits(request.chat.id): if self.user_manager.hit_limits(request.chat.chat_id):
return StartDeliveryResponsePb(status=StartDeliveryResponsePb.Status.TOO_MANY_DOWNLOADS) return StartDeliveryResponsePb(status=StartDeliveryResponsePb.Status.TOO_MANY_DOWNLOADS)
await DownloadTask( await DownloadTask(
delivery_service=self, delivery_service=self,

View File

@ -101,13 +101,13 @@ class SubmitterService(SubmitterServicer, BaseHubService):
if document.size > 20 * 1024 * 1024: if document.size > 20 * 1024 * 1024:
request_context.error_log(FileTooBigError(size=document.size)) request_context.error_log(FileTooBigError(size=document.size))
await self.telegram_client.send_message( await self.telegram_client.send_message(
request_context.chat.id, request_context.chat.chat_id,
t('FILE_TOO_BIG_ERROR', language=request_context.chat.language), t('FILE_TOO_BIG_ERROR', language=request_context.chat.language),
buttons=[close_button()], buttons=[close_button()],
) )
return SubmitResponsePb() return SubmitResponsePb()
processing_message = await self.telegram_client.send_message( processing_message = await self.telegram_client.send_message(
request_context.chat.id, request_context.chat.chat_id,
t("PROCESSING_PAPER", language=request_context.chat.language).format( t("PROCESSING_PAPER", language=request_context.chat.language).format(
filename=document.attributes[0].file_name, filename=document.attributes[0].file_name,
), ),
@ -121,7 +121,7 @@ class SubmitterService(SubmitterServicer, BaseHubService):
except BadRequestError as e: except BadRequestError as e:
request_context.error_log(e) request_context.error_log(e)
await self.telegram_client.send_message( await self.telegram_client.send_message(
request_context.chat.id, request_context.chat.chat_id,
t('UNPARSABLE_DOCUMENT_ERROR', language=request_context.chat.language), t('UNPARSABLE_DOCUMENT_ERROR', language=request_context.chat.language),
buttons=[close_button()], buttons=[close_button()],
) )
@ -130,7 +130,7 @@ class SubmitterService(SubmitterServicer, BaseHubService):
if not processed_document.get('doi'): if not processed_document.get('doi'):
request_context.error_log(UnparsableDoiError()) request_context.error_log(UnparsableDoiError())
await self.telegram_client.send_message( await self.telegram_client.send_message(
request_context.chat.id, request_context.chat.chat_id,
t('UNPARSABLE_DOI_ERROR', language=request_context.chat.language), t('UNPARSABLE_DOI_ERROR', language=request_context.chat.language),
buttons=[close_button()], buttons=[close_button()],
) )
@ -143,14 +143,14 @@ class SubmitterService(SubmitterServicer, BaseHubService):
page_size=1, page_size=1,
request_id=request_context.request_id, request_id=request_context.request_id,
session_id=session_id, session_id=session_id,
user_id=request_context.chat.id, user_id=request_context.chat.chat_id,
language=request_context.chat.language, language=request_context.chat.language,
) )
if len(search_response_pb.scored_documents) == 0: if len(search_response_pb.scored_documents) == 0:
request_context.error_log(UnavailableMetadataError(doi=processed_document['doi'])) request_context.error_log(UnavailableMetadataError(doi=processed_document['doi']))
await self.telegram_client.send_message( await self.telegram_client.send_message(
request_context.chat.id, request_context.chat.chat_id,
t( t(
'UNAVAILABLE_METADATA_ERROR', 'UNAVAILABLE_METADATA_ERROR',
language=request_context.chat.language language=request_context.chat.language
@ -175,7 +175,7 @@ class SubmitterService(SubmitterServicer, BaseHubService):
update_document=UpdateDocumentPb( update_document=UpdateDocumentPb(
typed_document=TypedDocumentPb(sharience=ShariencePb( typed_document=TypedDocumentPb(sharience=ShariencePb(
parent_id=document_view.id, parent_id=document_view.id,
uploader_id=request_context.chat.id, uploader_id=request_context.chat.chat_id,
updated_at=int(time.time()), updated_at=int(time.time()),
md5=hashlib.md5(file).hexdigest(), md5=hashlib.md5(file).hexdigest(),
filesize=document.size, filesize=document.size,

View File

@ -1,99 +1,3 @@
# Nexus Pipe # Nexus Pipe
`Pipe` processes Kafka queue of operations. This version has cut `configs` `Pipe` processes Kafka queue of operations.
subdirectory due to hard reliance of configs on the network infrastructure you are using.
You have to write your own configs taking example below into account.
## Sample `configs/base.yaml`
```yaml
---
log_path: '/var/log/nexus-pipe/{{ ENV_TYPE }}'
pipe:
brokers: |
kafka-0.example.net
schema:
- consumers:
- class: nexus.pipe.consumers.CrossReferencesBulkConsumer
topics:
- name: cross_references
workers: 4
group_id: pipe
processors:
- class: nexus.pipe.processors.CrossReferencesProcessor
kwargs:
brokers: |
kafka-0.example.net
database:
database: nexus
host: postgres.example.net
password: '{{ DATABASE_PASSWORD }}'
username: '{{ DATABASE_USERNAME }}'
- consumers:
- class: nexus.pipe.consumers.DocumentOperationsJsonConsumer
topics:
- name: operations
workers: 2
- class: nexus.pipe.consumers.DocumentOperationsConsumer
topics:
- name: operations_binary_hp
workers: 4
- name: operations_binary
workers: 14
group_id: pipe
processors:
- class: nexus.pipe.processors.ActionProcessor
kwargs:
actions:
- class: nexus.actions.FillDocumentOperationUpdateDocumentScimagPbFromExternalSourceAction
kwargs:
crossref:
rps: 50
user_agent: 'ScienceLegion/1.0 (Linux x86_64; ) ScienceLegion/1.0.0'
- class: nexus.actions.CleanDocumentOperationUpdateDocumentScimagPbAction
- class: nexus.actions.SendDocumentOperationUpdateDocumentScimagPbToGoldenPostgresAction
kwargs:
database:
database: nexus
host: postgres.example.net
password: '{{ DATABASE_PASSWORD }}'
username: '{{ DATABASE_USERNAME }}'
- class: nexus.actions.SendDocumentOperationUpdateDocumentScimagPbReferencesToKafkaAction
kwargs:
brokers: |
kafka-0.example.net
topic: cross_references
- class: nexus.actions.SendDocumentOperationUpdateDocumentPbToSummaAction
kwargs:
summa:
base_url: http://summa.example.net
timeout: 15
ttl_dns_cache: 30
filter:
class: nexus.pipe.filters.DocumentOperationFilter
kwargs:
document: scimag
operation: update_document
- class: nexus.pipe.processors.ActionProcessor
kwargs:
actions:
- class: nexus.actions.CleanDocumentOperationUpdateDocumentScitechPbAction
- class: nexus.actions.SendDocumentOperationUpdateDocumentScitechPbToGoldenPostgresAction
kwargs:
database:
database: nexus
host: postgres.example.net
password: '{{ DATABASE_PASSWORD }}'
username: '{{ DATABASE_USERNAME }}'
- class: nexus.actions.SendDocumentOperationUpdateDocumentPbToSummaAction
kwargs:
summa:
base_url: http://summa.example.net
timeout: 15
ttl_dns_cache: 30
filter:
class: nexus.pipe.filters.DocumentOperationFilter
kwargs:
document: scitech
operation: update_document
```

View File

@ -0,0 +1,90 @@
---
log_path: '/var/log/nexus-pipe/{{ ENV_TYPE }}'
pipe:
brokers: |
kafka-0.example.net
schema:
- consumers:
- class: nexus.pipe.consumers.CrossReferencesBulkConsumer
topics:
- name: cross_references
workers: 4
group_id: pipe
processors:
- class: nexus.pipe.processors.CrossReferencesProcessor
kwargs:
brokers: |
kafka-0.example.net
database:
database: nexus
host: postgres.example.net
password: '{{ DATABASE_PASSWORD }}'
username: '{{ DATABASE_USERNAME }}'
- consumers:
- class: nexus.pipe.consumers.DocumentOperationsJsonConsumer
topics:
- name: operations
workers: 2
- class: nexus.pipe.consumers.DocumentOperationsConsumer
topics:
- name: operations_binary_hp
workers: 4
- name: operations_binary
workers: 14
group_id: pipe
processors:
- class: nexus.pipe.processors.ActionProcessor
kwargs:
actions:
- class: nexus.actions.FillDocumentOperationUpdateDocumentScimagPbFromExternalSourceAction
kwargs:
crossref:
rps: 50
user_agent: 'ScienceLegion/1.0 (Linux x86_64; ) ScienceLegion/1.0.0'
- class: nexus.actions.CleanDocumentOperationUpdateDocumentScimagPbAction
- class: nexus.actions.SendDocumentOperationUpdateDocumentScimagPbToGoldenPostgresAction
kwargs:
database:
database: nexus
host: postgres.example.net
password: '{{ DATABASE_PASSWORD }}'
username: '{{ DATABASE_USERNAME }}'
- class: nexus.actions.SendDocumentOperationUpdateDocumentScimagPbReferencesToKafkaAction
kwargs:
brokers: |
kafka-0.example.net
topic: cross_references
- class: nexus.actions.SendDocumentOperationUpdateDocumentPbToSummaAction
kwargs:
summa:
base_url: http://summa.example.net
timeout: 15
ttl_dns_cache: 30
filter:
class: nexus.pipe.filters.DocumentOperationFilter
kwargs:
document: scimag
operation: update_document
- class: nexus.pipe.processors.ActionProcessor
kwargs:
actions:
- class: nexus.actions.CleanDocumentOperationUpdateDocumentScitechPbAction
- class: nexus.actions.SendDocumentOperationUpdateDocumentScitechPbToGoldenPostgresAction
kwargs:
database:
database: nexus
host: postgres.example.net
password: '{{ DATABASE_PASSWORD }}'
username: '{{ DATABASE_USERNAME }}'
- class: nexus.actions.SendDocumentOperationUpdateDocumentPbToSummaAction
kwargs:
summa:
base_url: http://summa.example.net
timeout: 15
ttl_dns_cache: 30
filter:
class: nexus.pipe.filters.DocumentOperationFilter
kwargs:
document: scitech
operation: update_document

View File

@ -0,0 +1,65 @@
---
logging:
disable_existing_loggers: false
formatters:
default:
class: library.logging.formatters.DefaultFormatter
traceback:
class: library.logging.formatters.TracebackFormatter
handlers:
debug:
class: logging.StreamHandler
formatter: default
level: DEBUG
stream: 'ext://sys.stderr'
error:
class: library.logging.handlers.BaseFileHandler
filename: '{{ log_path }}/error.log'
formatter: default
level: ERROR
statbox:
class: library.logging.handlers.BaseFileHandler
filename: '{{ log_path }}/statbox.log'
formatter: default
level: INFO
traceback:
class: library.logging.handlers.BaseFileHandler
filename: '{{ log_path }}/traceback.log'
formatter: traceback
level: ERROR
warning:
class: library.logging.handlers.BaseFileHandler
filename: '{{ log_path }}/warning.log'
formatter: default
level: WARNING
loggers:
aiokafka:
handlers:
- error
propagate: false
asyncio:
handlers:
- error
level: WARNING
debug:
handlers:
- debug
propagate: false
error:
handlers:
- error
- traceback
- warning
propagate: false
statbox:
handlers:
- statbox
propagate: false
root:
handlers:
- debug
- error
- warning
level: DEBUG
version: 1

View File

@ -1,16 +1,10 @@
import asyncio
from typing import ( from typing import (
AsyncIterable, AsyncIterable,
Callable,
Iterable, Iterable,
Optional, Optional,
) )
import aiohttp
import aiohttp.client_exceptions
from aiohttp_socks import (
ProxyConnectionError,
ProxyError,
)
from aiokit import AioThing from aiokit import AioThing
from library.logging import error_log from library.logging import error_log
from nexus.pylon.exceptions import ( from nexus.pylon.exceptions import (
@ -27,7 +21,6 @@ from nexus.pylon.sources import (
SciHubSeSource, SciHubSeSource,
) )
from nexus.pylon.sources.specific import get_specific_sources_for_doi from nexus.pylon.sources.specific import get_specific_sources_for_doi
from python_socks import ProxyTimeoutError
class PylonClient(AioThing): class PylonClient(AioThing):
@ -40,6 +33,7 @@ class PylonClient(AioThing):
self, self,
doi: str, doi: str,
md5: Optional[str] = None, md5: Optional[str] = None,
error_log_func: Callable = error_log,
) -> AsyncIterable[FileResponsePb]: ) -> AsyncIterable[FileResponsePb]:
sources = [] sources = []
sources.extend(get_specific_sources_for_doi(doi, proxy=self.proxy, resolve_proxy=self.resolve_proxy)) sources.extend(get_specific_sources_for_doi(doi, proxy=self.proxy, resolve_proxy=self.resolve_proxy))
@ -49,49 +43,42 @@ class PylonClient(AioThing):
LibgenDoiSource(doi=doi, md5=md5, proxy=self.proxy, resolve_proxy=self.resolve_proxy), LibgenDoiSource(doi=doi, md5=md5, proxy=self.proxy, resolve_proxy=self.resolve_proxy),
]) ])
sources = filter(lambda x: x.is_enabled, sources) sources = filter(lambda x: x.is_enabled, sources)
async for resp in self.download(sources=sources): async for resp in self.download(sources=sources, error_log_func=error_log_func):
yield resp yield resp
async def by_md5( async def by_md5(
self, self,
md5: str, md5: str,
error_log_func: Callable = error_log,
) -> AsyncIterable[FileResponsePb]: ) -> AsyncIterable[FileResponsePb]:
sources = filter(lambda x: x.is_enabled, [ sources = filter(lambda x: x.is_enabled, [
LibraryLolSource(md5=md5, proxy=self.proxy, resolve_proxy=self.resolve_proxy), LibraryLolSource(md5=md5, proxy=self.proxy, resolve_proxy=self.resolve_proxy),
LibgenMd5Source(md5=md5, proxy=self.proxy, resolve_proxy=self.resolve_proxy), LibgenMd5Source(md5=md5, proxy=self.proxy, resolve_proxy=self.resolve_proxy),
]) ])
async for resp in self.download(sources=sources): async for resp in self.download(sources=sources, error_log_func=error_log_func):
yield resp yield resp
async def download_source(self, source) -> AsyncIterable[FileResponsePb]: async def download_source(self, source, error_log_func: Callable = error_log) -> AsyncIterable[FileResponsePb]:
try: yield FileResponsePb(status=FileResponsePb.Status.RESOLVING, source=source.base_url)
yield FileResponsePb(status=FileResponsePb.Status.RESOLVING, source=source.base_url) async for prepared_file_request in source.resolve(error_log_func=error_log_func):
async for prepared_request in source.resolve():
async for resp in source.execute_prepared_request(prepared_request=prepared_request):
yield resp
return
raise DownloadError(error='not_found', source=str(source))
except (
aiohttp.client_exceptions.ClientConnectionError,
aiohttp.client_exceptions.ClientPayloadError,
aiohttp.client_exceptions.ClientResponseError,
aiohttp.client_exceptions.TooManyRedirects,
asyncio.TimeoutError,
ProxyConnectionError,
ProxyTimeoutError,
ProxyError,
) as e:
raise DownloadError(nested_error=str(e), nested_error_cls=e.__class__.__name__)
async def download(self, sources: Iterable[BaseSource]) -> AsyncIterable[FileResponsePb]:
for source in sources:
try: try:
await source.start() async for resp in source.execute_prepared_file_request(prepared_file_request=prepared_file_request):
async for resp in self.download_source(source):
yield resp yield resp
return return
except DownloadError as e: except DownloadError as e:
error_log(e) error_log_func(e)
continue
raise DownloadError(error='not_found', source=str(source))
async def download(self, sources: Iterable[BaseSource], error_log_func: Callable = error_log) -> AsyncIterable[FileResponsePb]:
for source in sources:
try:
await source.start()
async for resp in self.download_source(source, error_log_func=error_log_func):
yield resp
return
except DownloadError as e:
error_log_func(e)
continue continue
finally: finally:
await source.stop() await source.stop()

View File

@ -1,19 +1,27 @@
import asyncio
import hashlib import hashlib
import random import socket
from contextlib import asynccontextmanager
from typing import ( from typing import (
AsyncIterable, AsyncIterable,
Callable,
Optional, Optional,
) )
import aiohttp import aiohttp
import aiohttp.client_exceptions import aiohttp.client_exceptions
from aiohttp.client_reqrep import ClientRequest
from aiohttp_socks import ( from aiohttp_socks import (
ProxyConnectionError,
ProxyConnector, ProxyConnector,
ProxyError, ProxyError,
) )
from aiokit import AioThing from aiokit import AioThing
from izihawa_utils.importlib import class_fullname
from library.logging import error_log
from nexus.pylon.exceptions import ( from nexus.pylon.exceptions import (
BadResponseError, BadResponseError,
DownloadError,
IncorrectMD5Error, IncorrectMD5Error,
NotFoundError, NotFoundError,
) )
@ -30,19 +38,32 @@ from tenacity import (
DEFAULT_USER_AGENT = 'PylonBot/1.0 (Linux x86_64) PylonBot/1.0.0' DEFAULT_USER_AGENT = 'PylonBot/1.0 (Linux x86_64) PylonBot/1.0.0'
class KeepAliveClientRequest(ClientRequest):
async def send(self, conn):
sock = conn.protocol.transport.get_extra_info("socket")
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 2)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5)
return await super().send(conn)
class PreparedRequest: class PreparedRequest:
def __init__( def __init__(
self, self,
method: str, method: str,
url: str, url: str,
headers: dict = None, headers: Optional[dict] = None,
params: dict = None, params: Optional[dict] = None,
cookies: dict = None, cookies: Optional[dict] = None,
ssl: bool = True, ssl: bool = True,
timeout: Optional[float] = None
): ):
self.method = method self.method = method
self.url = url self.url = url
self.headers = { self.headers = {
'Connection': 'keep-alive',
'User-Agent': DEFAULT_USER_AGENT, 'User-Agent': DEFAULT_USER_AGENT,
} }
if headers: if headers:
@ -50,6 +71,7 @@ class PreparedRequest:
self.params = params self.params = params
self.cookies = cookies self.cookies = cookies
self.ssl = ssl self.ssl = ssl
self.timeout = timeout
def __repr__(self): def __repr__(self):
return f'{self.method} {self.url} {self.headers} {self.params}' return f'{self.method} {self.url} {self.headers} {self.params}'
@ -57,6 +79,34 @@ class PreparedRequest:
def __str__(self): def __str__(self):
return repr(self) return repr(self)
@asynccontextmanager
async def execute_with(self, session):
async with session.request(
method=self.method,
url=self.url,
timeout=self.timeout,
headers=self.headers,
cookies=self.cookies,
params=self.params,
ssl=self.ssl,
) as resp:
try:
yield resp
except BadResponseError as e:
e.add('url', self.url)
raise e
except (
aiohttp.client_exceptions.ClientConnectionError,
aiohttp.client_exceptions.ClientPayloadError,
aiohttp.client_exceptions.ClientResponseError,
aiohttp.client_exceptions.TooManyRedirects,
asyncio.TimeoutError,
ProxyConnectionError,
ProxyTimeoutError,
ProxyError,
) as e:
raise DownloadError(nested_error=repr(e), nested_error_cls=class_fullname(e))
class BaseValidator: class BaseValidator:
def update(self, chunk: bytes): def update(self, chunk: bytes):
@ -123,12 +173,12 @@ class BaseSource(AioThing):
return aiohttp.TCPConnector(verify_ssl=self.ssl) return aiohttp.TCPConnector(verify_ssl=self.ssl)
def get_session(self): def get_session(self):
return aiohttp.ClientSession(connector=self.get_proxy()) return aiohttp.ClientSession(request_class=KeepAliveClientRequest, connector=self.get_proxy())
def get_resolve_session(self): def get_resolve_session(self):
return aiohttp.ClientSession(connector=self.get_resolve_proxy()) return aiohttp.ClientSession(request_class=KeepAliveClientRequest, connector=self.get_resolve_proxy())
def resolve(self) -> AsyncIterable[PreparedRequest]: async def resolve(self, error_log_func: Callable = error_log) -> AsyncIterable[PreparedRequest]:
raise NotImplementedError("`resolve` for BaseSource is not implemented") raise NotImplementedError("`resolve` for BaseSource is not implemented")
def get_validator(self): def get_validator(self):
@ -139,19 +189,11 @@ class BaseSource(AioThing):
stop=stop_after_attempt(3), stop=stop_after_attempt(3),
retry=retry_if_exception_type((ProxyError, aiohttp.client_exceptions.ClientPayloadError, ProxyTimeoutError)), retry=retry_if_exception_type((ProxyError, aiohttp.client_exceptions.ClientPayloadError, ProxyTimeoutError)),
) )
async def execute_prepared_request(self, prepared_request: PreparedRequest): async def execute_prepared_file_request(self, prepared_file_request: PreparedRequest):
async with self.get_session() as session: async with self.get_session() as session:
async with session.request( async with prepared_file_request.execute_with(session=session) as resp:
method=prepared_request.method,
url=prepared_request.url,
timeout=self.timeout,
headers=prepared_request.headers,
cookies=prepared_request.cookies,
params=prepared_request.params,
ssl=prepared_request.ssl,
) as resp:
if resp.status == 404: if resp.status == 404:
raise NotFoundError(url=prepared_request.url) raise NotFoundError(url=prepared_file_request.url)
elif ( elif (
resp.status != 200 resp.status != 200
or ( or (
@ -160,24 +202,17 @@ class BaseSource(AioThing):
) )
): ):
raise BadResponseError( raise BadResponseError(
request_headers=prepared_request.headers, request_headers=prepared_file_request.headers,
url=prepared_request.url, url=prepared_file_request.url,
status=resp.status, status=resp.status,
headers=str(resp.headers), headers=str(resp.headers),
) )
file_validator = self.get_validator() file_validator = self.get_validator()
# Randomness is required due to annoying bug of when separators yield FileResponsePb(status=FileResponsePb.Status.BEGIN_TRANSMISSION, source=prepared_file_request.url)
# (\r\n) are splitted to different chunks async for content, _ in resp.content.iter_chunks():
# https://github.com/aio-libs/aiohttp/issues/4677
yield FileResponsePb(status=FileResponsePb.Status.BEGIN_TRANSMISSION, source=prepared_request.url)
async for content in resp.content.iter_chunked(1024 * 100 + random.randint(-1024, 1024)):
file_validator.update(content) file_validator.update(content)
yield FileResponsePb(chunk=ChunkPb(content=content), source=prepared_request.url) yield FileResponsePb(chunk=ChunkPb(content=content), source=prepared_file_request.url)
try: file_validator.validate()
file_validator.validate()
except BadResponseError as e:
e.add('url', prepared_request.url)
raise e
class Md5Source(BaseSource): class Md5Source(BaseSource):

View File

@ -1,5 +1,8 @@
import re import re
from typing import AsyncIterable from typing import (
AsyncIterable,
Callable,
)
from library.logging import error_log from library.logging import error_log
from nexus.pylon.exceptions import RegexNotFoundError from nexus.pylon.exceptions import RegexNotFoundError
@ -14,13 +17,14 @@ class LibgenDoiSource(DoiSource):
base_url = 'http://libgen.gs' base_url = 'http://libgen.gs'
resolve_timeout = 10 resolve_timeout = 10
async def resolve(self) -> AsyncIterable[PreparedRequest]: async def resolve(self, error_log_func: Callable = error_log) -> AsyncIterable[PreparedRequest]:
async with self.get_resolve_session() as session: async with self.get_resolve_session() as session:
url = f'{self.base_url}/scimag/ads.php?doi={self.doi}' url = f'{self.base_url}/scimag/ads.php?doi={self.doi}'
async with session.get( async with PreparedRequest(
url, method='get',
timeout=self.resolve_timeout url=url,
) as resp: timeout=self.resolve_timeout,
).execute_with(session=session) as resp:
downloaded_page_bytes = await resp.read() downloaded_page_bytes = await resp.read()
downloaded_page = downloaded_page_bytes.decode('utf-8', 'backslashreplace') downloaded_page = downloaded_page_bytes.decode('utf-8', 'backslashreplace')
match = re.search( match = re.search(
@ -29,6 +33,6 @@ class LibgenDoiSource(DoiSource):
re.IGNORECASE, re.IGNORECASE,
) )
if match: if match:
yield PreparedRequest(method='get', url=match.group()) yield PreparedRequest(method='get', url=match.group(), timeout=self.timeout)
else: else:
error_log(RegexNotFoundError(url=url)) error_log_func(RegexNotFoundError(url=url))

View File

@ -1,5 +1,10 @@
import re import re
from typing import AsyncIterable from typing import (
AsyncIterable,
Callable,
)
from library.logging import error_log
from .base import ( from .base import (
Md5Source, Md5Source,
@ -12,10 +17,11 @@ class LibgenMd5Source(Md5Source):
resolve_timeout = 10 resolve_timeout = 10
async def resolve_lg(self, session, url): async def resolve_lg(self, session, url):
async with session.get( async with PreparedRequest(
url, method='get',
url=url,
timeout=self.resolve_timeout timeout=self.resolve_timeout
) as resp: ).execute_with(session=session) as resp:
downloaded_page_fiction = await resp.text() downloaded_page_fiction = await resp.text()
match = re.search( match = re.search(
'https?://.*/get\\.php\\?md5=.*&key=[A-Za-z0-9]+', 'https?://.*/get\\.php\\?md5=.*&key=[A-Za-z0-9]+',
@ -23,9 +29,9 @@ class LibgenMd5Source(Md5Source):
re.IGNORECASE, re.IGNORECASE,
) )
if match: if match:
return PreparedRequest(method='get', url=match.group()) return PreparedRequest(method='get', url=match.group(), timeout=self.timeout)
async def resolve(self) -> AsyncIterable[PreparedRequest]: async def resolve(self, error_log_func: Callable = error_log) -> AsyncIterable[PreparedRequest]:
async with self.get_resolve_session() as session: async with self.get_resolve_session() as session:
url = f'{self.base_url}/ads.php?md5={self.md5}' url = f'{self.base_url}/ads.php?md5={self.md5}'
result = await self.resolve_lg(session, url) result = await self.resolve_lg(session, url)

View File

@ -1,5 +1,8 @@
import re import re
from typing import AsyncIterable from typing import (
AsyncIterable,
Callable,
)
from library.logging import error_log from library.logging import error_log
from nexus.pylon.exceptions import RegexNotFoundError from nexus.pylon.exceptions import RegexNotFoundError
@ -11,13 +14,14 @@ from .base import (
class LibgenNewSource(Md5Source): class LibgenNewSource(Md5Source):
async def resolve(self) -> AsyncIterable[PreparedRequest]: async def resolve(self, error_log_func: Callable = error_log) -> AsyncIterable[PreparedRequest]:
async with self.get_resolve_session() as session: async with self.get_resolve_session() as session:
url = f'{self.base_url}/main/{self.md5.upper()}' url = f'{self.base_url}/main/{self.md5.upper()}'
async with session.get( async with PreparedRequest(
url, method='get',
url=url,
timeout=self.resolve_timeout timeout=self.resolve_timeout
) as resp: ).execute_with(session) as resp:
downloaded_page = await resp.text() downloaded_page = await resp.text()
match_ipfs = re.search( match_ipfs = re.search(
'https://ipfs.io/ipfs/[A-Za-z0-9]+', 'https://ipfs.io/ipfs/[A-Za-z0-9]+',
@ -25,28 +29,28 @@ class LibgenNewSource(Md5Source):
re.IGNORECASE, re.IGNORECASE,
) )
if match_ipfs: if match_ipfs:
yield PreparedRequest(method='get', url=match_ipfs.group(), ssl=self.ssl) yield PreparedRequest(method='get', url=match_ipfs.group(), ssl=self.ssl, timeout=self.timeout)
match_cf = re.search( match_cf = re.search(
'https://cloudflare-ipfs.com/ipfs/[A-Za-z0-9]+', 'https://cloudflare-ipfs.com/ipfs/[A-Za-z0-9]+',
downloaded_page, downloaded_page,
re.IGNORECASE, re.IGNORECASE,
) )
if match_cf: if match_cf:
yield PreparedRequest(method='get', url=match_cf.group(), ssl=self.ssl) yield PreparedRequest(method='get', url=match_cf.group(), ssl=self.ssl, timeout=self.timeout)
match_infura = re.search( match_infura = re.search(
'https://ipfs.infura.io/ipfs/[A-Za-z0-9]+', 'https://ipfs.infura.io/ipfs/[A-Za-z0-9]+',
downloaded_page, downloaded_page,
re.IGNORECASE, re.IGNORECASE,
) )
if match_infura: if match_infura:
yield PreparedRequest(method='get', url=match_infura.group(), ssl=self.ssl) yield PreparedRequest(method='get', url=match_infura.group(), ssl=self.ssl, timeout=self.timeout)
if not match_cf or not match_infura or not match_ipfs: if not match_cf or not match_infura or not match_ipfs:
error_log(RegexNotFoundError(url=url)) error_log_func(RegexNotFoundError(url=url))
class LibraryLolSource(LibgenNewSource): class LibraryLolSource(LibgenNewSource):
base_url = 'http://library.lol' base_url = 'http://library.lol'
resolve_timeout = 10 resolve_timeout = 20
ssl = False ssl = False
timeout = 30 timeout = 120

View File

@ -1,5 +1,8 @@
import re import re
from typing import AsyncIterable from typing import (
AsyncIterable,
Callable,
)
from library.logging import error_log from library.logging import error_log
from nexus.pylon.exceptions import RegexNotFoundError from nexus.pylon.exceptions import RegexNotFoundError
@ -19,16 +22,17 @@ class SciHubSource(DoiSource):
base_url = None base_url = None
ssl = False ssl = False
async def resolve(self, timeout=None) -> AsyncIterable[PreparedRequest]: async def resolve(self, error_log_func: Callable = error_log) -> AsyncIterable[PreparedRequest]:
async with self.get_resolve_session() as session: async with self.get_resolve_session() as session:
url = f'{self.base_url}/{self.doi}' url = f'{self.base_url}/{self.doi}'
async with session.get( async with PreparedRequest(
url, method='get',
timeout=timeout or self.timeout url=url,
) as resp: timeout=self.resolve_timeout
).execute_with(session=session) as resp:
# Sometimes sci-hub returns file # Sometimes sci-hub returns file
if resp.headers.get('Content-Type') == 'application/pdf': if resp.headers.get('Content-Type') == 'application/pdf':
yield PreparedRequest(method='get', url=url) yield PreparedRequest(method='get', url=url, timeout=self.timeout)
downloaded_page_bytes = await resp.read() downloaded_page_bytes = await resp.read()
downloaded_page = downloaded_page_bytes.decode('utf-8', 'backslashreplace') downloaded_page = downloaded_page_bytes.decode('utf-8', 'backslashreplace')
match = re.search('(?:https?:)?//.*\\?download=true', downloaded_page, re.IGNORECASE) match = re.search('(?:https?:)?//.*\\?download=true', downloaded_page, re.IGNORECASE)
@ -36,9 +40,9 @@ class SciHubSource(DoiSource):
url = match.group() url = match.group()
if url.startswith('//'): if url.startswith('//'):
url = 'http:' + url url = 'http:' + url
yield PreparedRequest(method='get', url=url) yield PreparedRequest(method='get', url=url, timeout=self.timeout)
else: else:
error_log(RegexNotFoundError(url=url)) error_log_func(RegexNotFoundError(url=url))
class SciHubDoSource(SciHubSource): class SciHubDoSource(SciHubSource):

View File

@ -1,5 +1,9 @@
from typing import AsyncIterable from typing import (
AsyncIterable,
Callable,
)
from library.logging import error_log
from nexus.pylon.sources.base import ( from nexus.pylon.sources.base import (
DoiSource, DoiSource,
PreparedRequest, PreparedRequest,
@ -9,11 +13,12 @@ from nexus.pylon.sources.base import (
class BiorxivSource(DoiSource): class BiorxivSource(DoiSource):
base_url = 'https://dx.doi.org' base_url = 'https://dx.doi.org'
async def resolve(self) -> AsyncIterable[PreparedRequest]: async def resolve(self, error_log_func: Callable = error_log) -> AsyncIterable[PreparedRequest]:
async with self.get_resolve_session() as session: async with self.get_resolve_session() as session:
url = f'{self.base_url}/{self.doi}' url = f'{self.base_url}/{self.doi}'
async with session.get( async with PreparedRequest(
url, method='get',
timeout=self.resolve_timeout url=url,
) as resp: timeout=self.resolve_timeout,
yield PreparedRequest(method='get', url=str(resp.url) + '.full.pdf') ).execute_with(session=session) as resp:
yield PreparedRequest(method='get', url=str(resp.url) + '.full.pdf', timeout=self.timeout)

View File

@ -1,5 +1,9 @@
from typing import AsyncIterable from typing import (
AsyncIterable,
Callable,
)
from library.logging import error_log
from nexus.pylon.sources.base import ( from nexus.pylon.sources.base import (
DoiSource, DoiSource,
PreparedRequest, PreparedRequest,
@ -11,14 +15,20 @@ class LancetSource(DoiSource):
resolve_timeout = 10 resolve_timeout = 10
use_proxy = False use_proxy = False
async def resolve(self) -> AsyncIterable[PreparedRequest]: async def resolve(self, error_log_func: Callable = error_log) -> AsyncIterable[PreparedRequest]:
async with self.get_resolve_session() as session: async with self.get_resolve_session() as session:
splitted_doi = self.doi.split("/", maxsplit=1) splitted_doi = self.doi.split("/", maxsplit=1)
if len(splitted_doi) < 2: if len(splitted_doi) < 2:
return return
url = f'{self.base_url}/action/showPdf?pii={splitted_doi[1].upper()}' url = f'{self.base_url}/action/showPdf?pii={splitted_doi[1].upper()}'
async with session.get( async with PreparedRequest(
url, method='get',
timeout=self.resolve_timeout url=url,
) as resp: timeout=self.resolve_timeout,
yield PreparedRequest(method='get', cookies=resp.cookies, url=str(resp.url)) ).execute_with(session=session) as resp:
yield PreparedRequest(
method='get',
cookies=resp.cookies,
url=str(resp.url),
timeout=self.resolve_timeout,
)

View File

@ -1,5 +1,9 @@
from typing import AsyncIterable from typing import (
AsyncIterable,
Callable,
)
from library.logging import error_log
from nexus.pylon.sources.base import ( from nexus.pylon.sources.base import (
DoiSource, DoiSource,
PreparedRequest, PreparedRequest,
@ -11,11 +15,12 @@ class NejmSource(DoiSource):
resolve_timeout = 10 resolve_timeout = 10
use_proxy = False use_proxy = False
async def resolve(self) -> AsyncIterable[PreparedRequest]: async def resolve(self, error_log_func: Callable = error_log) -> AsyncIterable[PreparedRequest]:
async with self.get_resolve_session() as session: async with self.get_resolve_session() as session:
url = f'{self.base_url}/doi/pdf/{self.doi}' url = f'{self.base_url}/doi/pdf/{self.doi}'
async with session.get( async with PreparedRequest(
url, method='get',
url=url,
timeout=self.resolve_timeout, timeout=self.resolve_timeout,
) as resp: ).execute_with(session=session) as resp:
yield PreparedRequest(method='get', cookies=resp.cookies, url=str(resp.url)) yield PreparedRequest(method='get', cookies=resp.cookies, url=str(resp.url), timeout=self.timeout)

View File

@ -1,8 +1,11 @@
import re import re
from typing import AsyncIterable from typing import (
AsyncIterable,
Callable,
)
from library.logging import error_log
from nexus.pylon.exceptions import RegexNotFoundError from nexus.pylon.exceptions import RegexNotFoundError
from nexus.pylon.sources.base import ( from nexus.pylon.sources.base import (
DoiSource, DoiSource,
PreparedRequest, PreparedRequest,
@ -12,13 +15,14 @@ from nexus.pylon.sources.base import (
class ResearchSquareSource(DoiSource): class ResearchSquareSource(DoiSource):
base_url = 'https://dx.doi.org' base_url = 'https://dx.doi.org'
async def resolve(self) -> AsyncIterable[PreparedRequest]: async def resolve(self, error_log_func: Callable = error_log) -> AsyncIterable[PreparedRequest]:
async with self.get_resolve_session() as session: async with self.get_resolve_session() as session:
url = f'{self.base_url}/{self.doi}' url = f'{self.base_url}/{self.doi}'
async with session.get( async with PreparedRequest(
url, method='get',
timeout=self.resolve_timeout url=url,
) as resp: timeout=self.resolve_timeout,
).execute_with(session=session) as resp:
download_page = await resp.text() download_page = await resp.text()
match = re.search( match = re.search(
r'\"(https://www\.researchsquare\.com/article/[^\"]+\.pdf)\"', r'\"(https://www\.researchsquare\.com/article/[^\"]+\.pdf)\"',
@ -27,4 +31,4 @@ class ResearchSquareSource(DoiSource):
) )
if not match: if not match:
raise RegexNotFoundError(url=url) raise RegexNotFoundError(url=url)
yield PreparedRequest(method='get', url=match.group(1)) yield PreparedRequest(method='get', url=match.group(1), timeout=self.timeout)

View File

@ -84,7 +84,7 @@ class ProgressBar:
try: try:
if not self.message: if not self.message:
self.message = await self.telegram_client.send_message( self.message = await self.telegram_client.send_message(
self.request_context.chat.id, self.request_context.chat.chat_id,
text, text,
buttons=[close_button()], buttons=[close_button()],
) )

View File

@ -6,7 +6,10 @@ from typing import (
) )
from urllib.parse import quote from urllib.parse import quote
from izihawa_utils.common import filter_none from izihawa_utils.common import (
filter_none,
is_essential,
)
from nexus.models.proto.scitech_pb2 import Scitech as ScitechPb from nexus.models.proto.scitech_pb2 import Scitech as ScitechPb
from nexus.nlptools.utils import ( from nexus.nlptools.utils import (
cast_string_to_single_string, cast_string_to_single_string,
@ -48,7 +51,7 @@ class ScitechView(BaseView, AuthorMixin, DoiMixin, FileMixin, IssuedAtMixin):
locator = self.get_formatted_locator() locator = self.get_formatted_locator()
caption = '\n'.join(filter_none([head, doi, locator])) caption = '\n'.join(filter_none([head, doi, locator], predicate=is_essential))
if limit and len(caption) > limit: if limit and len(caption) > limit:
shorten_title = title[:limit] shorten_title = title[:limit]
shorten_title = shorten_title[:max(32, shorten_title.rfind(' '))] shorten_title = shorten_title[:max(32, shorten_title.rfind(' '))]