Merge pull request #24 from the-superpirate/master

- repo(nexus): Refactoring legacy
This commit is contained in:
the-superpirate 2021-04-11 17:45:16 +03:00 committed by GitHub
commit 58d4f50465
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 192 additions and 358 deletions

23
.gitignore vendored
View File

@ -1,8 +1,23 @@
# Ignore all bazel-* symlinks. There is no full list since this can change # Python
# based on the name of the directory bazel is cloned into. venv
/bazel-* test.db
node_modules/
__pycache__/ __pycache__/
cover
.coverage
dump.rdb
/build/
tasq
devenv/
# NodeJS
node_modules/
.nuxt
# Development
/bazel-*
.idea/
*.log
.DS_Store
# Telethon session files # Telethon session files
*.session *.session

View File

@ -77,36 +77,6 @@ http_archive(
], ],
) )
_configure_python_based_on_os = """
if [[ "$OSTYPE" == "darwin"* ]]; then
./configure --prefix=$(pwd)/bazel_install --with-openssl=$(brew --prefix openssl)
else
./configure --prefix=$(pwd)/bazel_install
fi
"""
http_archive(
name = "python_interpreter",
build_file_content = """
exports_files(["python_bin"])
filegroup(
name = "files",
srcs = glob(["bazel_install/**"], exclude = ["**/* *"]),
visibility = ["//visibility:public"],
)
""",
patch_cmds = [
"mkdir $(pwd)/bazel_install",
_configure_python_based_on_os,
"make",
"make install",
"ln -s bazel_install/bin/python3 python_bin",
],
sha256 = "4b0e6644a76f8df864ae24ac500a51bbf68bd098f6a173e27d3b61cdca9aa134",
strip_prefix = "Python-3.9.4",
urls = ["https://www.python.org/ftp/python/3.9.4/Python-3.9.4.tar.xz"],
)
http_archive( http_archive(
name = "rules_python", name = "rules_python",
sha256 = "b228318a786d99b665bc83bd6cdb81512cae5f8eb15e8cd19f9956604b8939f5", sha256 = "b228318a786d99b665bc83bd6cdb81512cae5f8eb15e8cd19f9956604b8939f5",
@ -118,6 +88,7 @@ http_archive(
http_archive( http_archive(
name = "subpar", name = "subpar",
sha256 = "481233d60c547e0902d381cd4fb85b63168130379600f330821475ad234d9336",
strip_prefix = "subpar-9fae6b63cfeace2e0fb93c9c1ebdc28d3991b16f", strip_prefix = "subpar-9fae6b63cfeace2e0fb93c9c1ebdc28d3991b16f",
urls = [ urls = [
"https://github.com/google/subpar/archive/9fae6b63cfeace2e0fb93c9c1ebdc28d3991b16f.tar.gz", "https://github.com/google/subpar/archive/9fae6b63cfeace2e0fb93c9c1ebdc28d3991b16f.tar.gz",
@ -134,6 +105,22 @@ http_archive(
], ],
) )
# Images Install
load("//images:install.bzl", "images_install")
images_install()
# Python
register_toolchains("//rules/python:py_toolchain")
load("@rules_python//python:pip.bzl", "pip_install")
pip_install(
name = "pip_modules",
requirements = "//rules/python:requirements.txt",
)
# Java # Java
load("//rules/java:artifacts.bzl", "maven_fetch_remote_artifacts") load("//rules/java:artifacts.bzl", "maven_fetch_remote_artifacts")
@ -230,17 +217,6 @@ py3_image_repos()
rust_image_repos() rust_image_repos()
# Python
register_toolchains("//rules/python:py_3_toolchain")
load("@rules_python//python:pip.bzl", "pip_install")
pip_install(
name = "pip_modules",
python_interpreter_target = "@python_interpreter//:python_bin",
requirements = "//rules/python:requirements.txt",
)
# K8s # K8s
load("@io_bazel_rules_k8s//k8s:k8s.bzl", "k8s_repositories") load("@io_bazel_rules_k8s//k8s:k8s.bzl", "k8s_repositories")
@ -261,12 +237,6 @@ load("//rules/misc:install.bzl", "rules_misc_install_internal")
rules_misc_install_internal() rules_misc_install_internal()
# Images Install
load("//images:install.bzl", "images_install")
images_install()
# Proto / gRPC # Proto / gRPC
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains") load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")

View File

@ -105,7 +105,6 @@ class IdmApi2GrpcClient(AioThing):
ban_until=None, ban_until=None,
ban_message=None, ban_message=None,
is_admin=None, is_admin=None,
last_location=None,
): ):
response = await self.chats_stub.update_chat( response = await self.chats_stub.update_chat(
UpdateChatRequest( UpdateChatRequest(
@ -116,7 +115,6 @@ class IdmApi2GrpcClient(AioThing):
ban_until=ban_until, ban_until=ban_until,
ban_message=ban_message, ban_message=ban_message,
is_admin=is_admin, is_admin=is_admin,
last_location=last_location,
), ),
metadata=( metadata=(
('request-id', request_id), ('request-id', request_id),

View File

@ -12,7 +12,6 @@ message ChatData {
int32 ban_until = 6; int32 ban_until = 6;
string ban_message = 7; string ban_message = 7;
bool is_admin = 8; bool is_admin = 8;
string tzinfo = 9;
bool is_subscribed = 10; bool is_subscribed = 10;
int64 created_at = 11; int64 created_at = 11;
} }
@ -57,9 +56,6 @@ message UpdateChatRequest {
oneof is_admin_oneof { oneof is_admin_oneof {
bool is_admin = 7; bool is_admin = 7;
} }
oneof last_location_oneof {
idm.api2.proto.Location last_location = 8;
}
} }
service Chats { service Chats {

View File

@ -21,7 +21,7 @@ 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\"idm/api2/proto/chats_service.proto\x12\x0eidm.api2.proto\x1a\x1didm/api2/proto/location.proto\"\xf2\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\x0e\n\x06tzinfo\x18\t \x01(\t\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\"\x98\x03\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\x12\x31\n\rlast_location\x18\x08 \x01(\x0b\x32\x18.idm.api2.proto.LocationH\x06\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_oneofB\x15\n\x13last_location_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/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'
, ,
dependencies=[idm_dot_api2_dot_proto_dot_location__pb2.DESCRIPTOR,]) dependencies=[idm_dot_api2_dot_proto_dot_location__pb2.DESCRIPTOR,])
@ -93,21 +93,14 @@ _CHATDATA = _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='tzinfo', full_name='idm.api2.proto.ChatData.tzinfo', index=8, name='is_subscribed', full_name='idm.api2.proto.ChatData.is_subscribed', index=8,
number=9, 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='is_subscribed', full_name='idm.api2.proto.ChatData.is_subscribed', index=9,
number=10, type=8, cpp_type=7, label=1, number=10, 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=10, name='created_at', full_name='idm.api2.proto.ChatData.created_at', index=9,
number=11, type=3, cpp_type=2, label=1, number=11, 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,
@ -126,7 +119,7 @@ _CHATDATA = _descriptor.Descriptor(
oneofs=[ oneofs=[
], ],
serialized_start=86, serialized_start=86,
serialized_end=328, serialized_end=312,
) )
@ -157,8 +150,8 @@ _CHATSDATA = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=330, serialized_start=314,
serialized_end=382, serialized_end=366,
) )
@ -203,8 +196,8 @@ _CREATECHATREQUEST = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=384, serialized_start=368,
serialized_end=456, serialized_end=440,
) )
@ -235,8 +228,8 @@ _GETCHATREQUEST = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=458, serialized_start=442,
serialized_end=491, serialized_end=475,
) )
@ -272,8 +265,8 @@ _LISTCHATSREQUEST = _descriptor.Descriptor(
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[]), fields=[]),
], ],
serialized_start=493, serialized_start=477,
serialized_end=565, serialized_end=549,
) )
@ -334,13 +327,6 @@ _UPDATECHATREQUEST = _descriptor.Descriptor(
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(
name='last_location', full_name='idm.api2.proto.UpdateChatRequest.last_location', index=7,
number=8, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
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=[ extensions=[
], ],
@ -382,21 +368,15 @@ _UPDATECHATREQUEST = _descriptor.Descriptor(
index=5, containing_type=None, index=5, containing_type=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
fields=[]), fields=[]),
_descriptor.OneofDescriptor(
name='last_location_oneof', full_name='idm.api2.proto.UpdateChatRequest.last_location_oneof',
index=6, containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[]),
], ],
serialized_start=568, serialized_start=552,
serialized_end=976, serialized_end=886,
) )
_CHATSDATA.fields_by_name['chats'].message_type = _CHATDATA _CHATSDATA.fields_by_name['chats'].message_type = _CHATDATA
_LISTCHATSREQUEST.oneofs_by_name['banned_at_moment_oneof'].fields.append( _LISTCHATSREQUEST.oneofs_by_name['banned_at_moment_oneof'].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_oneof']
_UPDATECHATREQUEST.fields_by_name['last_location'].message_type = idm_dot_api2_dot_proto_dot_location__pb2._LOCATION
_UPDATECHATREQUEST.oneofs_by_name['language_oneof'].fields.append( _UPDATECHATREQUEST.oneofs_by_name['language_oneof'].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_oneof']
@ -415,9 +395,6 @@ _UPDATECHATREQUEST.fields_by_name['ban_message'].containing_oneof = _UPDATECHATR
_UPDATECHATREQUEST.oneofs_by_name['is_admin_oneof'].fields.append( _UPDATECHATREQUEST.oneofs_by_name['is_admin_oneof'].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_oneof']
_UPDATECHATREQUEST.oneofs_by_name['last_location_oneof'].fields.append(
_UPDATECHATREQUEST.fields_by_name['last_location'])
_UPDATECHATREQUEST.fields_by_name['last_location'].containing_oneof = _UPDATECHATREQUEST.oneofs_by_name['last_location_oneof']
DESCRIPTOR.message_types_by_name['ChatData'] = _CHATDATA DESCRIPTOR.message_types_by_name['ChatData'] = _CHATDATA
DESCRIPTOR.message_types_by_name['ChatsData'] = _CHATSDATA DESCRIPTOR.message_types_by_name['ChatsData'] = _CHATSDATA
DESCRIPTOR.message_types_by_name['CreateChatRequest'] = _CREATECHATREQUEST DESCRIPTOR.message_types_by_name['CreateChatRequest'] = _CREATECHATREQUEST
@ -477,8 +454,8 @@ _CHATS = _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=979, serialized_start=889,
serialized_end=1291, serialized_end=1201,
methods=[ methods=[
_descriptor.MethodDescriptor( _descriptor.MethodDescriptor(
name='create_chat', name='create_chat',

View File

@ -11,8 +11,14 @@ def images_install():
container_pull( container_pull(
name = "ubuntu", name = "ubuntu",
digest = "sha256:45ff0162921e61c004010a67db1bee7d039a677bed0cb294e61f2b47346d42b3", digest = "sha256:5403064f94b617f7975a19ba4d1a1299fd584397f6ee4393d0e16744ed11aab1",
registry = "index.docker.io", registry = "index.docker.io",
repository = "library/ubuntu", repository = "library/ubuntu",
tag = "20.10", tag = "20.04",
)
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",
"libprotobuf23", "libprotobuf17",
"libssl1.1", "libssl1.1",
], ],
) )

View File

@ -15,13 +15,8 @@ py3_image(
base = "//images/production:base-python-image", base = "//images/production:base-python-image",
data = [ data = [
"configs/base.yaml", "configs/base.yaml",
"configs/custom.yaml",
"configs/development.yaml",
"configs/logging.yaml", "configs/logging.yaml",
"configs/metrics.yaml",
"configs/production.yaml",
"configs/promotions.yaml", "configs/promotions.yaml",
"configs/testing.yaml",
], ],
main = "main.py", main = "main.py",
srcs_version = "PY3ONLY", srcs_version = "PY3ONLY",

View File

@ -1,8 +1,6 @@
# Nexus Search: Telegram Bot # Nexus Search: Telegram Bot
`Bot` is a daemon processing user input from Telegram. This version has cut `configs` `Bot` is a daemon processing user input from Telegram.
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 three other essential parts: The bot requires three other essential parts:
- IDM API (auth) - IDM API (auth)
@ -10,109 +8,3 @@ The bot requires three other essential parts:
- Nexus Meta API (rescoring API for Summa Search server) - Nexus Meta API (rescoring API for Summa Search server)
or their substitutions or their substitutions
## Sample `configs/base.yaml`
```yaml
---
application:
# Amazon Recipient Email in /donate message
amazon_gift_card_recipient: pirate@ship.space
# Amazon URL for buying card in /donate message
amazon_gift_card_url: https://www.amazon.com/dp/B07TMNGSN4
bot_version: 1.6.0
# Bitcoin Donation address in /donate message
btc_donate_address: 3QbF3zRQVjn3qMJBSbmLC1gb6VUc555xkw
# List of chat IDs that is allowed to bypass maintenance mode
bypass_maintenance: []
# Debugging mode
debug: true
# All users (except `bypass_maintenance` ones) will get UPGRADE_MAINTENANCE message in response
is_maintenance_mode: false
# Disable /settings, uploading new articles (can be used while vacuuming backend Postgres)
# and preventing creation of new users
is_read_only_mode: false
# Require subscription to `related_channel` before allowing to use the bot
is_subscription_required: false
# Libera Pay URL in /donate message
libera_pay_url:
maintenance_picture_url:
nexus_version: InterCom
# Default page size for SERP
page_size: 5
# Length of generated Request-ID used for tracking requests across all backends
request_id_length: 12
# Enabled schemas (passed to Nexus Meta API)
schemas:
- scimag
- scitech
# Length of generated Session-ID used in commands to clue user sessions
session_id_length: 8
too_difficult_picture_url:
upgrade_maintenance_picture_url:
# Configuring behaviour of the bot in some cases
views:
settings:
has_discovery_button: true
has_language_buttons: true
has_location_button: false
has_router: false
has_system_messaging_button: true
hub:
url:
idm:
url:
log_path: '/var/log/nexus-bot/{{ ENV_TYPE }}'
meta_api:
url:
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 }}'
# WARNING! Potentially buggy telethon option. Sometimes it goes mad and overload users with tons of messages
# Collect missed messages at startup time and answer to them
catch_up: false
# Telegram account for forwarding copyright infringements from /copyright command
copyright_infringement_account:
# Telethon database for keeping cache
database:
session_id: '/usr/lib/nexus-bot/{{ ENV_TYPE }}/session.db'
# Enabled handlers
handlers:
- nexus.bot.handlers.admin.AdminHandler
- nexus.bot.handlers.ban.BanHandler
- nexus.bot.handlers.ban.BanlistHandler
- nexus.bot.handlers.ban.UnbanHandler
- nexus.bot.handlers.contact.ContactHandler
- nexus.bot.handlers.copyright.CopyrightHandler
- nexus.bot.handlers.close.CloseHandler
- nexus.bot.handlers.donate.DonateHandler
- nexus.bot.handlers.download.DownloadHandler
- nexus.bot.handlers.emoji.EmojiHandler
- nexus.bot.handlers.help.HelpHandler
- nexus.bot.handlers.referencing_to.ReferencingToHandler
- nexus.bot.handlers.referencing_to.ReferencingToPagingHandler
- nexus.bot.handlers.roll.RollHandler
- nexus.bot.handlers.settings.SettingsButtonsHandler
- nexus.bot.handlers.settings.SettingsRouterHandler
- nexus.bot.handlers.settings.SettingsManualHandler
- nexus.bot.handlers.shortlink.ShortlinkHandler
- nexus.bot.handlers.submit.SubmitHandler
- nexus.bot.handlers.start.StartHandler
- nexus.bot.handlers.stop.StopHandler
- nexus.bot.handlers.view.ViewHandler
- nexus.bot.handlers.vote.VoteHandler
- nexus.bot.handlers.noop.NoopHandler
- nexus.bot.handlers.search.SearchHandler
- nexus.bot.handlers.search.SearchEditHandler
- nexus.bot.handlers.search.SearchPagingHandler
# Channel that will be shown in /help, /donate, /contact and in promotions
related_channel: '@nexus_search'
```

View File

@ -0,0 +1,98 @@
---
application:
# Amazon Recipient Email in /donate message
amazon_gift_card_recipient: pirate@ship.space
# Amazon URL for buying card in /donate message
amazon_gift_card_url: https://www.amazon.com/dp/B07TMNGSN4
bot_version: 1.6.0
# Bitcoin Donation address in /donate message
btc_donate_address: 3QbF3zRQVjn3qMJBSbmLC1gb6VUc555xkw
# List of chat IDs that is allowed to bypass maintenance mode
bypass_maintenance: []
# Debugging mode
debug: true
# All users (except `bypass_maintenance` ones) will get UPGRADE_MAINTENANCE message in response
is_maintenance_mode: false
# Disable /settings, uploading new articles (can be used while vacuuming backend Postgres)
# and preventing creation of new users
is_read_only_mode: false
# Require subscription to `related_channel` before allowing to use the bot
is_subscription_required: false
# Libera Pay URL in /donate message
libera_pay_url:
maintenance_picture_url:
nexus_version: InterCom
# Default page size for SERP
page_size: 5
# Length of generated Request-ID used for tracking requests across all backends
request_id_length: 12
# Enabled schemas (passed to Nexus Meta API)
schemas:
- scimag
- scitech
# Length of generated Session-ID used in commands to clue user sessions
session_id_length: 8
too_difficult_picture_url:
upgrade_maintenance_picture_url:
# Configuring behaviour of the bot in some cases
views:
settings:
has_discovery_button: true
has_language_buttons: true
has_system_messaging_button: true
hub:
url:
idm:
url:
log_path: '/var/log/nexus-bot/{{ ENV_TYPE }}'
meta_api:
url:
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 }}'
# WARNING! Potentially buggy telethon option. Sometimes it goes mad and overload users with tons of messages
# Collect missed messages at startup time and answer to them
catch_up: false
# Telegram account for forwarding copyright infringements from /copyright command
copyright_infringement_account:
# Telethon database for keeping cache
database:
session_id: '/usr/lib/nexus-bot/{{ ENV_TYPE }}/session.db'
# Enabled handlers
handlers:
- nexus.bot.handlers.admin.AdminHandler
- nexus.bot.handlers.ban.BanHandler
- nexus.bot.handlers.ban.BanlistHandler
- nexus.bot.handlers.ban.UnbanHandler
- nexus.bot.handlers.contact.ContactHandler
- nexus.bot.handlers.copyright.CopyrightHandler
- nexus.bot.handlers.close.CloseHandler
- nexus.bot.handlers.donate.DonateHandler
- nexus.bot.handlers.download.DownloadHandler
- nexus.bot.handlers.emoji.EmojiHandler
- nexus.bot.handlers.help.HelpHandler
- nexus.bot.handlers.referencing_to.ReferencingToHandler
- nexus.bot.handlers.referencing_to.ReferencingToPagingHandler
- nexus.bot.handlers.roll.RollHandler
- nexus.bot.handlers.settings.SettingsButtonsHandler
- nexus.bot.handlers.settings.SettingsHandler
- nexus.bot.handlers.shortlink.ShortlinkHandler
- nexus.bot.handlers.submit.SubmitHandler
- nexus.bot.handlers.start.StartHandler
- nexus.bot.handlers.stop.StopHandler
- nexus.bot.handlers.view.ViewHandler
- nexus.bot.handlers.vote.VoteHandler
- nexus.bot.handlers.noop.NoopHandler
- nexus.bot.handlers.search.SearchHandler
- nexus.bot.handlers.search.SearchEditHandler
- nexus.bot.handlers.search.SearchPagingHandler
# Channel that will be shown in /help, /donate, /contact and in promotions
related_channel: '@nexus_search'

View File

@ -1,15 +1,6 @@
from izihawa_utils.podolsky_encoding import encode
from library.telegram.base import RequestContext from library.telegram.base import RequestContext
from nexus.bot.widgets.settings_widget import ( from nexus.bot.widgets.settings_widget import SettingsWidget
SettingsManualWidget, from telethon import events
SettingsRouterWidget,
)
from nexus.translations import t
from telethon import (
Button,
events,
functions,
)
from .base import ( from .base import (
BaseCallbackQueryHandler, BaseCallbackQueryHandler,
@ -17,7 +8,7 @@ from .base import (
) )
class SettingsRouterHandler(BaseHandler): class SettingsHandler(BaseHandler):
filter = events.NewMessage(incoming=True, pattern='^/settings(@[A-Za-z0-9_]+)?$') filter = events.NewMessage(incoming=True, pattern='^/settings(@[A-Za-z0-9_]+)?$')
is_group_handler = True is_group_handler = True
writing_handler = True writing_handler = True
@ -25,43 +16,7 @@ class SettingsRouterHandler(BaseHandler):
async def handler(self, event: events.ChatAction, request_context: RequestContext): async def handler(self, event: events.ChatAction, request_context: RequestContext):
request_context.add_default_fields(mode='settings_router') request_context.add_default_fields(mode='settings_router')
request_context.statbox(action='show') request_context.statbox(action='show')
if self.application.config['application']['views']['settings']['has_router']: settings_widget = SettingsWidget(
settings_router_widget = SettingsRouterWidget(
application=self.application,
chat=request_context.chat,
request_id=request_context.request_id,
)
text, buttons = await settings_router_widget.render()
else:
settings_widget = SettingsManualWidget(
application=self.application,
chat=request_context.chat,
is_group_mode=event.is_group or event.is_channel,
request_id=request_context.request_id,
)
text, buttons = await settings_widget.render()
await event.reply(text, buttons=buttons)
class SettingsAutomaticHandler(BaseHandler):
filter = events.NewMessage(incoming=True, pattern=f'^🌎{encode("1")}')
async def handler(self, event, request_context: RequestContext):
request_context.add_default_fields(mode='settings_automatic')
request_context.statbox(action='show')
await event.reply(
f'{t("SEND_YOUR_LOCATION", language=request_context.chat.language)}{encode("sg")}',
buttons=Button.clear()
)
class SettingsManualHandler(BaseHandler):
filter = events.NewMessage(incoming=True, pattern=f'^👇{encode("1")}')
async def handler(self, event, request_context: RequestContext):
request_context.add_default_fields(mode='settings_manual')
request_context.statbox(action='show')
settings_widget = SettingsManualWidget(
application=self.application, application=self.application,
chat=request_context.chat, chat=request_context.chat,
is_group_mode=event.is_group or event.is_channel, is_group_mode=event.is_group or event.is_channel,
@ -82,48 +37,17 @@ class SettingsButtonsHandler(BaseCallbackQueryHandler):
request_context.statbox(action='change', query=f'action_id: {action_id} data: {data}') request_context.statbox(action='change', query=f'action_id: {action_id} data: {data}')
settings_manual_widget = SettingsManualWidget( settings_widget = SettingsWidget(
application=self.application, application=self.application,
chat=request_context.chat, chat=request_context.chat,
is_group_mode=event.is_group or event.is_channel, is_group_mode=event.is_group or event.is_channel,
request_id=request_context.request_id, request_id=request_context.request_id,
) )
is_changed = await settings_manual_widget.process_action(action_id=action_id, data=data) is_changed = await settings_widget.process_action(action_id=action_id, data=data)
text, buttons = await settings_manual_widget.render() text, buttons = await settings_widget.render()
if not is_changed and not (event.is_group or event.is_channel): if not is_changed and not (event.is_group or event.is_channel):
await event.answer() await event.answer()
return return
if event.is_group or event.is_channel: if event.is_group or event.is_channel:
buttons = None buttons = None
await event.edit(text, buttons=buttons) await event.edit(text, buttons=buttons)
class GeoHandler(BaseHandler):
filter = events.NewMessage(incoming=True)
stop_propagation = False
async def handler(self, event, request_context: RequestContext):
request_context.add_default_fields(mode='geo')
if not event.geo:
return
request_context.statbox(action='geo', query=f'lon:{event.geo.long} lat:{event.geo.lat}')
result = await self.application.telegram_client(functions.messages.GetMessagesRequest(id=[event.id - 1]))
if not result.messages:
return
previous_message = result.messages[0]
if previous_message.message.endswith(encode("sg")):
request_context.statbox(action='catched_settings')
settings_manual_widget = SettingsManualWidget(
application=self.application,
chat=request_context.chat,
has_language_buttons=False,
is_group_mode=event.is_group or event.is_channel,
request_id=request_context.request_id,
)
await settings_manual_widget.set_last_location(lon=event.geo.long, lat=event.geo.lat)
text, buttons = await settings_manual_widget.render()
await event.reply(text, buttons=buttons or Button.clear())
raise events.StopPropagation()

View File

@ -1,8 +1,6 @@
from typing import Optional from typing import Optional
from idm.api2.proto.chats_service_pb2 import ChatData as Chat from idm.api2.proto.chats_service_pb2 import ChatData as Chat
from idm.api2.proto.location_pb2 import Location
from izihawa_utils.podolsky_encoding import encode
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
@ -32,23 +30,7 @@ boolean_emoji = {
} }
class SettingsRouterWidget: class SettingsWidget:
def __init__(self, application: TelegramApplication, chat: Chat, request_id: str = None):
self.application = application
self.chat = chat
self.request_id = request_id
async def render(self):
sa = f'🌎{encode("1")}{t("SETUP_AUTOMATICALLY", language=self.chat.language)}'
sm = f'👇{encode("1")}{t("SETUP_MANUALLY", language=self.chat.language)}'
return t("SETTINGS_ROUTER_HELP", language=self.chat.language), [[
Button.text(sa, resize=True, single_use=True),
Button.text(sm, resize=True, single_use=True),
Button.force_reply(),
]]
class SettingsManualWidget:
def __init__( def __init__(
self, self,
application: TelegramApplication, application: TelegramApplication,
@ -71,11 +53,12 @@ class SettingsManualWidget:
} }
async def _switch_language(self, target_language: str): async def _switch_language(self, target_language: str):
return 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.id,
language=target_language, language=target_language,
request_id=self.request_id, request_id=self.request_id,
) )
return self.chat
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(
@ -93,14 +76,6 @@ class SettingsManualWidget:
) )
return self.chat return self.chat
async def set_last_location(self, lon: float, lat: float):
self.chat = await self.application.idm_client.update_chat(
chat_id=self.chat.id,
last_location=Location(lon=lon, lat=lat),
request_id=self.request_id,
)
return
async def process_action(self, action_id: str, data: str): async def process_action(self, action_id: str, data: str):
old_chat = self.chat old_chat = self.chat
await self._actions[action_id](data) await self._actions[action_id](data)
@ -111,7 +86,6 @@ class SettingsManualWidget:
bot_version=self.application.config['application']['bot_version'], bot_version=self.application.config['application']['bot_version'],
nexus_version=self.application.config['application']['nexus_version'], nexus_version=self.application.config['application']['nexus_version'],
language=top_languages.get(self.chat.language, self.chat.language), language=top_languages.get(self.chat.language, self.chat.language),
tzinfo=self.chat.tzinfo or 'UTC',
) )
if not self.is_group_mode and self.application.config['application']['views']['settings']['has_discovery_button']: if not self.is_group_mode and self.application.config['application']['views']['settings']['has_discovery_button']:
text = f"{text}\n\n{t('NEXUS_DISCOVERY_DESCRIPTION', language=self.chat.language)}" text = f"{text}\n\n{t('NEXUS_DISCOVERY_DESCRIPTION', language=self.chat.language)}"
@ -151,8 +125,4 @@ class SettingsManualWidget:
data=f'/settings_sd_{1 - int(self.chat.is_discovery_enabled)}' data=f'/settings_sd_{1 - int(self.chat.is_discovery_enabled)}'
) )
]) ])
if self.application.config['application']['views']['settings']['has_location_button']:
buttons.append([
Button.request_location('Setup preferences automatically', resize=True)
])
return text, buttons return text, buttons

View File

@ -15,7 +15,6 @@ py3_image(
"configs/base.yaml", "configs/base.yaml",
"configs/logging.yaml", "configs/logging.yaml",
], ],
legacy_create_init = False,
main = "main.py", main = "main.py",
srcs_version = "PY3ONLY", srcs_version = "PY3ONLY",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],

View File

@ -137,14 +137,10 @@ en:
REPORT_OK_FILE: ✅ What Is Needed REPORT_OK_FILE: ✅ What Is Needed
SEARCHING: '`searching...`' SEARCHING: '`searching...`'
SEND_YOUR_LOCATION: Send your location (through the left attach button) SEND_YOUR_LOCATION: Send your location (through the left attach button)
SETTINGS_ROUTER_HELP: >
Settings could be set up automatically or manually.
Automatic mode will requests location to set timezone, language and georanking.
SETTINGS_TEMPLATE: | SETTINGS_TEMPLATE: |
**Bot Version:** {bot_version} **Bot Version:** {bot_version}
**Nexus Version:** {nexus_version} **Nexus Version:** {nexus_version}
**Language:** {language} **Language:** {language}
**Timezone:** {tzinfo}
SETUP_AUTOMATICALLY: Setup automatically SETUP_AUTOMATICALLY: Setup automatically
SETUP_MANUALLY: Setup manually SETUP_MANUALLY: Setup manually
SOURCES_UNAVAILABLE: '`{document}` is unavailable right now. Please, try later.' SOURCES_UNAVAILABLE: '`{document}` is unavailable right now. Please, try later.'
@ -300,14 +296,10 @@ es:
REPLY_MESSAGE_HAS_BEEN_DELETED: El mensaje de búsqueda ha sido (re)movido. Vuelve a buscar. REPLY_MESSAGE_HAS_BEEN_DELETED: El mensaje de búsqueda ha sido (re)movido. Vuelve a buscar.
SEARCHING: '`buscando...`' SEARCHING: '`buscando...`'
SEND_YOUR_LOCATION: Envía tu ubicación (a través del botón de adjuntar de la izquierda) SEND_YOUR_LOCATION: Envía tu ubicación (a través del botón de adjuntar de la izquierda)
SETTINGS_ROUTER_HELP: >
Los ajustes se pueden configurar de forma automática o manual.
El modo automático solicitará la ubicación para establecer la zona horaria, el idioma y la clasificación geográfica.
SETTINGS_TEMPLATE: | SETTINGS_TEMPLATE: |
**Versión del bot:** {bot_version} **Versión del bot:** {bot_version}
**Versión de Nexus:** {nexus_version} **Versión de Nexus:** {nexus_version}
**Idioma:** {language} **Idioma:** {language}
**Zona horaria:** {tzinfo}
SETUP_AUTOMATICALLY: Configurar automáticamente SETUP_AUTOMATICALLY: Configurar automáticamente
SETUP_MANUALLY: Configurar manualmente SETUP_MANUALLY: Configurar manualmente
SOURCES_UNAVAILABLE: '`{document}` no está disponible en este momento. Por favor intenta más tarde.' SOURCES_UNAVAILABLE: '`{document}` no está disponible en este momento. Por favor intenta más tarde.'
@ -440,14 +432,10 @@ it:
REPLY_MESSAGE_HAS_BEEN_DELETED: Il messaggio di ricerca è stato (ri-)mosso. Ripeti la ricerca. REPLY_MESSAGE_HAS_BEEN_DELETED: Il messaggio di ricerca è stato (ri-)mosso. Ripeti la ricerca.
SEARCHING: '`ricerca in corso...`' SEARCHING: '`ricerca in corso...`'
SEND_YOUR_LOCATION: Invia la tua posizione (tramite il pulsante allegato a sinistra) SEND_YOUR_LOCATION: Invia la tua posizione (tramite il pulsante allegato a sinistra)
SETTINGS_ROUTER_HELP: >
Le impostazioni possono essere configurate automaticamente o manualmente.
La modalità automatica richiederà la tua posizione per impostare il fuso orario, la lingua e il ranking geografico.
SETTINGS_TEMPLATE: | SETTINGS_TEMPLATE: |
**Versione del Bot:** {bot_version} **Versione del Bot:** {bot_version}
**Versione del Nexus:** {nexus_version} **Versione del Nexus:** {nexus_version}
**Lingua:** {language} **Lingua:** {language}
**Fuso orario:** {tzinfo}
SETUP_AUTOMATICALLY: Configura automaticamente SETUP_AUTOMATICALLY: Configura automaticamente
SETUP_MANUALLY: Configura manualmente SETUP_MANUALLY: Configura manualmente
SOURCES_UNAVAILABLE: '`{document}` non è disponibile adesso. Per favore, prova più tardi.' SOURCES_UNAVAILABLE: '`{document}` non è disponibile adesso. Per favore, prova più tardi.'
@ -599,14 +587,10 @@ pb:
REPLY_MESSAGE_HAS_BEEN_DELETED: A mensagem de pesquisa foi (re)movida. Pesquise novamente. REPLY_MESSAGE_HAS_BEEN_DELETED: A mensagem de pesquisa foi (re)movida. Pesquise novamente.
SEARCHING: '`procurando...`' SEARCHING: '`procurando...`'
SEND_YOUR_LOCATION: Envie sua localização (através do botão esquerdo anexar) SEND_YOUR_LOCATION: Envie sua localização (através do botão esquerdo anexar)
SETTINGS_ROUTER_HELP: >
As configurações podem ser configuradas automática ou manualmente.
O modo automático solicitará um local para definir o fuso horário, o idioma e a classificação geográfica.
SETTINGS_TEMPLATE: | SETTINGS_TEMPLATE: |
**Versão do bot:** {bot_version} **Versão do bot:** {bot_version}
**Versão do Nexus:** {nexus_version} **Versão do Nexus:** {nexus_version}
**Idioma:** {language} **Idioma:** {language}
**Fuso horário:** {tzinfo}
SETUP_AUTOMATICALLY: Configurar automaticamente SETUP_AUTOMATICALLY: Configurar automaticamente
SETUP_MANUALLY: Configurar manualmente SETUP_MANUALLY: Configurar manualmente
SOURCES_UNAVAILABLE: '`{document}` está indisponível nesse momento. Por favor, tente mais tarde.' SOURCES_UNAVAILABLE: '`{document}` está indisponível nesse momento. Por favor, tente mais tarde.'
@ -744,14 +728,10 @@ ru:
REPLY_MESSAGE_HAS_BEEN_DELETED: Сообщение с запросом было удалено/перемещено, выполните поиск заново. REPLY_MESSAGE_HAS_BEEN_DELETED: Сообщение с запросом было удалено/перемещено, выполните поиск заново.
SEARCHING: '`ищем...`' SEARCHING: '`ищем...`'
SEND_YOUR_LOCATION: Отправьте вашу геопозицию (кнопка прикрепления файла слева) SEND_YOUR_LOCATION: Отправьте вашу геопозицию (кнопка прикрепления файла слева)
SETTINGS_ROUTER_HELP: >
Настройки можно установить автоматически или в ручном режиме.
Автоматический режим запросит ваше местнонахождение для установки часового пояса, языка и георанжирования.
SETTINGS_TEMPLATE: | SETTINGS_TEMPLATE: |
**Версия бота:** {bot_version} **Версия бота:** {bot_version}
**Версия индекса Nexus:** {nexus_version} **Версия индекса Nexus:** {nexus_version}
**Язык:** {language} **Язык:** {language}
**Часовой пояс:** {tzinfo}
SETUP_AUTOMATICALLY: Установить автоматически SETUP_AUTOMATICALLY: Установить автоматически
SETUP_MANUALLY: Установить вручную SETUP_MANUALLY: Установить вручную
SOURCES_UNAVAILABLE: 'Прямо сейчас `{document}` недоступен. Попробуйте скачать его позже.' SOURCES_UNAVAILABLE: 'Прямо сейчас `{document}` недоступен. Попробуйте скачать его позже.'

View File

@ -2,22 +2,36 @@ load("@bazel_tools//tools/python:toolchain.bzl", "py_runtime_pair")
load("@rules_python//python:defs.bzl", "py_runtime") load("@rules_python//python:defs.bzl", "py_runtime")
py_runtime( py_runtime(
name = "python3_runtime", name = "python2.7",
files = ["@python_interpreter//:files"], files = [],
interpreter = "@python_interpreter//:python_bin", interpreter_path = select({
"//:osx": "/usr/local/bin/python2.7",
"//:linux": "/usr/bin/python2.7",
}),
python_version = "PY2",
visibility = ["//visibility:public"],
)
py_runtime(
name = "python3",
files = [],
interpreter_path = select({
"//:osx": "/usr/local/bin/python3.9",
"//:linux": "/usr/bin/python3.9",
}),
python_version = "PY3", python_version = "PY3",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
py_runtime_pair( py_runtime_pair(
name = "py_runtime_pair", name = "hyperboria_py_runtime_pair",
py2_runtime = None, py2_runtime = ":python2.7",
py3_runtime = ":python3_runtime", py3_runtime = ":python3",
) )
toolchain( toolchain(
name = "py_3_toolchain", name = "py_toolchain",
toolchain = ":py_runtime_pair", toolchain = ":hyperboria_py_runtime_pair",
toolchain_type = "@bazel_tools//tools/python:toolchain_type", toolchain_type = "@bazel_tools//tools/python:toolchain_type",
) )