- [nexus] Refactoring

- [nexus] Switch bot
  - [bot] Added extra receivers functionality

GitOrigin-RevId: 68fc32d3e79ff411758f54f435fe8680fc42dead
This commit is contained in:
the-superpirate 2022-03-28 17:39:36 +03:00
parent 7477616615
commit dd23846059
636 changed files with 14016 additions and 33784 deletions

144
WORKSPACE
View File

@ -17,34 +17,34 @@ http_archive(
# ToDo: wait for https://github.com/bazelbuild/rules_docker/pull/1638
http_archive(
name = "io_bazel_rules_docker",
sha256 = "5aa15ff7a83f8de8ff0346bd8274fb82eec52c947106a066dc190c2624ec1cb4",
strip_prefix = "rules_docker-aefbc69e5f758403d50f789eee55b30a3d947418",
sha256 = "c2a283bea1ea30a3ceb9e5388a4c8c8eef68a815ac86f1d381f9d35cdee57f1b",
strip_prefix = "rules_docker-46d29e34399a992087c857b13d8dcb8ec80dfd85",
urls = [
"https://github.com/the-superpirate/rules_docker/archive/aefbc69e5f758403d50f789eee55b30a3d947418.tar.gz",
"https://github.com/the-superpirate/rules_docker/archive/46d29e34399a992087c857b13d8dcb8ec80dfd85.tar.gz",
],
)
http_archive(
name = "build_bazel_rules_nodejs",
sha256 = "a160d9ac88f2aebda2aa995de3fa3171300c076f06ad1d7c2e1385728b8442fa",
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/3.4.1/rules_nodejs-3.4.1.tar.gz"],
sha256 = "f7037c8e295fdc921f714962aee7c496110052511e2b14076bd8e2d46bc9819c",
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/4.4.5/rules_nodejs-4.4.5.tar.gz"],
)
http_archive(
name = "io_bazel_rules_k8s",
sha256 = "c1c5a692ec994e99e9e7e77ae693086074d6dedfe72e6930efbcc66d30264032",
strip_prefix = "rules_k8s-f1c6399cdd691b7aca90073398e8f690ec8992c6",
sha256 = "a08850199d6900328ef899906717fb1dfcc6cde62701c63725748b2e6ca1d5d9",
strip_prefix = "rules_k8s-d05cbea5c56738ef02c667c10951294928a1d64a",
urls = [
"https://github.com/bazelbuild/rules_k8s/archive/f1c6399cdd691b7aca90073398e8f690ec8992c6.tar.gz",
"https://github.com/bazelbuild/rules_k8s/archive/d05cbea5c56738ef02c667c10951294928a1d64a.tar.gz",
],
)
http_archive(
name = "rules_rust",
sha256 = "d10dd5581f66ee169071ee06d52c52c8c7ca7467ac6266e301c0820d289b0f0b",
strip_prefix = "rules_rust-336e1934b07211fb8736c19749919ef94df4df68",
sha256 = "30c1b40d77a262e3f7dba6e4267fe4695b5eb1e68debc6aa06c3e09d429ae19a",
strip_prefix = "rules_rust-0.1.0",
urls = [
"https://github.com/bazelbuild/rules_rust/archive/336e1934b07211fb8736c19749919ef94df4df68.tar.gz",
"https://github.com/bazelbuild/rules_rust/archive/0.1.0.tar.gz",
],
)
@ -67,40 +67,17 @@ http_archive(
)
http_archive(
name = "rules_proto",
sha256 = "aa1ee19226f707d44bee44c720915199c20c84a23318bb0597ed4e5c873ccbd5",
strip_prefix = "rules_proto-40298556293ae502c66579620a7ce867d5f57311",
urls = [
"https://github.com/bazelbuild/rules_proto/archive/40298556293ae502c66579620a7ce867d5f57311.tar.gz",
],
name = "rules_proto_grpc",
sha256 = "507e38c8d95c7efa4f3b1c0595a8e8f139c885cb41a76cab7e20e4e67ae87731",
strip_prefix = "rules_proto_grpc-4.1.1",
urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/4.1.1.tar.gz"],
)
http_archive(
name = "rules_python",
sha256 = "b228318a786d99b665bc83bd6cdb81512cae5f8eb15e8cd19f9956604b8939f5",
strip_prefix = "rules_python-a4a1ccffc666db5376342789ad021a943fb84256",
urls = [
"https://github.com/bazelbuild/rules_python/archive/a4a1ccffc666db5376342789ad021a943fb84256.tar.gz",
],
)
http_archive(
name = "subpar",
sha256 = "481233d60c547e0902d381cd4fb85b63168130379600f330821475ad234d9336",
strip_prefix = "subpar-9fae6b63cfeace2e0fb93c9c1ebdc28d3991b16f",
urls = [
"https://github.com/google/subpar/archive/9fae6b63cfeace2e0fb93c9c1ebdc28d3991b16f.tar.gz",
],
)
http_archive(
name = "cython",
build_file = "@com_github_grpc_grpc//third_party:cython.BUILD",
sha256 = "e2e38e1f0572ca54d6085df3dec8b607d20e81515fb80215aed19c81e8fe2079",
strip_prefix = "cython-0.29.21",
urls = [
"https://github.com/cython/cython/archive/0.29.21.tar.gz",
],
sha256 = "15f84594af9da06750ceb878abbf129241421e3abbd6e36893041188db67f2fb",
strip_prefix = "rules_python-0.7.0",
urls = ["https://github.com/bazelbuild/rules_python/archive/0.7.0.tar.gz"],
)
# Images Install
@ -109,37 +86,38 @@ load("//images:install.bzl", "images_install")
images_install()
# Go
load("//rules/go:setup.bzl", "go_setup")
go_setup()
load("//rules/go:install.bzl", "go_install")
go_install()
# Python
register_toolchains("//rules/python:py_toolchain")
load("@rules_python//python:pip.bzl", "pip_install")
load("@rules_python//python:pip.bzl", "pip_parse")
pip_install(
pip_parse(
name = "pip_modules",
requirements = "//rules/python:requirements.txt",
requirements_lock = "//rules/python:requirements-lock.txt",
)
load("@pip_modules//:requirements.bzl", "install_deps")
install_deps()
# Proto / gRPC
http_archive(
name = "rules_proto_grpc",
sha256 = "7954abbb6898830cd10ac9714fbcacf092299fda00ed2baf781172f545120419",
strip_prefix = "rules_proto_grpc-3.1.1",
urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/3.1.1.tar.gz"],
)
load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_repos", "rules_proto_grpc_toolchains")
rules_proto_grpc_toolchains()
rules_proto_grpc_repos()
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
rules_proto_dependencies()
rules_proto_toolchains()
load("@rules_proto_grpc//js:repositories.bzl", "js_repos")
js_repos()
@ -160,28 +138,11 @@ maven_fetch_remote_artifacts()
# Rust
load("@rules_rust//rust:repositories.bzl", "rust_repository_set")
load("@rules_rust//rust:repositories.bzl", "rust_repositories")
rust_version = "1.51.0"
rustfmt_version = "1.4.20"
rust_repository_set(
name = "rust_linux_x86_64",
edition = "2018",
exec_triple = "x86_64-unknown-linux-gnu",
extra_target_triples = ["wasm32-unknown-unknown"],
rustfmt_version = rustfmt_version,
version = rust_version,
)
rust_repository_set(
name = "rust_darwin_x86_64",
edition = "2018",
exec_triple = "x86_64-apple-darwin",
extra_target_triples = ["wasm32-unknown-unknown"],
rustfmt_version = rustfmt_version,
version = rust_version,
rust_repositories(
edition = "2021",
version = "1.59.0",
)
load("//rules/rust:crates.bzl", "raze_fetch_remote_crates")
@ -192,17 +153,8 @@ raze_fetch_remote_crates()
load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories", "yarn_install")
node_repositories(
node_repositories = {
"15.5.1-darwin_amd64": ("node-v15.5.1-darwin-x64.tar.gz", "node-v15.5.1-darwin-x64", "4507dab0481b0b5374b5758b1eba7d105c8cbcb173548119b04d9ef7d9f1d40f"),
"15.5.1-linux_amd64": ("node-v15.5.1-linux-x64.tar.xz", "node-v15.5.1-linux-x64", "dbc41a611d99aedf2cfd3d0acc50759a6b9084c7447862e990f51958d4a7aa41"),
"15.5.1-windows_amd64": ("node-v15.5.1-win-x64.zip", "node-v15.5.1-win-x64", "e1f826f9647fc7058b48c669991956a427fe4b6ccefa415a18b41715483f958d"),
"15.5.1-linux_s390x": ("node-v15.5.1-linux-s390x.tar.gz", "node-v15.5.1-linux-s390x", "e05f949ea11e2aafc08a7972c0f41a11a3628762e857d44965e0605d3bcd143f"),
"15.5.1-linux_arm64": ("node-v15.5.1-linux-arm64.tar.gz", "node-v15.5.1-linux-arm64", "a2d14db86c6f8a070f227940ea44a3409966f6bed14df0ec6f676fe2e2f601c9"),
},
node_version = "15.5.1",
package_json = ["//rules/nodejs:package.json"],
preserve_symlinks = True,
yarn_version = "1.22.4",
)
yarn_install(
@ -227,17 +179,6 @@ container_repositories()
load("@io_bazel_rules_docker//repositories:deps.bzl", container_deps = "deps")
# ToDo: temorary fix as registry was broken at 24.04.2021
load("@bazel_gazelle//:deps.bzl", "go_repository")
go_repository(
name = "com_github_google_go_containerregistry",
importpath = "github.com/google/go-containerregistry",
strip_prefix = "google-go-containerregistry-8a28419",
type = "tar.gz",
urls = ["https://api.github.com/repos/google/go-containerregistry/tarball/8a2841911ffee4f6892ca0083e89752fb46c48dd"], # v0.1.4
)
container_deps()
load("@io_bazel_rules_docker//repositories:py_repositories.bzl", "py_deps")
@ -259,7 +200,7 @@ rust_image_repos()
# K8s
load("@io_bazel_rules_k8s//k8s:k8s.bzl", "k8s_repositories")
load("@io_bazel_rules_k8s//k8s:k8s.bzl", "k8s_defaults", "k8s_repositories")
k8s_repositories()
@ -267,6 +208,11 @@ load("@io_bazel_rules_k8s//k8s:k8s_go_deps.bzl", k8s_go_deps = "deps")
k8s_go_deps()
k8s_defaults(
name = "k8s_deploy",
image_chroot = "registry.infra.svc.cluster.local",
)
# Miscellaneous
load("//rules/misc:setup.bzl", "rules_misc_setup_internal")

View File

@ -15,7 +15,6 @@ py3_image(
"*.py",
"configs/**/*.py",
"daemons/**/*.py",
"models/**",
"proto/**",
"services/**",
],
@ -36,8 +35,8 @@ py3_image(
requirement("grpcio"),
requirement("pypika"),
requirement("uvloop"),
"//idm/api/proto:idm_grpc_py",
"//idm/api/proto:idm_proto_py",
"//idm/api/proto:grpc_py",
"//idm/api/proto:proto_py",
"//library/aiogrpctools",
requirement("aiokit"),
"//library/aiopostgres",
@ -54,7 +53,7 @@ container_push(
name = "push-testing",
format = "Docker",
image = ":image",
registry = "registry.example.com",
registry = "registry.infra.svc.cluster.local",
repository = "idm-api",
tag = "testing",
)
@ -63,7 +62,7 @@ container_push(
name = "push-latest",
format = "Docker",
image = ":image",
registry = "registry.example.com",
registry = "registry.infra.svc.cluster.local",
repository = "idm-api",
tag = "latest",
)

View File

@ -7,8 +7,8 @@ py_library(
visibility = ["//visibility:public"],
deps = [
requirement("tenacity"),
"//idm/api/proto:idm_grpc_py",
"//idm/api/proto:idm_proto_py",
"//idm/api/proto:grpc_py",
"//idm/api/proto:proto_py",
requirement("aiogrpcclient"),
],
)

View File

@ -1,8 +1,6 @@
from typing import Optional
from aiogrpcclient import BaseGrpcClient
from grpc import StatusCode
from grpc.experimental.aio import AioRpcError
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 import (
@ -12,12 +10,6 @@ from idm.api.proto.chat_manager_service_pb2 import (
UpdateChatRequest,
)
from idm.api.proto.chat_manager_service_pb2_grpc import ChatManagerStub
from tenacity import (
retry,
retry_if_exception,
stop_after_attempt,
wait_fixed,
)
class IdmApiGrpcClient(BaseGrpcClient):
@ -44,14 +36,6 @@ class IdmApiGrpcClient(BaseGrpcClient):
)
return response
@retry(
retry=retry_if_exception(
lambda e: isinstance(e, AioRpcError) and e.code() == StatusCode.UNAVAILABLE
),
reraise=True,
stop=stop_after_attempt(10),
wait=wait_fixed(5),
)
async def get_chat(
self,
chat_id: int,

View File

@ -6,13 +6,5 @@ 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'
related_channel: '@nexus_search'
port: 82
log_path: '/var/log/idm-api'

View File

@ -13,10 +13,10 @@ logging:
class: library.logging.formatters.TracebackFormatter
handlers:
debug:
class: logging.StreamHandler
class: library.logging.handlers.BaseFileHandler
formatter: default
filename: '{{ log_path }}/debug.log'
level: DEBUG
stream: 'ext://sys.stderr'
error:
class: library.logging.handlers.BaseFileHandler
filename: '{{ log_path }}/error.log'

View File

@ -1,86 +0,0 @@
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):
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.channel_name = telegram_config['related_channel']
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

View File

@ -1,9 +1,7 @@
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
@ -14,27 +12,21 @@ from library.logging import configure_logging
class GrpcServer(AioGrpcServer):
def __init__(self, config: Configurator):
super().__init__(address=config['grpc']['address'], port=config['grpc']['port'])
database = config['database']
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'],
conninfo=f'dbname={database["database"]} '
f'user={database["username"]} '
f'password={database["password"]} '
f'host={database["host"]}'
f'port={database["port"]}',
timeout=30,
pool_recycle=60,
maxsize=4,
)
self.admin_log_reader = AdminLogReader(
telegram_config=config['telegram'],
max_size=4,
)
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])

View File

@ -4,7 +4,7 @@ load("@rules_proto//proto:defs.bzl", "proto_library")
package(default_visibility = ["//visibility:public"])
proto_library(
name = "idm_proto",
name = "proto",
srcs = [
"chat_manager_service.proto",
],
@ -14,12 +14,12 @@ proto_library(
)
py_proto_library(
name = "idm_proto_py",
deps = [":idm_proto"],
name = "proto_py",
deps = [":proto"],
)
py_grpc_library(
name = "idm_grpc_py",
srcs = [":idm_proto"],
deps = [":idm_proto_py"],
name = "grpc_py",
srcs = [":proto"],
deps = [":proto_py"],
)

View File

@ -10,8 +10,8 @@ message Chat {
int32 ban_until = 6;
string ban_message = 7;
bool is_admin = 8;
bool is_subscribed = 9;
int64 created_at = 10;
int64 updated_at = 11;
}
message Chats {

View File

@ -1,3 +1,5 @@
import logging
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
@ -5,6 +7,7 @@ from idm.api.proto.chat_manager_service_pb2_grpc import (
ChatManagerServicer,
add_ChatManagerServicer_to_server,
)
from izihawa_utils.pb_to_json import MessageToDict
from library.aiogrpctools.base import (
BaseService,
aiogrpc_request_wrapper,
@ -18,19 +21,14 @@ from pypika import (
class ChatManagerService(ChatManagerServicer, BaseService):
chats_table = Table('chats')
def __init__(self, server, service_name, pool_holder, admin_log_reader):
def __init__(self, server, service_name, pool_holder):
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(
@ -75,7 +73,7 @@ class ChatManagerService(ChatManagerServicer, BaseService):
chat = await result.fetchone()
if chat is None:
await context.abort(StatusCode.NOT_FOUND, 'not_found')
return self.enrich_chat(ChatPb(**chat))
return ChatPb(**chat)
@aiogrpc_request_wrapper()
async def get_chat(self, request, context, metadata):
@ -95,7 +93,7 @@ class ChatManagerService(ChatManagerServicer, BaseService):
results = await session.execute(query)
chats = await results.fetchall()
return ChatsPb(
chats=list(map(lambda x: self.enrich_chat(ChatPb(**x)), chats))
chats=list(map(lambda x: ChatPb(**x), chats))
)
@aiogrpc_request_wrapper()
@ -109,4 +107,4 @@ class ChatManagerService(ChatManagerServicer, BaseService):
async with self.pool_holder.pool.acquire() as session:
result = await session.execute(query)
chat = await result.fetchone()
return self.enrich_chat(ChatPb(**chat))
return ChatPb(**chat)

View File

@ -11,8 +11,8 @@ def images_install():
container_pull(
name = "ubuntu",
digest = "sha256:45ff0162921e61c004010a67db1bee7d039a677bed0cb294e61f2b47346d42b3",
digest = "sha256:d0b4808a158b42b6efb3ae93abb567b1cb6ee097221813c0315390de0fa320b9",
registry = "index.docker.io",
repository = "library/ubuntu",
tag = "20.10",
tag = "21.10",
)

View File

@ -26,6 +26,7 @@ class AioGrpcServer(AioRootThing):
'address': self.address,
'mode': 'grpc',
'port': self.port,
'extras': [x.__class__.__name__ for x in self.starts + self.waits]
})
await self.server.start()
await self.server.wait_for_termination()
@ -80,6 +81,8 @@ def aiogrpc_request_wrapper(log=True):
return r
except aio.AbortError:
raise
except aio.AioRpcError as e:
await context.abort(e.code(), e.details())
except Exception as e:
serialized_request = MessageToDict(request, preserving_proto_field_name=True)
error_log(e, request=serialized_request, request_id=metadata.get('request-id'))
@ -107,8 +110,8 @@ def aiogrpc_streaming_request_wrapper(func):
mode=func.__name__,
request_id=metadata.get('request-id'),
)
except aio.AbortError:
raise
except aio.AioRpcError as e:
await context.abort(e.code(), e.details())
except Exception as e:
serialized_request = MessageToDict(request, preserving_proto_field_name=True)
error_log(e, request=serialized_request, request_id=metadata.get('request-id'))

View File

@ -10,7 +10,9 @@ py_library(
srcs_version = "PY3",
visibility = ["//visibility:public"],
deps = [
requirement("aiopg"),
requirement("psycopg"),
requirement("psycopg-pool"),
requirement("psycopg-binary"),
requirement("tenacity"),
requirement("aiokit"),
],

View File

@ -1,42 +1,51 @@
import aiopg
import psycopg2.extras
from typing import Optional
from aiokit import AioThing
from psycopg2 import OperationalError
from tenacity import (
retry,
retry_if_exception_type,
stop_after_attempt,
wait_fixed,
)
from psycopg.rows import tuple_row
from psycopg_pool import AsyncConnectionPool
class AioPostgresPoolHolder(AioThing):
def __init__(self, fn=aiopg.create_pool, *args, **kwargs):
def __init__(self, conninfo, timeout=30, min_size=1, max_size=4):
super().__init__()
self.fn = fn
self.args = args
self.kwargs = kwargs
self.pool = None
self.fn = lambda: AsyncConnectionPool(
conninfo=conninfo,
timeout=timeout,
min_size=min_size,
max_size=max_size,
)
@retry(
retry=retry_if_exception_type(OperationalError),
stop=stop_after_attempt(3),
wait=wait_fixed(1.0),
)
async def start(self):
if not self.pool:
self.pool = await self.fn(*self.args, **self.kwargs)
self.pool = self.fn()
async def stop(self):
if self.pool:
self.pool.close()
await self.pool.wait_closed()
await self.pool.close()
self.pool = None
async def execute(self, stmt, values=None, fetch=False, timeout=None, cursor_factory=psycopg2.extras.DictCursor):
async with self.pool.acquire() as conn:
async with conn.cursor(cursor_factory=cursor_factory) as cur:
await cur.execute(stmt, values, timeout=timeout)
if fetch:
return await cur.fetchall()
return cur.rowcount
async def iterate(
self,
stmt: str,
values=None,
row_factory=tuple_row,
cursor_name: Optional[str] = None,
itersize: Optional[int] = None,
):
if not self.pool:
raise RuntimeError('AioPostgresPoolHolder has not been started')
async with self.pool.connection() as conn:
async with conn.cursor(name=cursor_name, row_factory=row_factory) as cur:
if itersize is not None:
cur.itersize = itersize
await cur.execute(stmt, values)
async for row in cur:
yield row
async def execute(self, stmt: str, values=None, cursor_name: Optional[str] = None, row_factory=tuple_row):
if not self.pool:
raise RuntimeError('AioPostgresPoolHolder has not been started')
async with self.pool.connection() as conn:
async with conn.cursor(name=cursor_name, row_factory=row_factory) as cur:
await cur.execute(stmt, values)

View File

@ -86,8 +86,14 @@ class Configurator(RichDict):
elif isinstance(c, list):
return [self.walk_and_render(e) for e in c]
elif isinstance(c, dict):
for key in c:
for key in list(c.keys()):
c[key] = self.walk_and_render(c[key])
if key.endswith('_filepath'):
with open(c[key]) as f:
if c[key].endswith('.json'):
c[key.replace('_filepath', '')] = json.loads(f.read())
elif c[key].endswith('.yaml'):
c[key.replace('_filepath', '')] = yaml.safe_load(f.read())
return c
def update(self, new_config, basename=None, **kwargs):

View File

@ -1,12 +1,14 @@
import asyncio
import datetime
import logging
import os.path
from typing import (
Optional,
Union,
)
from aiokit import AioThing
from izihawa_utils.random import random_string
from izihawa_utils.random import generate_request_id
from library.logging import error_log
from telethon import (
TelegramClient,
@ -16,6 +18,7 @@ from telethon import (
from tenacity import ( # noqa
retry,
retry_if_exception_type,
stop_after_attempt,
wait_fixed,
)
@ -28,6 +31,8 @@ class BaseTelegramClient(AioThing):
app_id: Union[int, str],
app_hash: str,
database: dict,
phone: Optional[str] = None,
password: Optional[str] = None,
bot_token: Optional[str] = None,
mtproxy: Optional[dict] = None,
flood_sleep_threshold: int = 60,
@ -44,6 +49,8 @@ class BaseTelegramClient(AioThing):
flood_sleep_threshold=flood_sleep_threshold,
**self._get_proxy(mtproxy=mtproxy),
)
self.phone = phone
self.password = password
self.bot_token = bot_token
def _get_session(self, database):
@ -71,9 +78,23 @@ class BaseTelegramClient(AioThing):
}
return {}
@retry(retry=retry_if_exception_type(ConnectionError), wait=wait_fixed(5))
@retry(retry=retry_if_exception_type(ConnectionError), stop=stop_after_attempt(3), wait=wait_fixed(5))
async def start(self):
await self._telegram_client.start(bot_token=self.bot_token)
logging.getLogger('debug').info({'mode': 'telegram', 'action': 'starting'})
await self._telegram_client.start(
phone=lambda: self.phone,
bot_token=self.bot_token,
password=self.password,
code_callback=self.polling_file,
)
logging.getLogger('debug').info({'mode': 'telegram', 'action': 'started'})
async def polling_file(self):
fname = '/tmp/telegram_code'
while not os.path.exists(fname):
await asyncio.sleep(5.0)
with open(fname, 'r') as code_file:
return code_file.read().strip()
async def stop(self):
return await self.disconnect()
@ -119,6 +140,9 @@ class BaseTelegramClient(AioThing):
def get_input_entity(self, *args, **kwargs):
return self._telegram_client.get_input_entity(*args, **kwargs)
def get_permissions(self, *args, **kwargs):
return self._telegram_client.get_permissions(*args, **kwargs)
def iter_admin_log(self, *args, **kwargs):
return self._telegram_client.iter_admin_log(*args, **kwargs)
@ -158,7 +182,7 @@ class RequestContext:
@staticmethod
def generate_request_id(length):
return random_string(length)
return generate_request_id(length)
def add_default_fields(self, **fields):
self.default_fields.update(fields)

View File

@ -18,8 +18,8 @@ py_library(
requirement("aiocrossref"),
requirement("aiolibgen"),
"//library/aiopostgres",
"//nexus/models/proto:models_proto_py",
requirement("izihawa_types"),
"//nexus/models/proto:proto_py",
"//nexus/nlptools",
requirement("aiosumma"),
],
)

View File

@ -1,27 +0,0 @@
from .update_document import SendDocumentOperationUpdateDocumentPbToSummaAction
from .update_document_scimag import (
CleanDocumentOperationUpdateDocumentScimagPbAction,
FillDocumentOperationUpdateDocumentScimagPbFromExternalSourceAction,
SendDocumentOperationUpdateDocumentScimagPbReferencesToKafkaAction,
SendDocumentOperationUpdateDocumentScimagPbToGoldenPostgresAction,
)
from .update_document_scitech import (
CleanDocumentOperationUpdateDocumentScitechPbAction,
SendDocumentOperationUpdateDocumentScitechPbToGoldenPostgresAction,
)
from .update_document_sharience import (
SendDocumentOperationUpdateDocumentShariencePbToGoldenPostgresAction,
)
from .vote import SendDocumentOperationVotePbToGoldenPostgresAction
__all__ = [
'CleanDocumentOperationUpdateDocumentScimagPbAction',
'CleanDocumentOperationUpdateDocumentScitechPbAction',
'FillDocumentOperationUpdateDocumentScimagPbFromExternalSourceAction',
'SendDocumentOperationUpdateDocumentPbToSummaAction',
'SendDocumentOperationUpdateDocumentScimagPbReferencesToKafkaAction',
'SendDocumentOperationUpdateDocumentScimagPbToGoldenPostgresAction',
'SendDocumentOperationUpdateDocumentScitechPbToGoldenPostgresAction',
'SendDocumentOperationUpdateDocumentShariencePbToGoldenPostgresAction',
'SendDocumentOperationVotePbToGoldenPostgresAction',
]

View File

@ -2,4 +2,12 @@ from urllib.parse import unquote
def canonize_doi(doi):
return unquote(doi.lower())
return (
unquote(doi.lower())
.replace('\\n', '\n')
.replace('\n', '')
.replace('\\', '')
.strip('\'"')
.replace('\x00', '')
.strip()
)

View File

@ -4,6 +4,7 @@ from datetime import date
from nexus.models.proto.scimag_pb2 import Scimag as ScimagPb
from .base import BaseAction
from .exceptions import InterruptProcessing
def extract_authors(authors):
@ -17,7 +18,7 @@ def extract_authors(authors):
def extract_dates(date_parts):
if not date_parts or not date_parts[0]:
return '', None
return 0, None
year, month, day = date_parts[0] + [0] * (3 - len(date_parts[0]))
if year:
issued_at = int(time.mktime(date(
@ -25,8 +26,8 @@ def extract_dates(date_parts):
month=month if month else 1,
day=day if day else 1,
).timetuple()))
return str(year), issued_at
return '', None
return year, issued_at
return 0, None
def extract_first(arr, default=''):
@ -74,13 +75,17 @@ def extract_title(title, subtitle):
return ': '.join(filter(lambda x: bool(x), [title.strip(), subtitle.strip()]))
class CrossrefApiToThinScimagPbAction(BaseAction):
class ToThinScimagPbAction(BaseAction):
async def do(self, item: dict) -> ScimagPb:
if 'DOI' not in item:
raise InterruptProcessing(document_id=None, reason='no_doi')
return ScimagPb(doi=item['DOI'])
class CrossrefApiToScimagPbAction(BaseAction):
class ToScimagPbAction(BaseAction):
async def do(self, item: dict) -> ScimagPb:
if 'DOI' not in item:
raise InterruptProcessing(document_id=None, reason='no_doi')
scimag_pb = ScimagPb(
abstract=item.get('abstract'),
container_title=extract_first(item.get('container-title')),

View File

@ -0,0 +1,41 @@
from library.aiopostgres.pool_holder import AioPostgresPoolHolder
from nexus.models.proto.operation_pb2 import \
DocumentOperation as DocumentOperationPb
from pypika import (
PostgreSQLQuery,
Table,
)
from ..base import BaseAction
class ToPostgresAction(BaseAction):
telegram_files_table = Table('telegram_files')
def __init__(self, database):
super().__init__()
self.pool_holder = AioPostgresPoolHolder(
conninfo=f'dbname={database["database"]} '
f'user={database["username"]} '
f'password={database["password"]} '
f'host={database["host"]}',
)
self.waits.append(self.pool_holder)
async def do(self, document_operation_pb: DocumentOperationPb) -> DocumentOperationPb:
store_telegram_file_id_pb = document_operation_pb.store_telegram_file_id
query = (
PostgreSQLQuery
.into(self.telegram_files_table)
.columns('bot_name', 'document_id', 'telegram_file_id')
.insert(
store_telegram_file_id_pb.bot_name,
store_telegram_file_id_pb.document_id,
store_telegram_file_id_pb.telegram_file_id,
)
.on_conflict('bot_name', 'document_id')
.do_nothing()
)
await self.pool_holder.execute(query.get_sql())
return document_operation_pb

View File

@ -4,7 +4,6 @@ from typing import (
Set,
)
import aiopg
from aiocrossref import CrossrefClient
from aiocrossref.exceptions import (
NotFoundError,
@ -23,13 +22,13 @@ from pypika import (
)
from pypika.terms import Array
from .base import BaseAction
from .crossref_api import CrossrefApiToScimagPbAction
from .exceptions import InterruptProcessing
from .scimag import CleanScimagPbAction
from .. import scimag_pb
from ..base import BaseAction
from ..crossref_api import ToScimagPbAction
from ..exceptions import InterruptProcessing
class SendDocumentOperationUpdateDocumentScimagPbToGoldenPostgresAction(BaseAction):
class ToPostgresAction(BaseAction):
scimag_table = Table('scimag')
db_multi_fields = {
'authors',
@ -55,7 +54,6 @@ class SendDocumentOperationUpdateDocumentScimagPbToGoldenPostgresAction(BaseActi
'md5',
'ref_by_count',
'scimag_bulk_id',
'telegram_file_id',
'title',
'type',
'updated_at',
@ -66,14 +64,10 @@ class SendDocumentOperationUpdateDocumentScimagPbToGoldenPostgresAction(BaseActi
def __init__(self, database):
super().__init__()
self.pool_holder = AioPostgresPoolHolder(
fn=aiopg.create_pool,
dsn=f'dbname={database["database"]} '
conninfo=f'dbname={database["database"]} '
f'user={database["username"]} '
f'password={database["password"]} '
f'host={database["host"]}',
timeout=30,
pool_recycle=60,
maxsize=4,
)
self.waits.append(self.pool_holder)
@ -153,12 +147,12 @@ class SendDocumentOperationUpdateDocumentScimagPbToGoldenPostgresAction(BaseActi
scimag_pb=scimag_pb,
fields=fields,
)
result = await self.pool_holder.execute(sql, fetch=True)
result = [row async for row in self.pool_holder.iterate(sql)]
scimag_pb.id = result[0][0]
return document_operation_pb
class SendDocumentOperationUpdateDocumentScimagPbReferencesToKafkaAction(BaseAction):
class ReferencesToKafkaAction(BaseAction):
def __init__(self, topic, brokers):
super().__init__()
self.topic = topic
@ -194,7 +188,7 @@ class SendDocumentOperationUpdateDocumentScimagPbReferencesToKafkaAction(BaseAct
return document_operation_pb
class FillDocumentOperationUpdateDocumentScimagPbFromExternalSourceAction(BaseAction):
class FillFromExternalSourceAction(BaseAction):
def __init__(self, crossref):
super().__init__()
self.crossref_client = CrossrefClient(
@ -204,8 +198,9 @@ class FillDocumentOperationUpdateDocumentScimagPbFromExternalSourceAction(BaseAc
retry_delay=crossref.get('retry_delay', 0.5),
timeout=crossref.get('timeout'),
user_agent=crossref.get('user_agent'),
ttl_dns_cache=crossref.get('ttl_dns_cache'),
)
self.crossref_api_to_scimag_pb_action = CrossrefApiToScimagPbAction()
self.crossref_api_to_scimag_pb_action = ToScimagPbAction()
self.waits.append(self.crossref_client)
async def do(self, document_operation_pb: DocumentOperationPb) -> DocumentOperationPb:
@ -216,16 +211,16 @@ class FillDocumentOperationUpdateDocumentScimagPbFromExternalSourceAction(BaseAc
try:
crossref_api_response = await self.crossref_client.works(doi=scimag_pb.doi)
except (WrongContentTypeError, NotFoundError) as e:
raise InterruptProcessing(doc_id=scimag_pb.doi, reason=str(e))
raise InterruptProcessing(document_id=scimag_pb.doi, reason=str(e))
new_scimag_pb = await self.crossref_api_to_scimag_pb_action.do(crossref_api_response)
scimag_pb.MergeFrom(new_scimag_pb)
return document_operation_pb
class CleanDocumentOperationUpdateDocumentScimagPbAction(BaseAction):
class CleanAction(BaseAction):
def __init__(self):
super().__init__()
self.cleaner = CleanScimagPbAction()
self.cleaner = scimag_pb.CleanAction()
self.waits.append(self.cleaner)
async def do(self, document_operation_pb: DocumentOperationPb) -> DocumentOperationPb:

View File

@ -1,4 +1,6 @@
import aiopg
import logging
from izihawa_utils.pb_to_json import MessageToDict
from library.aiopostgres.pool_holder import AioPostgresPoolHolder
from nexus.models.proto.operation_pb2 import \
DocumentOperation as DocumentOperationPb
@ -10,17 +12,17 @@ from pypika import (
)
from pypika.terms import Array
from .base import BaseAction
from .exceptions import ConflictError
from .scitech import CleanScitechAction
from .. import scitech_pb
from ..base import BaseAction
from ..exceptions import ConflictError
class UuidFunction(functions.Function):
def __init__(self, uuid, alias=None):
super(UuidFunction, self).__init__('UUID', uuid, alias=alias)
def __init__(self, uuid, name=None):
super(UuidFunction, self).__init__('UUID', uuid, name=name)
class SendDocumentOperationUpdateDocumentScitechPbToGoldenPostgresAction(BaseAction):
class ToPostgresAction(BaseAction):
scitech_table = Table('scitech')
db_single_fields = {
'id',
@ -41,7 +43,6 @@ class SendDocumentOperationUpdateDocumentScitechPbToGoldenPostgresAction(BaseAct
'original_id',
'pages',
'series',
'telegram_file_id',
'title',
'updated_at',
'volume',
@ -57,25 +58,23 @@ class SendDocumentOperationUpdateDocumentScitechPbToGoldenPostgresAction(BaseAct
def __init__(self, database):
super().__init__()
self.pool_holder = AioPostgresPoolHolder(
fn=aiopg.create_pool,
dsn=f'dbname={database["database"]} '
conninfo=f'dbname={database["database"]} '
f'user={database["username"]} '
f'password={database["password"]} '
f'host={database["host"]}',
timeout=30,
pool_recycle=60,
maxsize=4,
)
self.waits.append(self.pool_holder)
def cast_field_value(self, field_name, field_value):
if field_name in self.db_multi_fields:
field_value = Array(*field_value)
if field_name in {'title', }:
field_value = field_value.replace('\0', '').strip()
return field_name, field_value
def is_field_set(self, scitech_pb: ScitechPb, field_name: str):
field_value = getattr(scitech_pb, field_name)
if field_name in {'issued_at'}:
if field_name in {'issued_at', }:
return scitech_pb.HasField(field_name)
return field_value
@ -107,10 +106,7 @@ class SendDocumentOperationUpdateDocumentScitechPbToGoldenPostgresAction(BaseAct
.where(casted_conditions)
.get_sql()
)
result = await self.pool_holder.execute(
sql,
fetch=True
)
result = [row async for row in self.pool_holder.iterate(sql)]
count = result[0][0]
if count > 1:
@ -144,15 +140,19 @@ class SendDocumentOperationUpdateDocumentScitechPbToGoldenPostgresAction(BaseAct
query = query.do_update(col, val)
sql = query.returning('id', 'original_id').get_sql()
result = await self.pool_holder.execute(sql, fetch=True)
try:
result = [row async for row in self.pool_holder.iterate(sql)]
except:
logging.getLogger('error').error({'sql': sql, 'scitech': MessageToDict(scitech_pb)})
raise
scitech_pb.id, scitech_pb.original_id = result[0][0], result[0][1] or 0
return document_operation_pb
class CleanDocumentOperationUpdateDocumentScitechPbAction(BaseAction):
class CleanAction(BaseAction):
def __init__(self):
super().__init__()
self.cleaner = CleanScitechAction()
self.cleaner = scitech_pb.CleanAction()
self.waits.append(self.cleaner)
async def do(self, document_operation_pb: DocumentOperationPb) -> DocumentOperationPb:

View File

@ -3,7 +3,6 @@ from typing import (
Set,
)
import aiopg
from library.aiopostgres.pool_holder import AioPostgresPoolHolder
from nexus.models.proto.operation_pb2 import \
DocumentOperation as DocumentOperationPb
@ -14,10 +13,10 @@ from pypika import (
)
from pypika.terms import Array
from .base import BaseAction
from ..base import BaseAction
class SendDocumentOperationUpdateDocumentShariencePbToGoldenPostgresAction(BaseAction):
class ToPostgresAction(BaseAction):
sharience_table = Table('sharience')
db_multi_fields = {
'ipfs_multihashes',
@ -28,7 +27,6 @@ class SendDocumentOperationUpdateDocumentShariencePbToGoldenPostgresAction(BaseA
'uploader_id',
'filesize',
'md5',
'telegram_file_id',
'updated_at',
}
db_fields = db_single_fields | db_multi_fields
@ -36,14 +34,10 @@ class SendDocumentOperationUpdateDocumentShariencePbToGoldenPostgresAction(BaseA
def __init__(self, database):
super().__init__()
self.pool_holder = AioPostgresPoolHolder(
fn=aiopg.create_pool,
dsn=f'dbname={database["database"]} '
conninfo=f'dbname={database["database"]} '
f'user={database["username"]} '
f'password={database["password"]} '
f'host={database["host"]}',
timeout=30,
pool_recycle=60,
maxsize=4,
)
self.waits.append(self.pool_holder)
@ -80,7 +74,7 @@ class SendDocumentOperationUpdateDocumentShariencePbToGoldenPostgresAction(BaseA
sharience_pb=sharience_pb,
fields=fields,
)
result = await self.pool_holder.execute(sql, fetch=True)
result = [row async for row in self.pool_holder.iterate(sql)]
sharience_pb.id = result[0][0]
return document_operation_pb

View File

@ -1,4 +1,3 @@
import aiopg
from library.aiopostgres.pool_holder import AioPostgresPoolHolder
from nexus.models.proto.operation_pb2 import \
DocumentOperation as DocumentOperationPb
@ -7,23 +6,20 @@ from pypika import (
Table,
)
from .base import BaseAction
from ..base import BaseAction
class SendDocumentOperationVotePbToGoldenPostgresAction(BaseAction):
class ToPostgresAction(BaseAction):
votes_table = Table('votes')
def __init__(self, database):
super().__init__()
self.pool_holder = AioPostgresPoolHolder(
fn=aiopg.create_pool,
dsn=f'dbname={database["database"]} '
conninfo=f'dbname={database["database"]} '
f'user={database["username"]} '
f'password={database["password"]} '
f'host={database["host"]}',
timeout=30,
pool_recycle=60,
maxsize=2,
max_size=2,
)
self.waits.append(self.pool_holder)

View File

@ -6,8 +6,8 @@ from izihawa_utils.exceptions import BaseError
class InterruptProcessing(BaseError):
code = 'interrupt_processing'
def __init__(self, doc_id, reason):
super().__init__(doc_id=doc_id, reason=reason)
def __init__(self, document_id, reason):
super().__init__(document_id=document_id, reason=reason)
class ConflictError(BaseError):

View File

@ -1,8 +0,0 @@
from nexus.models.proto.scimag_pb2 import Scimag as ScimagPb
from .base import BaseAction
class GoldenPostgresToThinScimagPbAction(BaseAction):
async def do(self, item: dict) -> ScimagPb:
return ScimagPb(doi=item['doi'])

View File

@ -84,7 +84,7 @@ def create_cu(libgen_id, coverurl, md5):
return coverurl, cu_suf
class LibgenApiToScitechPbAction(BaseAction):
class ToScitechPbAction(BaseAction):
async def do(self, item: dict) -> ScitechPb:
scitech_pb = ScitechPb(
authors=(item.get('author') or '').split('; '),
@ -113,7 +113,7 @@ class LibgenApiToScitechPbAction(BaseAction):
item['tags'].split(';')
),
)),
title=item['title'],
title=item['title'].replace('\0', '').strip(),
)
scitech_pb.cu, scitech_pb.cu_suf = create_cu(
@ -123,6 +123,7 @@ class LibgenApiToScitechPbAction(BaseAction):
)
year = safe_int(item['year'])
if year and year < 9999:
scitech_pb.year = str(year)
scitech_pb.issued_at = np.datetime64(scitech_pb.year).astype('<M8[s]').astype(np.int64)
scitech_pb.year = year
# Subtract 1970
scitech_pb.issued_at = np.datetime64(year, 'Y').astype('datetime64[s]').astype(np.int64) - 62167132800
return scitech_pb

34
nexus/actions/postgres.py Normal file
View File

@ -0,0 +1,34 @@
import orjson as json
from izihawa_utils.common import filter_none
from nexus.models.proto.scimag_pb2 import Scimag as ScimagPb
from summa.proto.proto_grpc_py_pb import index_pb2 as index_pb
from .base import BaseAction
class ToThinScimagPbAction(BaseAction):
async def do(self, item: dict) -> ScimagPb:
return ScimagPb(doi=item['doi'])
class ScitechToIndexOperationBytesAction(BaseAction):
restricted_column_set = [
'extension',
'fiction_id',
'filesize',
'has_duplicates',
'ipfs_multihashes',
'libgen_id',
'md5',
'original_id',
]
async def do(self, item: dict) -> bytes:
# if item['original_id'] is not None:
# item = {rc: item[rc] for rc in self.restricted_column_set}
return index_pb.IndexOperation(
index_document=index_pb.IndexDocumentOperation(
document=json.dumps(filter_none(item)),
reindex=True,
),
).SerializeToString()

View File

@ -1,7 +1,6 @@
from html import unescape
from bs4 import BeautifulSoup
from nexus.actions.common import canonize_doi
from nexus.models.proto.operation_pb2 import \
DocumentOperation as DocumentOperationPb
from nexus.models.proto.operation_pb2 import UpdateDocument as UpdateDocumentPb
@ -15,9 +14,10 @@ from nexus.nlptools.utils import (
)
from .base import BaseAction
from .common import canonize_doi
class CleanScimagPbAction(BaseAction):
class CleanAction(BaseAction):
async def do(self, scimag_pb: ScimagPb) -> ScimagPb:
if scimag_pb.abstract:
abstract_soup = BeautifulSoup(unescape(scimag_pb.abstract), 'lxml')
@ -61,7 +61,7 @@ class CleanScimagPbAction(BaseAction):
return scimag_pb
class ScimagPbToDocumentOperationBytesAction(BaseAction):
class ToDocumentOperationAction(BaseAction):
async def do(self, item: ScimagPb) -> bytes:
document_operation_pb = DocumentOperationPb(
update_document=UpdateDocumentPb(

View File

@ -1,7 +1,6 @@
from html import unescape
from bs4 import BeautifulSoup
from nexus.actions.common import canonize_doi
from nexus.models.proto.operation_pb2 import \
DocumentOperation as DocumentOperationPb
from nexus.models.proto.operation_pb2 import UpdateDocument as UpdateDocumentPb
@ -15,9 +14,10 @@ from nexus.nlptools.utils import (
)
from .base import BaseAction
from .common import canonize_doi
class CleanScitechAction(BaseAction):
class CleanAction(BaseAction):
async def do(self, scitech_pb: ScitechPb) -> ScitechPb:
if scitech_pb.authors:
for i, author in enumerate(scitech_pb.authors):
@ -47,7 +47,7 @@ class CleanScitechAction(BaseAction):
return scitech_pb
class ScitechPbToDocumentOperationBytesAction(BaseAction):
class ToDocumentOperationPbAction(BaseAction):
async def do(self, item: ScitechPb) -> bytes:
document_operation_pb = DocumentOperationPb(
update_document=UpdateDocumentPb(

View File

@ -1,31 +0,0 @@
from aiosumma import SummaHttpClient
from izihawa_utils.pb_to_json import MessageToDict
from nexus.models.proto.operation_pb2 import \
DocumentOperation as DocumentOperationPb
from .base import BaseAction
class SendDocumentOperationUpdateDocumentPbToSummaAction(BaseAction):
def __init__(self, summa):
super().__init__()
self.summa_client = SummaHttpClient(**summa)
self.waits.append(self.summa_client)
async def do(self, document_operation_pb: DocumentOperationPb) -> DocumentOperationPb:
update_document_pb = document_operation_pb.update_document
schema = update_document_pb.typed_document.WhichOneof('document')
document = getattr(update_document_pb.typed_document, schema)
original_id = getattr(document, 'original_id', None)
if not update_document_pb.reindex or original_id:
return document_operation_pb
casted_document = MessageToDict(document, preserving_proto_field_name=True)
# ToDo: Required to rework checking for extra fields in document
# ToDo: It is needed to go to actual schema and load real fields and then check against them
casted_document.pop('is_deleted', None)
casted_document.pop('meta_language', None)
casted_document.pop('type', None)
await self.summa_client.put_document(schema, casted_document)
if update_document_pb.commit:
await self.summa_client.commit(schema)
return document_operation_pb

View File

@ -25,7 +25,6 @@ py3_image(
requirement("aiodns"),
requirement("aiohttp"),
requirement("aiohttp_socks"),
requirement("psycopg2-binary"),
requirement("pytimeparse"),
requirement("python_socks"),
requirement("tenacity"),
@ -39,7 +38,7 @@ py3_image(
"//library/telegram",
"//nexus/hub/aioclient",
"//nexus/meta_api/aioclient",
"//nexus/models/proto:models_proto_py",
"//nexus/models/proto:proto_py",
"//nexus/nlptools",
"//nexus/views/telegram",
requirement("izihawa_utils"),
@ -50,7 +49,7 @@ container_push(
name = "push-testing",
format = "Docker",
image = ":image",
registry = "registry.example.com",
registry = "registry.infra.svc.cluster.local",
repository = "nexus-bot",
tag = "testing",
)
@ -59,7 +58,7 @@ container_push(
name = "push-latest",
format = "Docker",
image = ":image",
registry = "registry.example.com",
registry = "registry.infra.svc.cluster.local",
repository = "nexus-bot",
tag = "latest",
)

View File

@ -20,13 +20,13 @@ class TelegramApplication(AioRootThing):
mtproxy=self.config['telegram'].get('mtproxy'),
)
self.hub_client = HubGrpcClient(base_url=self.config['hub']['url'])
self.hub_client = HubGrpcClient(endpoint=self.config['hub']['endpoint'])
self.starts.append(self.hub_client)
self.idm_client = None
if self.config['idm']['enabled']:
self.idm_client = IdmApiGrpcClient(base_url=self.config['idm']['url'])
self.idm_client = IdmApiGrpcClient(endpoint=self.config['idm']['endpoint'])
self.starts.append(self.idm_client)
self.meta_api_client = MetaApiGrpcClient(base_url=self.config['meta_api']['url'])
self.meta_api_client = MetaApiGrpcClient(endpoint=self.config['meta_api']['endpoint'])
self.starts.append(self.meta_api_client)
self.promotioner = Promotioner(promotions=self.config['promotions'])

View File

@ -12,13 +12,16 @@ application:
bypass_maintenance: []
# Debugging mode
debug: true
# Enabled indices (passed to Nexus Meta API)
index_aliases:
- scitech
# 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
is_subscription_required: true
# Libera Pay URL in /donate message
libera_pay_url:
maintenance_picture_url:
@ -27,9 +30,6 @@ application:
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:
- scitech
# Length of generated Session-ID used in commands to clue user sessions
session_id_length: 8
too_difficult_picture_url:
@ -41,13 +41,13 @@ application:
has_language_buttons: true
has_system_messaging_button: true
hub:
url:
endpoint:
idm:
enabled: false
url:
enabled: true
endpoint:
log_path: '/var/log/nexus-bot'
meta_api:
url:
endpoint:
metrics:
enabled: false
telegram:
@ -56,9 +56,7 @@ telegram:
# 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_name: libgen_scihub_1_bot
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

View File

@ -12,6 +12,7 @@ logging:
handlers:
debug:
class: library.logging.handlers.BaseFileHandler
formatter: default
filename: '{{ log_path }}/debug.log'
level: DEBUG
error:

View File

@ -21,5 +21,5 @@ class UnknownFileFormatError(BaseError):
code = 'unknown_file_format_error'
class UnknownSchemaError(BaseError):
code = 'unknown_schema_error'
class UnknownIndexAliasError(BaseError):
code = 'unknown_index_alias_error'

View File

@ -13,7 +13,7 @@ from library.logging import error_log
from library.telegram.base import RequestContext
from library.telegram.utils import safe_execution
from nexus.bot.application import TelegramApplication
from nexus.bot.exceptions import UnknownSchemaError
from nexus.bot.exceptions import UnknownIndexAliasError
from nexus.models.proto.typed_document_pb2 import \
TypedDocument as TypedDocumentPb
from nexus.translations import t
@ -24,7 +24,10 @@ from telethon import (
TelegramClient,
events,
)
from telethon.errors import QueryIdInvalidError
from telethon.errors import (
QueryIdInvalidError,
UserNotParticipantError,
)
def get_username(event: events.ChatAction, chat):
@ -44,10 +47,6 @@ def is_banned(chat: ChatPb) -> bool:
return chat.ban_until is not None and datetime.utcnow().timestamp() < chat.ban_until
def is_subscribed(chat: ChatPb) -> bool:
return chat.is_subscribed or chat.chat_id < 0 or chat.created_at > time.time() - 10 * 60
class ReadOnlyModeError(BaseError):
level = logging.WARNING
code = 'read_only_mode_error'
@ -68,11 +67,11 @@ class BaseHandler(ABC):
def __init__(self, application: TelegramApplication):
self.application = application
self.schema_to_resolver = {
self.index_alias_to_resolver = {
'scimag': self.resolve_scimag,
'scitech': self.resolve_scitech,
}
self.short_schema_to_schema_dict = {
self.short_index_alias_to_index_alias_dict = {
'a': 'scimag',
'b': 'scitech',
}
@ -80,19 +79,19 @@ class BaseHandler(ABC):
def generate_session_id(self) -> str:
return random_string(self.application.config['application']['session_id_length'])
def short_schema_to_schema(self, short_schema: str) -> str:
return self.short_schema_to_schema_dict[short_schema]
def short_index_alias_to_index_alias(self, short_index_alias: str) -> str:
return self.short_index_alias_to_index_alias_dict[short_index_alias]
async def get_typed_document_pb(
self,
schema: str,
index_alias: str,
document_id: int,
request_context: RequestContext,
session_id: str,
position: int,
) -> TypedDocumentPb:
return await self.application.meta_api_client.get(
schema=schema,
index_alias=index_alias,
document_id=document_id,
session_id=session_id,
position=position,
@ -108,7 +107,7 @@ class BaseHandler(ABC):
session_id: str,
) -> ScimagView:
typed_document_pb = await self.get_typed_document_pb(
schema='scimag',
index_alias='scimag',
document_id=document_id,
position=position,
request_context=request_context,
@ -124,14 +123,14 @@ class BaseHandler(ABC):
session_id: str,
) -> ScitechView:
typed_document_pb = await self.get_typed_document_pb(
schema='scitech',
index_alias='scitech',
document_id=document_id,
position=position,
request_context=request_context,
session_id=session_id,
)
search_response_duplicates = await self.application.meta_api_client.search(
schemas=('scitech',),
index_aliases=('scitech',),
query=f'original_id:{document_id}',
page_size=16,
request_id=request_context.request_id,
@ -149,16 +148,16 @@ class BaseHandler(ABC):
async def resolve_document(
self,
schema: str,
index_alias: str,
document_id: int,
position: int,
session_id: str,
request_context: RequestContext
) -> Union[ScimagView, ScitechView]:
if schema not in self.schema_to_resolver:
raise UnknownSchemaError()
if index_alias not in self.index_alias_to_resolver:
raise UnknownIndexAliasError(index_alias=index_alias)
resolver = self.schema_to_resolver[schema]
resolver = self.index_alias_to_resolver[index_alias]
return await resolver(
document_id=document_id,
position=position,
@ -251,11 +250,23 @@ class BaseHandler(ABC):
)
raise events.StopPropagation()
async def is_subscribed(self, chat: ChatPb) -> bool:
if chat.chat_id < 0 or chat.created_at > time.time() - 10 * 60:
return True
try:
await self.application.telegram_client.get_permissions(
self.application.config['telegram']['related_channel'],
chat.chat_id,
)
except UserNotParticipantError:
return False
return True
async def _check_subscription(self, event: events.ChatAction, request_context: RequestContext, chat: ChatPb):
if (
self.application.config['application']['is_subscription_required']
and self.is_subscription_required_for_handler
and not is_subscribed(chat)
and not await self.is_subscribed(chat)
):
async with safe_execution(
request_context=request_context,
@ -284,7 +295,6 @@ class BaseHandler(ABC):
language='en',
username=username,
is_admin=False,
is_subscribed=True,
)
return chat

View File

@ -13,24 +13,24 @@ class DownloadHandler(BaseCallbackQueryHandler):
is_group_handler = True
def parse_pattern(self, event: events.ChatAction):
short_schema = event.pattern_match.group(1).decode()
schema = self.short_schema_to_schema(short_schema)
short_index_alias = event.pattern_match.group(1).decode()
index_alias = self.short_index_alias_to_index_alias(short_index_alias)
session_id = event.pattern_match.group(2).decode()
document_id = int(event.pattern_match.group(3))
position = int(event.pattern_match.group(4).decode())
return short_schema, schema, session_id, document_id, position
return short_index_alias, index_alias, session_id, document_id, position
async def handler(self, event: events.ChatAction, request_context: RequestContext):
short_schema, schema, session_id, document_id, position = self.parse_pattern(event)
short_index_alias, index_alias, session_id, document_id, position = self.parse_pattern(event)
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.statbox(action='get', document_id=document_id, position=position, schema=schema)
request_context.statbox(action='get', document_id=document_id, position=position, index_alias=index_alias)
typed_document_pb = await self.get_typed_document_pb(
schema=schema,
index_alias=index_alias,
document_id=document_id,
request_context=request_context,
session_id=session_id,
@ -41,6 +41,7 @@ class DownloadHandler(BaseCallbackQueryHandler):
chat=request_context.chat,
request_id=request_context.request_id,
session_id=session_id,
bot_name=request_context.bot_name,
)
if start_delivery_response_pb.status == StartDeliveryResponsePb.Status.ALREADY_DOWNLOADING:
await event.answer(

View File

@ -31,7 +31,7 @@ class RollHandler(BaseHandler):
view, buttons = scitech_view.get_view(
language=request_context.chat.language,
session_id=session_id,
bot_external_name=self.application.config['telegram']['bot_external_name'],
bot_name=self.application.config['telegram']['bot_name'],
)
actions = [
self.application.telegram_client.send_message(

View File

@ -27,7 +27,7 @@ from .base import (
class BaseSearchHandler(BaseHandler, ABC):
def preprocess_query(self, query):
return query.replace(f'@{self.application.config["telegram"]["bot_external_name"]}', '').strip()
return query.replace(f'@{self.application.config["telegram"]["bot_name"]}', '').strip()
async def do_search(
self,
@ -109,7 +109,7 @@ class BaseSearchHandler(BaseHandler, ABC):
document_view = parse_typed_document_to_view(scored_document.typed_document)
# Second (re-)fetching is required to retrieve duplicates
document_view = await self.resolve_document(
schema=scored_document.typed_document.WhichOneof('document'),
index_alias=scored_document.typed_document.WhichOneof('document'),
document_id=document_view.id,
position=0,
session_id=session_id,
@ -118,7 +118,7 @@ class BaseSearchHandler(BaseHandler, ABC):
view, buttons = document_view.get_view(
language=request_context.chat.language,
session_id=session_id,
bot_external_name=self.application.config['telegram']['bot_external_name'],
bot_name=self.application.config['telegram']['bot_name'],
with_buttons=not is_group_mode,
)
return await asyncio.gather(
@ -254,7 +254,7 @@ class SearchPagingHandler(BaseCallbackQueryHandler):
should_reset_last_widget = False
def preprocess_query(self, query):
return query.replace(f'@{self.application.config["telegram"]["bot_external_name"]}', '').strip()
return query.replace(f'@{self.application.config["telegram"]["bot_name"]}', '').strip()
def parse_pattern(self, event: events.ChatAction):
session_id = event.pattern_match.group(1).decode()

View File

@ -17,7 +17,7 @@ class ShortlinkHandler(BaseHandler):
request_context.statbox(action='start', mode='shortlink', query=query)
try:
bot_name = self.application.config["telegram"]["bot_external_name"]
bot_name = self.application.config["telegram"]["bot_name"]
text = encode_query_to_deep_link(query, bot_name)
except TooLongQueryError:
text = t('TOO_LONG_QUERY_FOR_SHORTLINK', language=request_context.chat.language),

View File

@ -38,6 +38,7 @@ class SubmitHandler(BaseHandler):
chat=request_context.chat,
request_id=request_context.request_id,
session_id=session_id,
bot_name=request_context.bot_name,
),
event.delete(),
)

View File

@ -18,8 +18,8 @@ class ViewHandler(BaseHandler):
should_reset_last_widget = False
def parse_pattern(self, event: events.ChatAction):
short_schema = event.pattern_match.group(1)
schema = self.short_schema_to_schema(short_schema)
short_index_alias = event.pattern_match.group(1)
index_alias = self.short_index_alias_to_index_alias(short_index_alias)
session_id = event.pattern_match.group(3)
old_message_id = int(event.pattern_match.group(4))
document_id = int(event.pattern_match.group(5))
@ -27,7 +27,7 @@ class ViewHandler(BaseHandler):
page = int(position / self.application.config['application']['page_size'])
return schema, session_id, old_message_id, document_id, position, page
return index_alias, session_id, old_message_id, document_id, position, page
async def process_widgeting(self, has_found_old_widget, old_message_id, request_context: RequestContext):
if has_found_old_widget:
@ -56,10 +56,10 @@ class ViewHandler(BaseHandler):
return f'/search_{session_id}_{message_id}_{page}'
async def handler(self, event: events.ChatAction, request_context: RequestContext):
schema, session_id, old_message_id, document_id, position, page = self.parse_pattern(event)
index_alias, session_id, old_message_id, document_id, position, page = self.parse_pattern(event)
request_context.add_default_fields(mode='view', session_id=session_id)
request_context.statbox(action='view', document_id=document_id, position=position, schema=schema)
request_context.statbox(action='view', document_id=document_id, position=position, index_alias=index_alias)
has_found_old_widget = old_message_id == self.application.user_manager.last_widget.get(request_context.chat.chat_id)
@ -71,7 +71,7 @@ class ViewHandler(BaseHandler):
)
document_view = await self.resolve_document(
schema,
index_alias,
document_id,
position,
session_id,
@ -91,7 +91,7 @@ class ViewHandler(BaseHandler):
view, buttons = document_view.get_view(
language=request_context.chat.language,
session_id=session_id,
bot_external_name=self.application.config['telegram']['bot_external_name'],
bot_name=self.application.config['telegram']['bot_name'],
position=position,
back_command=back_command,
)

View File

@ -16,24 +16,24 @@ class VoteHandler(BaseCallbackQueryHandler):
filter = events.CallbackQuery(pattern='^/vote([ab])?_([A-Za-z0-9]+)_([0-9]+)_([bo])$')
def parse_pattern(self, event: events.ChatAction):
short_schema = event.pattern_match.group(1)
schema = self.short_schema_to_schema(short_schema.decode()) if short_schema else None
short_index_alias = event.pattern_match.group(1)
index_alias = self.short_index_alias_to_index_alias(short_index_alias.decode()) if short_index_alias else None
session_id = event.pattern_match.group(2).decode()
document_id = int(event.pattern_match.group(3).decode())
vote = event.pattern_match.group(4).decode()
vote_value = {'b': -1, 'o': 1}[vote]
return schema, session_id, document_id, vote, vote_value
return index_alias, session_id, document_id, vote, vote_value
async def handler(self, event: events.ChatAction, request_context: RequestContext):
schema, session_id, document_id, vote, vote_value = self.parse_pattern(event)
index_alias, session_id, document_id, vote, vote_value = self.parse_pattern(event)
request_context.add_default_fields(mode='vote', session_id=session_id)
request_context.statbox(
action='vote',
document_id=document_id,
query=vote,
schema=schema,
index_alias=index_alias,
)
document_operation_pb = DocumentOperationPb(
@ -44,7 +44,7 @@ class VoteHandler(BaseCallbackQueryHandler):
),
)
logging.getLogger('operation').info(
msg=MessageToDict(document_operation_pb),
msg=MessageToDict(document_operation_pb, preserving_proto_field_name=True),
)
message = await event.get_message()

View File

@ -64,7 +64,7 @@ class SearchWidget:
async def _acquire_documents(self):
self._search_response = await self.application.meta_api_client.search(
schemas=self.application.config['application']['schemas'],
index_aliases=self.application.config['application']['index_aliases'],
query=self.query,
page=self.page,
page_size=self.application.config['application']['page_size'],
@ -87,7 +87,7 @@ class SearchWidget:
return t('COULD_NOT_FIND_ANYTHING', language=self.chat.language), [close_button(self.session_id)]
serp_elements = []
bot_external_name = self.application.config['telegram']['bot_external_name']
bot_name = self.application.config['telegram']['bot_name']
for scored_document in self.scored_documents:
view = parse_typed_document_to_view(scored_document.typed_document)
@ -98,7 +98,7 @@ class SearchWidget:
position=scored_document.position,
)
else:
view_command = view.get_deep_link(bot_external_name, text='⬇️')
view_command = view.get_deep_link(bot_name, text='⬇️')
serp_elements.append(
view.get_snippet(
language=self.chat.language,
@ -112,18 +112,18 @@ class SearchWidget:
try:
encoded_query = encode_query_to_deep_link(
self.query,
bot_external_name,
bot_name,
)
serp = (
f"{serp}\n\n**{t('DOWNLOAD_AND_SEARCH_MORE', language=self.chat.language)}: **"
f'[@{bot_external_name}]'
f'[@{bot_name}]'
f'({encoded_query})'
)
except TooLongQueryError:
serp = (
f"{serp}\n\n**{t('DOWNLOAD_AND_SEARCH_MORE', language=self.chat.language)}: **"
f'[@{bot_external_name}]'
f'(https://t.me/{bot_external_name})'
f'[@{bot_name}]'
f'(https://t.me/{bot_name})'
)
if not self.is_group_mode:

View File

@ -1,21 +1,21 @@
---
# yamllint disable rule:key-ordering
default_fields: ["abstract", "authors", "language", "title", "tags", "year"]
enabled: true
default_fields: ["abstract", "authors", "language", "title", "tags"]
key_field: "id"
multi_fields: ["authors", "ipfs_multihashes", "issns", "references", "tags"]
name: scimag
schema:
- name: id
type: i64
options:
fast: single
fieldnorms: false
indexed: true
stored: true
- name: abstract
type: text
options:
indexing:
fieldnorms: true
record: position
tokenizer: summa
stored: true
@ -23,6 +23,7 @@ schema:
type: text
options:
indexing:
fieldnorms: true
record: position
tokenizer: summa
stored: true
@ -30,18 +31,21 @@ schema:
type: text
options:
indexing:
fieldnorms: true
record: basic
tokenizer: raw
stored: true
- name: first_page
type: i64
options:
fieldnorms: false
indexed: false
stored: true
- name: container_title
type: text
options:
indexing:
fieldnorms: true
record: position
tokenizer: summa
stored: true
@ -58,41 +62,48 @@ schema:
- name: issued_at
type: i64
options:
fieldnorms: false
indexed: true
stored: true
- name: language
type: text
options:
indexing:
fieldnorms: true
record: basic
tokenizer: raw
stored: true
- name: last_page
type: i64
options:
fieldnorms: false
indexed: false
stored: true
- name: ref_by_count
type: i64
options:
fieldnorms: false
indexed: false
stored: true
- name: references
type: text
options:
indexing:
fieldnorms: true
record: basic
tokenizer: raw
stored: false
- name: scimag_bulk_id
type: i64
options:
fieldnorms: false
indexed: false
stored: true
- name: tags
type: text
options:
indexing:
fieldnorms: true
record: position
tokenizer: summa
stored: true
@ -100,12 +111,14 @@ schema:
type: text
options:
indexing:
fieldnorms: true
record: position
tokenizer: summa
stored: true
- name: updated_at
type: i64
options:
fieldnorms: false
indexed: true
stored: true
- name: volume
@ -114,9 +127,8 @@ schema:
indexing: null
stored: true
- name: year
type: text
type: i64
options:
indexing:
record: basic
tokenizer: raw
stored: false
fieldnorms: true
indexed: true
stored: true

View File

@ -1,21 +1,21 @@
---
# yamllint disable rule:key-ordering
default_fields: ["authors", "description", "doi", "tags", "title", "year"]
enabled: true
default_fields: ["authors", "description", "tags", "title"]
key_field: "id"
multi_fields: ["authors", "ipfs_multihashes", "isbns", "tags"]
name: scitech
schema:
- name: id
type: i64
options:
fast: single
fieldnorms: false
indexed: true
stored: true
- name: authors
type: text
options:
indexing:
fieldnorms: true
record: position
tokenizer: summa
stored: true
@ -33,6 +33,7 @@ schema:
type: text
options:
indexing:
fieldnorms: true
record: position
tokenizer: summa
stored: true
@ -40,6 +41,7 @@ schema:
type: text
options:
indexing:
fieldnorms: true
record: basic
tokenizer: raw
stored: true
@ -47,28 +49,33 @@ schema:
type: text
options:
indexing:
fieldnorms: true
record: basic
tokenizer: raw
stored: true
- name: fiction_id
type: i64
options:
fieldnorms: false
indexed: false
stored: true
- name: filesize
type: i64
options:
fieldnorms: false
indexed: false
stored: true
- name: ipfs_multihashes
type: text
options:
fieldnorms: false
indexed: false
stored: true
- name: isbns
type: text
options:
indexing:
fieldnorms: true
record: basic
tokenizer: raw
stored: true
@ -76,28 +83,33 @@ schema:
type: i64
options:
fast: single
fieldnorms: false
indexed: true
stored: true
- name: has_duplicates
type: i64
options:
fieldnorms: false
indexed: false
stored: true
- name: language
type: text
options:
indexing:
fieldnorms: true
record: basic
tokenizer: raw
stored: true
- name: libgen_id
type: i64
options:
fieldnorms: false
indexed: false
stored: true
- name: original_id
type: i64
options:
fieldnorms: false
indexed: true
stored: true
- name: pages
@ -119,6 +131,7 @@ schema:
type: text
options:
indexing:
fieldnorms: true
record: position
tokenizer: summa
stored: true
@ -126,6 +139,7 @@ schema:
type: text
options:
indexing:
fieldnorms: true
record: position
tokenizer: summa
stored: true
@ -133,6 +147,7 @@ schema:
type: text
options:
indexing:
fieldnorms: true
record: position
tokenizer: summa
stored: true
@ -140,6 +155,7 @@ schema:
type: i64
options:
fast: single
fieldnorms: false
indexed: true
stored: true
- name: volume
@ -148,9 +164,8 @@ schema:
indexing: null
stored: true
- name: year
type: text
type: i64
options:
indexing:
record: basic
tokenizer: raw
stored: false
fieldnorms: true
indexed: true
stored: true

View File

@ -1,16 +0,0 @@
---
http:
address: 0.0.0.0
keep_alive_secs: 75
max_body_size_mb: 32
port: 80
workers: 48
log_path: /var/log/nexus-cognitron
search_engine:
auto_commit: false
data_path: /nexus-cognitron/summa
default_page_size: 5
timeout_secs: 15
writer_memory_mb: 1024
writer_threads: 4

View File

@ -1,20 +0,0 @@
load("@pip_modules//:requirements.bzl", "requirement")
load("@rules_python//python:defs.bzl", "py_binary")
py_binary(
name = "installer",
srcs = glob([
"**/*.py",
]),
imports = ["."],
main = "main.py",
srcs_version = "PY3",
visibility = ["//visibility:public"],
deps = [
requirement("aioipfs"),
requirement("fire"),
requirement("pyyaml"),
requirement("tantipy"),
requirement("izihawa_utils"),
],
)

View File

@ -1,11 +0,0 @@
import time
import fire
from nexus.cognitron.installer.scripts.iterate import iterate
if __name__ == '__main__':
start = time.time()
fire.Fire({
'iterate': iterate,
})
print(f'Elapsed {time.time() - start:.2f} secs')

View File

@ -1,9 +0,0 @@
import os
def resolve_path(filepath):
if os.path.isabs(filepath):
return filepath
cwd = os.environ.get('BUILD_WORKING_DIRECTORY', os.getcwd())
filepath = os.path.join(cwd, filepath)
return filepath

View File

@ -1,46 +0,0 @@
import glob
import multiprocessing
import os
from functools import partial
import yaml
from izihawa_utils.itertools import ichunks
from tantipy import (
TantivyCoder,
TantivyReader,
)
from .common import resolve_path
def work(document):
# ToDo: Replace this function to what you want to do with document
print(document)
def _do_work(coder, chunk_size, limit, store_filepath):
with open(store_filepath, 'rb') as file:
data = file.read()
print(f'Processing segment {store_filepath}, size: {len(data) / (1024 * 1024):.2f} Mb ...')
tr = TantivyReader(data, coder=coder)
for chunk_num, documents in enumerate(ichunks(tr.documents(), chunk_size)):
for doc_num, document in enumerate(documents):
if limit and chunk_num * chunk_size + doc_num > limit:
print(f'Segment {store_filepath} early terminated due to limits')
return
work(document)
print(f'Segment {store_filepath} successfully processed')
def iterate(data_filepath, schema_filepath, processes=8, chunk_size=100, limit=1):
data_filepath = resolve_path(data_filepath)
schema_filepath = resolve_path(schema_filepath)
with open(schema_filepath) as schema_file:
coder = TantivyCoder(yaml.safe_load(schema_file.read()))
store_filepaths = glob.glob(os.path.join(data_filepath, '*.store'))
print(f'Total segments: {len(store_filepaths)}')
pool = multiprocessing.Pool(processes)
pool.map(partial(_do_work, coder, chunk_size, limit), store_filepaths)

View File

@ -1,51 +1,43 @@
load("@io_bazel_rules_docker//container:container.bzl", "container_push")
load("@io_bazel_rules_docker//nodejs:image.bzl", "nodejs_image")
load("@npm//nuxt:index.bzl", "nuxt")
load("@npm//nuxt3:index.bzl", "nuxi")
files = ["nuxt.config.js"] + glob([
"assets/**",
files = [
"app.vue",
"nuxt.config.ts",
] + glob([
"components/**/*.vue",
"layouts/*.vue",
"middleware/*.js",
"pages/**/*.vue",
"plugins/*.js",
"static/*",
"store/*.js",
"layouts/**/*.vue",
"plugins/**/*.js",
])
deps = [
"@npm//@nuxtjs/axios",
"@npm//bootstrap",
"@npm//bootstrap-vue",
"@npm//core-js",
"@npm//dateformat",
"@npm//electron",
"@npm//axios",
"@npm//bootstrap-vue-3",
"@npm//pug",
"@npm//pug-plain-loader",
"@npm//sass",
"@npm//sass-loader",
"@npm//vue",
"@npm//@types/node",
"//nexus/meta_api/js/client",
"//nexus/views/js",
]
nuxt(
nuxi(
name = "web-dev",
args = [
"-c",
"nexus/cognitron/web/nuxt.config.js",
"--watch-poll",
"dev",
"nexus/cognitron/web",
],
data = files + deps,
)
nuxt(
name = ".nuxt",
nuxi(
name = ".output",
args = [
"build",
"--standalone",
"-c",
"nexus/cognitron/web/nuxt.config.js",
"nexus/cognitron/web",
"--buildDir=$(@D)",
],
data = files + deps,
@ -55,19 +47,8 @@ nuxt(
nodejs_image(
name = "image",
base = "//images/production:base-nodejs-image",
data = glob(["static/*"]) + [
"nuxt.config.js",
":.nuxt",
"@npm//@nuxtjs/axios",
"@npm//bootstrap-vue",
"@npm//nuxt",
],
entry_point = "@npm//:node_modules/nuxt/bin/nuxt.js",
templated_args = [
"start",
"-c",
"nexus/cognitron/web/nuxt.config.js",
],
data = [":.output"],
entry_point = ".output/server/index.mjs",
)

View File

@ -1,6 +1,29 @@
# Nexus Web
# Nuxt 3 Minimal Starter
#### Development
```shell script
bazel run web-dev
```
We recommend to look at the [documentation](https://v3.nuxtjs.org).
## Setup
Make sure to install the dependencies
```bash
yarn install
```
## Development
Start the development server on http://localhost:3000
```bash
yarn dev
```
## Production
Build the application for production:
```bash
yarn build
```
Checkout the [deployment documentation](https://v3.nuxtjs.org/docs/deployment).

View File

@ -0,0 +1,4 @@
<template lang="pug">
div
NuxtWelcome
</template>

View File

@ -1,41 +0,0 @@
.btn {
border-radius: 0 !important; // sass-lint:disable-line no-important
padding: 0 2em !important; // sass-lint:disable-line no-important
}
.pagination {
li {
padding-left: 0 !important; // sass-lint:disable-line no-important
&::after {
content: none;
}
}
}
$primary: #1a95e0;
@import 'bootstrap/scss/bootstrap';
body {
font-size: 13px !important; // sass-lint:disable-line no-important
line-height: 1.3em !important; // sass-lint:disable-line no-important
}
html {
overflow-y: scroll;
}
a {
&:hover {
text-decoration: none;
}
}
.radio-group {
label {
span {
line-height: 2em;
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
<template lang="pug">
div.document
v-scimag(v-if="document.schema === 'scimag'" :document="document")
v-scitech(v-if="document.schema === 'scitech'" :document="document")
div.document
v-scimag(v-if="document.schema === 'scimag'" :document="document")
v-scitech(v-if="document.schema === 'scitech'" :document="document")
</template>
<script>

View File

@ -1,8 +1,8 @@
<template lang="pug">
ul
li(v-for='document in documents')
v-scimag-search-item(v-if="document.schema == 'scimag'", :document='document', :key='document.id')
v-scitech-search-item(v-if="document.schema == 'scitech'", :document='document', :key='document.id')
ul
li(v-for='document in documents')
v-scimag-search-item(v-if="document.index_alias == 'scimag'", :document='document', :key='document.id')
v-scitech-search-item(v-if="document.index_alias == 'scitech'", :document='document', :key='document.id')
</template>
<script>

View File

@ -1,10 +1,10 @@
<template lang="pug">
nav.navbar.fixed-bottom.ml-auto
ul.navbar-nav.ml-auto
li.nav-item
| Powered by&nbsp;
a(href="https://github.com/nexus-stc/hyperboria") Nexus STC
| , 2025
nav.navbar.fixed-bottom.ml-auto
ul.navbar-nav.ml-auto
li.nav-item
| Powered by&nbsp;
a(href="https://github.com/nexus-stc/hyperboria") Nexus STC
| , 2025
</template>
<script>
@ -17,7 +17,3 @@ export default {
}
}
</script>
<style scoped lang="scss">
</style>

View File

@ -1,10 +1,10 @@
<template lang="pug">
nav.navbar.navbar-light.bg-light
b-container
nuxt-link(to="/" title="Go to search!").logo
| > Nexus Cognitron
a.nav-link(href="https://t.me/nexus_search" title="News")
| News
nav.navbar.navbar-light.bg-light
b-container
nuxt-link(to="/" title="Go to search!").logo
| > Nexus Cognitron
a.nav-link(href="https://t.me/nexus_search" title="News")
| News
</template>
<script>

View File

@ -1,16 +1,15 @@
<template lang="pug">
div.d-flex
div
nuxt-link(:to="{ name: 'documents-schema-id', params: { schema: document.schema, id: document.id }}") {{ document.icon }} {{ document.title }}
.detail
div
i.mr-1 DOI:
span {{ document.doi }}
div(v-if='document.getFirstAuthors(false, 1)')
span {{ document.getFirstAuthors(false, 1) }} {{ issuedAt }}
.gp
span.el.text-uppercase {{ document.getFormattedFiledata() }}
div.d-flex
div
nuxt-link(:to="{ name: 'documents-index-name-id', params: { schema: document.schema, id: document.id }}") {{ document.icon }} {{ document.title }}
.detail
div
i.mr-1 DOI:
span {{ document.doi }}
div(v-if='document.getFirstAuthors(false, 1)')
span {{ document.getFirstAuthors(false, 1) }} {{ issuedAt }}
.gp
span.el.text-uppercase {{ document.getFormattedFiledata() }}
</template>
<script>

View File

@ -1,19 +1,19 @@
<template lang="pug">
div
.top
h6 {{ document.title }}
.top
i
h6 {{ document.getFormattedLocator() }}
table
tbody
v-tr(label="DOI", :value="document.doi")
v-tr(label="Description", :value="document.abstract", @max-length=300)
v-tr(label="Tags", :value="tags")
v-tr(label="ISSNS", :value="issns")
v-tr(label="ISBNS", :value="isbns")
v-tr(label="File", :value="document.getFormattedFiledata()")
v-tr-multi-link(label="Links", :links="links")
div
.top
h6 {{ document.title }}
.top
i
h6 {{ document.getFormattedLocator() }}
table
tbody
v-tr(label="DOI", :value="document.doi")
v-tr(label="Description", :value="document.abstract", @max-length=300)
v-tr(label="Tags", :value="tags")
v-tr(label="ISSNS", :value="issns")
v-tr(label="ISBNS", :value="isbns")
v-tr(label="File", :value="document.getFormattedFiledata()")
v-tr-multi-link(label="Links", :links="links")
</template>
<script>

View File

@ -1,15 +1,15 @@
<template lang="pug">
div.d-flex
div
nuxt-link(:to="{ name: 'documents-schema-id', params: { schema: document.schema, id: document.id }}") {{ document.icon }} {{ document.title }}
.detail
div
i.mr-1(v-if='document.doi') DOI:
span {{ document.doi }}
div(v-if='document.getFirstAuthors(false, 1)')
span {{ document.getFirstAuthors(false, 1) }} {{ issuedAt }}
.gp
span.el.text-uppercase {{ document.getFormattedFiledata() }}
div.d-flex
div
nuxt-link(:to="{ name: 'documents-index-name-id', params: { index_alias: document.index_alias, id: document.id }}") {{ document.icon }} {{ document.title }}
.detail
div
i.mr-1(v-if='document.doi') DOI:
span {{ document.doi }}
div(v-if='document.getFirstAuthors(false, 1)')
span {{ document.getFirstAuthors(false, 1) }} {{ issuedAt }}
.gp
span.el.text-uppercase {{ document.getFormattedFiledata() }}
</template>
<script>

View File

@ -1,19 +1,19 @@
<template lang="pug">
div
.top
h6 {{ document.title }}
.top
i
h6 {{ document.getFormattedLocator() }}
table
tbody
v-tr(label="DOI", :value="document.doi")
v-tr(label="Description", :value="document.description", @max-length=300)
v-tr(label="Tags", :value="tags")
v-tr(label="ISBNS", :value="isbns")
v-tr(label="ISSNS", :value="issns")
v-tr(label="File", :value="document.getFormattedFiledata()")
v-tr-multi-link(label="Links", :links="links")
div
.top
h6 {{ document.title }}
.top
i
h6 {{ document.getFormattedLocator() }}
table
tbody
v-tr(label="DOI", :value="document.doi")
v-tr(label="Description", :value="document.description", @max-length=300)
v-tr(label="Tags", :value="tags")
v-tr(label="ISBNS", :value="isbns")
v-tr(label="ISSNS", :value="issns")
v-tr(label="File", :value="document.getFormattedFiledata()")
v-tr-multi-link(label="Links", :links="links")
</template>
<script>

View File

@ -1,8 +1,8 @@
<template lang="pug">
tr
th {{ label }}
td
a(v-for="link in links" :href="link.url" download) {{ link.value }}
tr
th {{ label }}
td
a(v-for="link in links" :href="link.url" download) {{ link.value }}
</template>
<script>

View File

@ -1,10 +1,10 @@
<template lang="pug">
tr(v-show="value")
th {{ label }}
td(:class="valueClasses")
| {{ formattedValue }}
cite
a(href="javascript:void(null);" @click="showMore" v-if="shouldCollapseText") show more...
tr(v-show="value")
th {{ label }}
td(:class="valueClasses")
| {{ formattedValue }}
cite
a(href="javascript:void(null);" @click="showMore" v-if="shouldCollapseText") show more...
</template>
<script>

View File

@ -1,7 +0,0 @@
<template lang="pug">
div
v-header
b-container.mt-3
nuxt
v-footer
</template>

View File

@ -1,90 +0,0 @@
let buildDir = process.argv.find((s) => s.startsWith('--buildDir='))
if (buildDir) {
buildDir = buildDir.substr('--buildDir='.length)
} else {
buildDir = 'nexus/cognitron/web/.nuxt'
}
module.exports = {
server: {
host: process.env['NEXUS_COGNITRON_WEB_application.address'] || '0.0.0.0',
port: process.env['NEXUS_COGNITRON_WEB_application.port'] || 3000
},
buildDir: buildDir,
srcDir: 'nexus/cognitron/web',
modulesDir: ['external/' + process.env.BAZEL_NODE_MODULES_ROOT],
head: {
title: 'Nexus Cognitron',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: 'Biggest Library on both Earth and Mars' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' },
{ rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' },
{ rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png' },
{ rel: 'manifest', href: '/site.webmanifest' },
{ rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#5bbad5' },
{ name: 'msapplication-TileColor', content: '#603cba' },
{ name: 'theme-color', content: '#ffffff' }
]
},
// Global CSS (https://go.nuxtjs.dev/config-css)
css: [
'@/assets/css/app.scss',
'@/assets/css/terminal.css'
],
publicRuntimeConfig: {
meta_api: {
hostname: process.env['NEXUS_COGNITRON_WEB_meta_api.hostname'],
url: process.env['NEXUS_COGNITRON_WEB_meta_api.url']
},
ipfs: {
gateway: {
url: process.env['NEXUS_COGNITRON_WEB_ipfs.gateway.url'] || 'https://ipfs.io'
}
}
},
// Plugins to run before rendering page (https://go.nuxtjs.dev/config-plugins)
plugins: [
'plugins/helpers',
{ src: 'plugins/meta-api', mode: 'client' },
'plugins/utils'
],
// Auto import components (https://go.nuxtjs.dev/config-components)
components: true,
// Modules for dev and build (recommended) (https://go.nuxtjs.dev/config-modules)
buildModules: [],
// Modules (https://go.nuxtjs.dev/config-modules)
modules: [
'@nuxtjs/axios',
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt'
],
loading: { color: '#1a95e0', throttle: 0 },
watchers: {
webpack: {
poll: true
}
},
// Build Configuration (https://go.nuxtjs.dev/config-build)
build: {
extend (config) {
config.resolve.alias['~'] = process.cwd()
},
transpile: ['nexus-meta-api-js-client', 'nexus-views-js']
},
node: {
window: 'empty'
}
}

View File

@ -0,0 +1,33 @@
import { defineNuxtConfig } from 'nuxt3'
let buildDir = process.argv.find((s) => s.startsWith('--buildDir='))
if (buildDir) {
buildDir = buildDir.substr('--buildDir='.length)
}
export default defineNuxtConfig({
head: {
title: 'Nexus Cognitron',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: 'Biggest Library on both Earth and Mars' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' },
{ rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' },
{ rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png' },
{ rel: 'manifest', href: '/site.webmanifest' },
{ rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#5bbad5' },
{ name: 'msapplication-TileColor', content: '#603cba' },
{ name: 'theme-color', content: '#ffffff' }
]
},
nitro: {
preset: 'server',
output: {
dir: buildDir,
}
}
})

View File

@ -1,22 +0,0 @@
<template>
<div>
<document
v-if="document"
:document="document"
/>
</div>
</template>
<script>
export default {
data () {
return {
document: {}
}
},
async fetch () {
this.document = await this.$meta_api.get(this.$route.params.schema, this.$route.params.id)
},
fetchOnServer: false
}
</script>

View File

@ -1,101 +0,0 @@
<template lang="pug">
div
form
.input-group
b-form-input(v-model='query' placeholder='Enter book name or DOI')
b-button(type='submit' @click.stop.prevent='submit(query, 1, schemas)') Search
b-form-checkbox-group.checkbox-group(
v-model="schemas"
:options="availableSchemas"
value-field="item"
text-field="name")
p.mt-5(v-if="nothingFound") Nothing found
.search-list
search-list(:documents='documents')
b-pagination(v-if='documents.length > 0' v-model='page' :total-rows='totalRows' :per-page='perPage' limit="2" :disabled="isLoading")
</template>
<script>
import SearchList from '@/components/search-list'
export default {
name: 'Index',
components: { SearchList },
loading: true,
data () {
return {
availableSchemas: [
{ item: 'scitech', name: 'SciTech' },
{ item: 'scimag', name: 'SciMag' }
],
documents: [],
nothingFound: false,
page: 1,
perPage: 1,
query: '',
schemas: ['scimag', 'scitech'],
totalRows: 10
}
},
async fetch () {
this.nothingFound = false
this.query = this.$route.query.query
if (!this.query) {
this.documents = []
return this.$router.push({ path: '/' })
}
this.schemas = this.$route.query.schemas.split(',')
if (this.schemas.length === 0) {
this.schemas = ['scimag', 'scitech']
}
this.page = this.$route.query.page
await this.retrieveDocuments()
},
fetchOnServer: false,
computed: {
isLoading () {
return this.$fetchState.pending || false
}
},
watch: {
'$route.query': '$fetch',
async schemas () {
if (this.query) {
await this.submit(this.query, 1, this.schemas)
}
},
async page () {
await this.submit(this.query, this.page, this.schemas)
}
},
methods: {
async submit (query, page, schemas) {
await this.$router.push({ path: '/', query: { query: query, page: page, schemas: schemas.join(',') } })
},
async retrieveDocuments () {
const response = await this.$meta_api.search(this.schemas, this.query, this.page - 1, 5)
if (response.hasNext) {
this.totalRows = Number(this.page) + 1
} else {
this.totalRows = this.page
}
if (response.documents.length === 0) {
this.nothingFound = true
}
this.documents = response.documents
}
}
}
</script>
<style scoped>
.search-list {
padding-top: 15px;
padding-bottom: 15px;
}
.checkbox-group {
margin: 10px 0;
}
</style>

20
nexus/cognitron/web/plugins/meta-api.js Executable file → Normal file
View File

@ -5,10 +5,10 @@ function getSchema (typedDocument) {
return Object.keys(typedDocument).filter(k => typedDocument[k] !== undefined)[0]
}
function schemaToView (schema, pb) {
if (schema === 'scimag') {
function indexNameToView (indexName, pb) {
if (indexName === 'scimag') {
return new ScimagView(pb)
} else if (schema === 'scitech') {
} else if (indexName === 'scitech') {
return new ScitechView(pb)
}
}
@ -18,16 +18,16 @@ class MetaApiWrapper {
this.metaApi = new MetaApi(metaApiConfig.url || ('http://' + window.location.host), metaApiConfig.hostname)
}
async get (schema, id) {
const response = await this.metaApi.get(schema, id)
return schemaToView(schema, response[schema])
async get (indexName, id) {
const response = await this.metaApi.get(indexName, id)
return indexNameToView(indexName, response[indexName])
}
async search (schemas, query, page, pageSize) {
const response = await this.metaApi.search(schemas, query, page, pageSize)
async search (names, query, page, pageSize) {
const response = await this.metaApi.search(names, query, page, pageSize)
const documents = response.scoredDocumentsList.map((scoredDocument) => {
const schema = getSchema(scoredDocument.typedDocument)
return schemaToView(schema, scoredDocument.typedDocument[schema])
const indexName = getSchema(scoredDocument.typedDocument)
return indexNameToView(indexName, scoredDocument.typedDocument[indexName])
})
return {
hasNext: response.hasNext,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#603cba</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View File

@ -1,932 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="640.000000pt" height="640.000000pt" viewBox="0 0 640.000000 640.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,640.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M2 6365 c0 -23 4 -35 12 -32 6 2 10 12 8 22 -3 13 1 16 14 11 10 -4
21 0 28 9 8 11 15 12 26 4 11 -8 12 -12 3 -16 -25 -10 -12 -21 22 -20 33 1 52
17 20 17 -8 0 -15 5 -15 10 0 6 6 10 13 8 7 -2 15 2 17 7 3 6 -25 11 -71 13
l-77 3 0 -36z"/>
<path d="M6364 6385 c25 -19 36 -19 36 0 0 10 -10 15 -27 15 -27 -1 -27 -1 -9
-15z"/>
<path d="M200 6381 c0 -5 -8 -8 -17 -7 -13 1 -19 -7 -21 -26 -5 -42 41 -35 50
7 2 11 10 19 16 18 7 -2 12 2 12 7 0 6 -9 10 -20 10 -11 0 -20 -4 -20 -9z"/>
<path d="M6245 6381 c-3 -5 -1 -12 5 -16 5 -3 10 -16 10 -28 1 -20 2 -21 14
-4 7 9 16 17 20 17 3 0 6 -8 7 -17 0 -15 2 -15 9 2 5 11 9 29 9 40 0 19 0 19
-10 2 -10 -17 -13 -17 -34 -3 -13 9 -26 12 -30 7z"/>
<path d="M43 6320 c1 -20 8 -25 20 -14 7 8 -3 34 -13 34 -5 0 -8 -9 -7 -20z"/>
<path d="M0 6290 c0 -14 6 -20 23 -19 16 1 17 3 5 6 -10 2 -15 10 -11 19 3 8
0 14 -6 14 -6 0 -11 -9 -11 -20z"/>
<path d="M6270 6289 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M338 6233 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
<path d="M10 6213 c0 -13 5 -23 10 -23 13 0 13 11 0 30 -8 12 -10 11 -10 -7z"/>
<path d="M40 6170 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 -11 10 -2 0 -4
-4 -4 -10z"/>
<path d="M0 6149 c0 -5 5 -7 10 -4 5 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10 -5
-10 -11z"/>
<path d="M6310 6079 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M6375 6070 c4 -6 11 -8 16 -5 14 9 11 15 -7 15 -8 0 -12 -5 -9 -10z"/>
<path d="M6271 5854 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M6040 5789 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M5070 5749 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M5240 5740 c0 -5 5 -10 10 -10 6 0 10 5 10 10 0 6 -4 10 -10 10 -5 0
-10 -4 -10 -10z"/>
<path d="M3170 5705 c0 -11 11 -15 37 -15 35 0 36 1 17 15 -25 19 -54 19 -54
0z"/>
<path d="M3068 5693 c17 -2 47 -2 65 0 17 2 3 4 -33 4 -36 0 -50 -2 -32 -4z"/>
<path d="M3285 5690 c22 -5 65 -9 95 -9 49 -1 264 -33 315 -47 11 -3 28 -8 38
-10 9 -3 17 -1 17 4 0 18 -251 59 -405 66 -73 4 -89 3 -60 -4z"/>
<path d="M2948 5683 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
<path d="M3884 5589 c32 -11 63 -18 70 -16 13 5 -89 37 -113 36 -9 0 10 -9 43
-20z"/>
<path d="M2390 5570 c-9 -6 -10 -10 -3 -10 6 0 15 5 18 10 8 12 4 12 -15 0z"/>
<path d="M3970 5566 c0 -2 9 -6 20 -9 11 -3 18 -1 14 4 -5 9 -34 13 -34 5z"/>
<path d="M4030 5547 c0 -7 141 -59 146 -54 3 2 -27 16 -66 31 -72 26 -80 29
-80 23z"/>
<path d="M2290 5530 c-8 -5 -10 -10 -5 -10 6 0 17 5 25 10 8 5 11 10 5 10 -5
0 -17 -5 -25 -10z"/>
<path d="M4340 5399 c80 -45 173 -101 208 -125 35 -24 66 -44 70 -44 8 0 -60
59 -69 60 -4 0 -37 21 -75 46 -65 43 -254 144 -271 144 -5 0 57 -37 137 -81z"/>
<path d="M4700 5380 c0 -5 5 -10 10 -10 6 0 10 5 10 10 0 6 -4 10 -10 10 -5 0
-10 -4 -10 -10z"/>
<path d="M1835 5290 c-3 -5 -2 -10 4 -10 6 0 50 -33 98 -73 48 -40 98 -80 110
-88 12 -9 45 -36 73 -62 28 -26 56 -47 63 -47 7 0 6 -4 -3 -10 -13 -8 -13 -10
1 -10 9 0 22 -7 29 -15 7 -8 16 -12 21 -9 5 3 -28 37 -73 77 -133 117 -144
127 -125 121 9 -3 17 -1 17 5 0 6 -5 11 -10 11 -6 0 -20 9 -32 20 -12 11 -27
20 -32 19 -6 0 -5 -3 2 -6 6 -2 12 -9 12 -15 0 -6 -6 -6 -15 2 -8 7 -15 18
-15 26 0 7 -10 16 -22 20 -13 4 -34 17 -47 30 -25 25 -46 30 -56 14z"/>
<path d="M4636 5216 c3 -9 14 -18 23 -21 10 -3 31 -18 47 -33 16 -15 38 -33
49 -41 11 -7 -5 10 -35 38 -64 60 -93 79 -84 57z"/>
<path d="M2065 5140 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0
-7 -4 -4 -10z"/>
<path d="M2138 5087 c79 -71 202 -169 196 -155 -3 7 -39 40 -81 73 -41 33 -91
75 -111 92 -20 18 -40 33 -44 33 -4 0 14 -19 40 -43z"/>
<path d="M4780 5109 c0 -2 48 -51 107 -109 60 -58 96 -89 82 -70 -23 31 -189
188 -189 179z"/>
<path d="M2250 4951 c0 -5 5 -13 10 -16 6 -3 10 -2 10 4 0 5 -4 13 -10 16 -5
3 -10 2 -10 -4z"/>
<path d="M2375 4890 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0
-7 -4 -4 -10z"/>
<path d="M2400 4873 c0 -5 13 -17 28 -27 l27 -19 -24 26 c-26 28 -31 32 -31
20z"/>
<path d="M2477 4809 c12 -12 24 -21 27 -18 2 2 -8 13 -22 23 l-27 19 22 -24z"/>
<path d="M2495 4730 c10 -11 20 -20 23 -20 3 0 -3 9 -13 20 -10 11 -20 20 -23
20 -3 0 3 -9 13 -20z"/>
<path d="M1206 4694 c-5 -14 -4 -15 9 -4 17 14 19 20 6 20 -5 0 -12 -7 -15
-16z"/>
<path d="M2595 4628 c39 -39 82 -84 97 -99 15 -14 24 -29 21 -33 -3 -3 -1 -6
5 -6 7 0 12 -4 12 -10 0 -5 -19 -11 -42 -12 -40 -1 -40 -1 -8 -5 19 -2 45 -6
56 -8 22 -5 57 -49 57 -72 0 -7 4 -10 8 -7 11 6 56 -94 65 -146 11 -65 13
-171 3 -197 -4 -12 -11 -44 -14 -70 -3 -26 -15 -81 -27 -120 -19 -65 -25 -73
-47 -74 -14 -1 -30 -1 -35 0 -4 1 -14 -3 -21 -9 -12 -10 -71 -4 -90 10 -5 4
-30 10 -55 13 -58 7 -80 16 -80 33 0 18 -37 36 -60 29 -10 -3 -33 1 -50 10
-16 8 -30 11 -30 6 0 -13 47 -41 70 -41 11 0 20 -4 20 -10 0 -24 -85 -1 -106
29 -9 12 -22 16 -43 13 -16 -2 -37 1 -48 8 -15 9 -13 10 16 6 l33 -6 -23 21
-24 21 22 -5 c13 -3 26 -1 29 4 5 9 -15 13 -50 9 -10 -1 -15 3 -12 8 3 5 -6
13 -22 17 -15 3 -41 17 -59 31 -17 13 -38 21 -46 18 -8 -3 -18 -1 -22 5 -3 6
-16 11 -28 11 -22 0 -22 -1 3 -21 l25 -20 -32 15 c-78 39 -49 6 37 -40 38 -21
68 -39 66 -41 -3 -3 -101 41 -126 57 -16 10 -284 99 -371 124 -30 8 -94 26
-144 41 -107 31 -225 45 -208 23 6 -7 12 -16 12 -20 3 -22 114 -133 136 -136
16 -2 14 2 -10 21 -55 45 -71 62 -78 80 -8 22 -6 22 130 -9 63 -14 120 -23
126 -21 7 2 -19 11 -58 20 -96 23 -164 46 -139 46 34 1 343 -86 359 -101 11
-11 -27 -3 -93 19 -24 8 -46 13 -48 11 -2 -3 56 -24 129 -48 72 -24 137 -46
142 -50 6 -4 30 -15 55 -24 43 -16 158 -66 208 -89 12 -6 22 -15 22 -20 0 -10
1 -11 -185 33 -71 17 -193 37 -270 46 -77 8 -154 19 -171 25 -17 5 -37 9 -46
9 -17 0 -32 -26 -22 -41 3 -5 0 -9 -5 -9 -6 0 -11 5 -11 10 0 6 -4 10 -10 10
-20 0 -9 -15 28 -40 46 -31 47 -32 100 -75 24 -19 49 -35 55 -35 19 1 -67 74
-112 95 -53 26 -52 38 2 31 23 -3 85 -10 137 -16 52 -5 113 -12 135 -15 25 -3
17 1 -20 9 -33 8 -107 20 -165 26 -130 14 -146 24 -25 15 203 -15 565 -91 550
-116 -4 -5 -2 -9 4 -9 5 0 22 -5 38 -12 15 -6 34 -12 41 -14 45 -9 4 -12 -140
-11 -197 2 -354 -7 -372 -23 -8 -6 -18 -10 -22 -9 -20 3 -34 -2 -34 -13 0 -6
3 -8 7 -5 3 4 16 1 28 -7 18 -12 31 -12 86 0 l64 13 -70 -4 c-109 -8 -56 12
70 26 122 13 313 6 348 -12 12 -7 33 -13 47 -15 14 -1 39 -9 55 -18 l30 -16
-30 -1 c-73 -2 -172 -10 -190 -15 -11 -3 -71 -16 -133 -29 -61 -13 -120 -29
-130 -35 -14 -8 -12 -9 11 -4 38 9 34 -3 -8 -20 -25 -11 -33 -19 -29 -30 5
-13 -2 -16 -41 -16 -34 0 -44 -3 -39 -12 6 -10 4 -10 -8 0 -11 9 -17 10 -20 2
-4 -12 -365 -23 -372 -11 -4 6 -260 -3 -346 -13 -25 -3 -71 -5 -104 -5 -51 -1
-57 -3 -46 -16 7 -9 21 -13 31 -10 11 4 19 2 19 -4 0 -6 -4 -11 -10 -11 -14 0
-13 -37 1 -46 12 -7 146 -35 164 -33 18 1 -29 18 -53 18 -13 1 -30 7 -38 15
-7 8 -22 12 -33 9 -10 -3 -22 1 -27 8 -5 8 -3 11 8 7 8 -4 46 2 84 13 84 23
243 38 469 44 94 2 197 7 230 10 49 4 55 3 35 -6 -14 -6 -41 -12 -60 -13 -20
-1 -58 -10 -85 -19 -28 -9 -81 -23 -120 -33 -38 -9 -119 -30 -179 -46 -80 -21
-111 -26 -117 -17 -4 6 -12 8 -18 5 -6 -4 -9 -11 -6 -15 2 -5 -16 -16 -40 -26
-25 -9 -45 -22 -45 -29 0 -14 76 -43 87 -33 3 4 -1 7 -11 7 -39 0 1 21 97 50
135 41 131 40 125 45 -2 2 -28 -2 -58 -10 -59 -16 -77 -18 -68 -10 3 3 52 17
109 31 143 34 140 33 133 23 -3 -5 -15 -9 -27 -9 -12 0 -29 -5 -37 -10 -17
-12 4 -8 100 16 52 13 300 54 418 69 31 4 56 13 59 21 3 8 13 14 22 14 9 0 23
5 31 11 11 8 6 9 -22 5 -40 -5 -46 1 -25 23 10 11 9 13 -5 8 -9 -3 -41 -2 -70
3 -31 4 -54 4 -56 -1 -4 -11 -22 -12 -22 -1 0 5 16 16 36 25 19 10 33 21 30
25 -2 4 21 8 52 8 35 0 57 4 60 12 2 7 13 10 28 6 30 -8 96 4 89 16 -3 5 -1
11 5 15 5 3 10 -1 10 -9 0 -10 9 -16 23 -16 15 0 18 3 9 9 -14 9 76 21 156 21
34 0 43 -3 35 -11 -6 -6 -24 -11 -38 -11 -19 0 -23 -3 -14 -9 8 -5 11 -9 7
-10 -8 -1 -19 -4 -35 -8 -5 -2 1 -6 11 -10 15 -6 4 -12 -45 -25 -77 -22 -205
-73 -239 -97 -14 -9 -79 -27 -145 -39 -140 -26 -198 -43 -117 -35 83 9 61 -2
-44 -20 -53 -10 -90 -21 -84 -25 13 -8 -99 -50 -134 -50 -18 0 -19 -2 -6 -11
12 -9 10 -10 -8 -6 -13 3 -21 3 -18 0 3 -3 -25 -21 -62 -40 -72 -37 -104 -61
-97 -73 7 -12 55 -18 61 -9 2 5 26 18 52 29 25 11 49 24 52 28 8 11 80 43 143
63 64 20 100 38 79 39 -20 0 -240 -87 -291 -116 -50 -27 -69 -31 -51 -9 7 8
16 15 21 15 5 0 49 19 97 41 93 44 276 109 301 108 9 0 -2 -9 -24 -19 -54 -25
-34 -24 40 1 33 12 71 23 85 24 13 2 26 9 28 14 7 20 41 12 39 -9 -1 -22 -1
-21 -34 -25 -50 -5 -114 -29 -137 -50 -14 -13 -27 -21 -30 -18 -6 6 -148 -70
-196 -106 -57 -43 -59 -45 -45 -65 7 -9 15 -13 16 -8 6 15 149 104 239 149 91
45 290 123 316 123 28 0 5 -17 -41 -30 -50 -14 -75 -35 -28 -24 l28 7 -22 -22
c-12 -11 -24 -21 -27 -21 -3 0 -31 -16 -62 -34 -32 -19 -61 -33 -66 -30 -5 3
-7 2 -6 -3 2 -4 -1 -9 -6 -10 -5 -1 -12 -6 -15 -11 -11 -18 -108 -72 -129 -72
-11 0 -24 -4 -27 -10 -3 -6 4 -10 17 -10 23 -1 23 -1 -5 -23 l-27 -23 22 -21
c20 -17 32 -20 86 -15 34 2 62 9 62 14 0 6 -7 8 -15 4 -17 -6 -75 3 -75 13 1
3 24 23 53 44 140 101 160 117 166 123 9 10 -71 -36 -90 -53 -8 -7 -19 -13
-23 -13 -10 0 -84 -58 -119 -93 -16 -16 -31 -26 -35 -23 -19 20 216 188 265
190 18 1 35 5 40 9 4 5 0 5 -10 1 -37 -14 -15 15 26 34 68 32 80 36 115 37 38
2 41 -6 10 -28 -19 -13 -20 -16 -6 -16 9 -1 20 4 23 9 3 6 11 10 18 10 14 0
-17 -28 -87 -82 -74 -57 -216 -182 -216 -190 0 -5 7 -8 15 -8 8 0 15 -6 15
-13 0 -15 77 -11 86 5 4 7 -9 8 -37 4 -33 -5 -39 -4 -21 4 12 5 22 14 22 20 0
5 11 20 25 32 24 21 35 46 15 33 -5 -3 -10 -2 -10 3 0 6 7 13 16 16 8 3 12 2
9 -4 -12 -20 12 -9 41 18 54 51 111 91 159 112 41 18 50 19 64 7 9 -8 22 -12
29 -10 28 11 6 -15 -57 -67 -37 -30 -88 -78 -113 -107 -25 -28 -49 -49 -52
-46 -3 4 -5 -6 -4 -21 2 -16 8 -30 14 -33 13 -5 158 37 151 44 -3 2 -26 -1
-51 -8 -62 -15 -76 -3 -48 43 27 43 80 94 89 84 4 -3 -9 -21 -28 -39 -20 -18
-42 -41 -49 -52 -8 -11 22 14 65 55 89 84 155 140 164 140 13 0 43 -43 34 -49
-4 -3 -34 -39 -67 -78 -32 -40 -62 -73 -66 -73 -14 0 -41 -63 -32 -72 16 -16
93 -5 137 19 12 7 25 7 40 -1 23 -12 118 -9 110 3 -2 4 -18 6 -35 3 -35 -5
-39 10 -13 48 l17 25 -24 -20 -23 -20 13 25 c7 14 27 43 44 65 17 22 32 42 33
44 2 2 6 1 10 -2 3 -4 -2 -17 -13 -30 -11 -12 -26 -31 -34 -42 -14 -18 -14
-19 2 -7 9 8 22 11 27 7 7 -4 8 1 4 11 -5 13 -3 15 7 9 8 -5 11 -4 7 2 -4 6 2
13 13 16 11 3 18 1 15 -3 -3 -5 -8 -28 -11 -52 -4 -24 -10 -51 -15 -60 -13
-24 -11 -56 3 -60 7 -3 32 7 55 21 39 23 43 30 43 66 0 84 17 64 23 -27 4 -51
9 -103 13 -116 5 -19 3 -21 -12 -16 -17 6 -17 5 1 -9 15 -11 22 -12 28 -4 5 7
5 1 1 -13 -4 -14 -7 -29 -7 -35 1 -19 41 -200 58 -260 29 -101 34 -137 35
-234 0 -107 -7 -126 -106 -275 -32 -49 -63 -96 -69 -105 -5 -9 -20 -31 -32
-48 -13 -17 -23 -34 -23 -37 0 -8 -63 -101 -72 -104 -4 -2 -8 -9 -8 -16 0 -6
-9 -18 -20 -26 -11 -8 -20 -19 -20 -25 0 -5 -19 -28 -43 -50 -23 -22 -40 -41
-37 -42 3 -1 -5 -15 -18 -32 -13 -18 -27 -29 -32 -26 -6 3 -7 -1 -4 -9 3 -8
-4 -25 -16 -38 -12 -13 -43 -54 -68 -93 -26 -38 -49 -72 -52 -75 -3 -3 -5 -16
-6 -30 0 -14 -4 -50 -7 -80 -3 -30 -3 -85 0 -122 6 -67 6 -68 32 -62 38 8 100
35 144 62 48 29 69 26 72 -13 4 -51 16 -129 20 -134 7 -6 85 34 143 72 28 20
53 33 53 29 1 -4 2 -18 3 -32 5 -63 18 -120 29 -127 19 -12 115 -10 110 3 -2
6 6 18 19 26 13 9 37 27 53 42 17 14 35 26 41 26 6 0 18 15 25 33 7 17 20 37
29 44 8 6 11 15 7 19 -4 5 -12 2 -16 -5 -7 -10 -10 -11 -16 0 -5 8 1 20 16 32
13 11 23 27 22 36 -2 9 3 6 9 -7 9 -19 9 -25 -3 -32 -11 -7 -11 -12 -1 -22 9
-9 12 -9 12 2 0 25 22 -6 42 -57 25 -65 23 -104 -6 -138 l-24 -27 -18 31 c-10
17 -21 31 -25 31 -4 0 -10 11 -13 25 -4 14 -11 25 -16 25 -6 0 -10 -2 -10 -5
0 -11 24 -55 30 -55 4 0 12 -12 18 -26 7 -14 8 -23 3 -19 -5 3 -12 1 -15 -3
-3 -5 -27 -6 -53 -2 -26 5 -53 4 -58 0 -6 -5 -35 -10 -65 -13 -45 -3 41 -7
210 -9 51 -1 61 3 71 29 7 19 13 23 21 15 8 -8 8 -15 0 -25 -10 -11 -6 -14 22
-15 32 -1 33 0 45 53 11 50 29 81 32 56 3 -21 4 -26 17 -56 7 -17 21 -36 30
-42 23 -14 114 -25 128 -16 14 10 27 73 15 73 -5 0 -7 10 -5 23 4 22 2 19 -23
-43 -14 -33 -15 -34 -29 -16 -8 11 -14 16 -14 10 0 -5 -5 -1 -11 8 -7 14 -2
35 25 90 19 40 40 92 47 115 11 37 14 41 24 26 7 -10 11 -23 8 -30 -3 -6 2
-22 11 -35 20 -28 20 -14 1 76 -13 60 -13 76 -1 116 8 25 12 58 11 74 -2 17 4
35 16 47 10 10 14 19 9 19 -6 0 -9 28 -8 70 2 38 6 70 10 70 4 0 30 -48 58
-107 28 -60 57 -119 64 -133 7 -14 21 -54 31 -88 10 -34 23 -62 28 -62 5 0 7
6 5 13 -3 6 -13 39 -23 72 -10 33 -21 62 -25 65 -20 17 -100 223 -100 257 0
18 -4 33 -9 33 -12 0 -20 80 -11 126 l7 39 26 -55 c34 -70 35 -39 2 34 -39 86
-31 136 15 96 13 -11 19 -20 14 -20 -5 0 -2 -6 8 -13 9 -7 24 -27 35 -43 10
-17 34 -55 53 -85 19 -30 42 -67 50 -84 8 -16 18 -32 21 -35 3 -3 23 -36 44
-75 21 -38 43 -79 49 -90 37 -67 96 -143 96 -124 0 7 -7 20 -15 29 -8 9 -38
61 -65 115 -28 54 -60 109 -70 123 -11 13 -20 31 -20 38 0 8 -5 14 -10 14 -6
0 -16 12 -22 28 -7 15 -24 45 -39 67 -14 22 -34 58 -45 80 -10 22 -21 42 -24
45 -22 16 -58 103 -62 148 l-5 52 23 -25 c12 -13 31 -38 41 -55 12 -20 24 -30
38 -28 18 3 21 -4 26 -57 3 -33 7 -48 9 -32 4 43 5 45 11 51 12 13 -3 54 -24
65 -21 10 -21 11 3 5 l25 -7 -20 24 c-11 13 -26 30 -34 36 -9 8 -10 13 -3 13
6 0 -1 12 -15 26 -19 19 -23 28 -14 37 8 8 14 8 24 -2 10 -10 8 -11 -7 -5 -32
12 -10 -12 24 -26 16 -7 27 -17 24 -22 -3 -5 2 -16 12 -23 15 -11 16 -14 4
-15 -11 0 -11 -3 3 -11 11 -6 26 -12 35 -13 10 -2 28 -22 40 -45 l24 -41 -29
12 c-15 6 -32 15 -38 20 -15 13 -27 9 -13 -5 15 -15 75 -41 82 -36 2 2 15 -9
27 -26 13 -16 27 -31 31 -33 5 -2 8 -12 8 -23 0 -10 4 -19 9 -19 8 0 89 -103
152 -195 46 -66 44 -64 85 -110 35 -38 36 -38 18 -8 -10 18 -30 47 -45 64 -15
18 -33 44 -40 58 -8 14 -30 50 -51 79 -22 32 -34 60 -31 69 4 10 2 14 -5 9 -6
-3 -13 -2 -17 4 -3 5 2 10 12 11 14 0 14 2 -4 9 -13 5 -23 16 -23 25 0 8 -4
15 -10 15 -5 0 -7 7 -4 15 4 10 0 15 -11 15 -9 0 -14 4 -10 9 3 6 -3 14 -12
20 -10 6 -13 11 -8 11 6 0 1 8 -11 18 -11 9 -46 54 -78 98 -31 45 -75 100 -96
122 -25 26 -35 42 -27 45 7 3 4 6 -7 6 -12 1 -16 6 -12 16 3 9 0 15 -9 15 -8
0 -15 6 -15 14 0 8 -6 17 -12 19 -10 4 -10 8 1 15 11 8 9 11 -10 14 -18 2 -27
13 -37 43 -17 52 -17 191 0 220 3 6 18 62 32 125 15 63 31 131 36 150 5 19 12
53 16 75 3 22 9 42 13 45 12 10 14 140 1 140 -6 0 -8 -5 -4 -11 8 -14 -27
-189 -39 -189 -5 0 -7 -7 -3 -15 3 -8 -1 -21 -9 -29 -8 -7 -11 -17 -7 -20 3
-4 2 -10 -3 -14 -9 -7 -55 -89 -55 -99 0 -2 4 -2 9 1 14 9 22 -23 11 -44 -7
-13 -6 -20 3 -24 9 -5 9 -7 0 -12 -7 -3 -10 -11 -7 -18 3 -7 -2 -25 -10 -42
l-16 -29 3 32 c1 17 6 29 11 25 5 -3 6 2 3 12 -4 10 -7 22 -7 27 0 20 -30 7
-36 -16 -3 -14 -12 -25 -20 -25 -10 0 -14 -14 -15 -42 -2 -32 -3 -36 -6 -15
-4 27 -5 28 -18 10 -13 -17 -14 -17 -28 2 -15 19 -16 18 -37 -17 -24 -41 -53
-56 -75 -38 -24 20 -26 -7 -4 -46 15 -28 19 -46 14 -72 -4 -20 -2 -38 3 -41
15 -9 3 -61 -13 -55 -7 3 -15 -5 -18 -17 -6 -21 -4 -21 60 -16 36 3 61 2 55
-2 -10 -6 -13 -16 -21 -61 -1 -3 -5 1 -9 8 -6 9 -13 0 -22 -27 -13 -40 -74
-110 -86 -99 -3 4 -15 -3 -27 -15 -20 -20 -21 -20 -27 -1 -4 10 -13 19 -21 19
-7 0 -16 11 -19 24 -7 32 -30 40 -24 9 3 -14 -1 -11 -10 10 -9 18 -20 31 -26
27 -5 -3 -10 3 -10 13 0 11 -6 22 -14 25 -11 4 -14 28 -7 58 0 3 18 4 39 3 20
0 57 2 82 6 40 7 41 8 11 9 -24 1 -32 5 -28 15 3 8 -2 16 -15 19 -13 4 -19 13
-17 29 1 13 -2 32 -7 43 -10 25 -10 82 1 113 7 22 6 23 -29 17 -29 -6 -36 -4
-36 9 0 9 -3 16 -7 16 -5 0 -16 17 -26 38 -23 48 -48 52 -41 8 l4 -31 -9 28
c-6 16 -7 36 -4 44 5 13 2 15 -15 11 -13 -4 -22 -1 -22 5 0 8 -3 7 -9 -1 -6
-10 -10 -9 -14 7 -3 12 -20 28 -39 36 -18 8 -27 14 -19 15 11 0 10 4 -4 20
-12 13 -15 27 -11 47 5 21 4 24 -4 13 -8 -12 -10 -11 -10 6 0 12 -7 27 -15 34
-18 15 -20 70 -2 70 7 0 8 3 2 8 -5 4 -11 14 -12 22 -1 8 -12 48 -24 89 -22
78 -18 94 22 84 11 -3 19 0 19 6 0 8 -12 11 -32 9 -33 -3 -33 -3 -38 49 -6 53
-13 68 -33 68 -7 0 -11 10 -9 22 2 12 -1 24 -7 27 -12 8 16 96 31 96 5 0 6
-10 3 -22 -5 -18 -4 -19 4 -8 15 22 24 5 11 -20 -8 -15 -8 -20 -1 -16 7 5 11
-4 11 -26 0 -35 15 -67 25 -56 4 3 1 16 -5 28 -6 11 -9 31 -7 43 4 18 5 17 6
-5 0 -16 6 -28 11 -28 6 0 10 -6 10 -12 0 -7 12 -24 27 -38 l26 -25 -6 25 c-4
14 -7 33 -7 43 0 10 -8 17 -21 17 -17 0 -20 5 -17 40 2 22 7 39 12 38 14 -4
27 55 16 66 -6 6 -10 17 -9 26 0 12 2 12 6 1 6 -15 44 -55 47 -49 20 36 24 74
11 105 -20 47 -20 70 -1 54 10 -8 16 -9 21 -1 3 6 9 8 13 6 4 -2 7 9 8 25 1
25 -1 27 -14 17 -12 -11 -14 -6 -8 37 7 64 10 78 20 93 4 6 2 12 -3 12 -6 0
-11 13 -11 29 0 24 6 31 32 40 22 8 34 8 36 1 2 -5 11 -10 20 -9 11 0 12 3 5
6 -9 3 -10 10 -3 23 7 12 7 28 0 44 -6 18 -6 26 1 26 6 0 7 5 3 11 -4 8 1 9
16 4 20 -6 21 -4 18 27 l-3 33 47 -3 48 -3 -17 30 c-14 24 -20 27 -32 18 -12
-10 -13 -9 -7 1 6 10 2 13 -15 10 -13 -1 -29 -14 -36 -28 -7 -14 -13 -18 -13
-10 0 13 -1 13 -10 0 -10 -15 -12 -12 -13 28 -1 9 4 16 11 15 7 -2 12 4 12 12
0 10 10 13 35 11 30 -2 35 1 35 19 0 13 -10 27 -25 35 -30 16 -32 33 -4 61 19
19 20 21 3 33 -12 9 -14 15 -6 20 7 4 12 13 12 19 0 7 -4 6 -10 -3 -7 -11 -8
-7 -4 15 3 20 -3 50 -21 90 l-25 60 24 -28 c14 -15 30 -44 35 -65 6 -21 17
-46 25 -57 9 -11 16 -27 16 -36 0 -9 7 -20 16 -23 24 -9 26 -8 14 14 -17 32 7
24 26 -10 23 -37 44 -47 44 -19 0 18 6 16 48 -19 67 -56 127 -78 172 -62 3 1
25 1 50 1 l45 -2 -55 28 c-48 24 -55 32 -55 55 1 19 -2 25 -9 18 -6 -6 -42
-10 -81 -9 l-70 1 58 12 c33 6 56 14 52 18 -14 14 17 38 47 38 27 0 30 3 24
23 -3 12 -6 25 -6 29 0 9 -74 48 -90 48 -5 0 -10 -7 -10 -15 0 -18 3 -17 -37
-6 -22 6 -42 6 -62 -2 l-29 -11 13 -43 c7 -24 19 -50 25 -58 7 -9 8 -15 2 -15
-5 0 -15 6 -21 14 -21 26 -49 142 -32 131 5 -3 30 -2 57 2 39 5 44 8 26 14
-13 5 -25 15 -28 24 -4 8 -13 15 -21 15 -7 0 -25 11 -39 25 -14 14 -35 27 -47
29 -11 1 -23 8 -25 14 -2 6 -15 10 -30 8 -15 -2 -20 -1 -10 1 26 7 30 31 4 26
-13 -3 -28 2 -35 11 -11 14 -11 16 5 17 11 0 14 3 7 6 -7 2 -13 9 -13 14 0 12
67 12 75 -1 3 -5 25 -10 48 -10 31 0 38 3 27 10 -8 5 -27 10 -42 10 -16 0 -28
4 -28 10 0 5 19 8 45 6 24 -3 47 0 50 5 4 5 16 10 28 11 30 2 -111 18 -148 16
-63 -2 -135 9 -135 21 0 3 -14 12 -31 20 -17 7 -68 41 -113 75 -95 72 -124 90
-80 50 54 -49 78 -69 137 -113 31 -23 57 -45 57 -50 0 -16 -55 -20 -77 -5 -22
14 -82 64 -173 146 -82 73 -88 78 -96 78 -5 0 23 -32 61 -72z m311 -195 c15
-20 18 -43 6 -43 -4 0 -23 13 -42 30 l-35 29 29 1 c16 0 35 -8 42 -17z m420
-245 c10 -12 15 -19 9 -16 -5 3 -18 8 -28 11 -10 4 -15 11 -12 17 8 13 9 13
31 -12z m-59 -33 c9 -25 -2 -27 -28 -6 l-24 20 23 1 c12 0 25 -7 29 -15z
m-1348 -175 c85 -31 141 -57 141 -65 0 -5 -50 13 -180 64 -99 39 -67 40 39 1z
m1461 10 c13 -8 13 -10 -2 -10 -9 0 -20 5 -23 10 -8 13 5 13 25 0z m-1180 -85
c7 -8 9 -15 6 -15 -4 0 -14 7 -22 15 -9 8 -11 15 -6 15 5 0 15 -7 22 -15z m20
-45 c0 -5 -2 -10 -4 -10 -3 0 -8 5 -11 10 -3 6 -1 10 4 10 6 0 11 -4 11 -10z
m48 -54 c11 -4 28 -4 38 -1 14 4 13 2 -3 -6 -12 -6 -33 -9 -47 -5 -20 5 -26
12 -25 34 0 25 1 26 9 5 5 -12 17 -24 28 -27z m212 12 c0 -6 -6 -5 -15 2 -8 7
-15 14 -15 16 0 2 7 1 15 -2 8 -4 15 -11 15 -16z m431 2 c22 -12 23 -19 10
-54 -8 -18 -15 -23 -30 -19 -24 6 -29 33 -6 33 17 0 20 16 5 25 -11 7 -14 25
-4 25 3 0 14 -5 25 -10z m-429 -47 c21 -9 35 -19 33 -23 -3 -4 15 -11 40 -14
43 -7 62 -29 33 -39 -7 -2 1 -2 17 1 37 5 23 -13 -17 -23 -22 -6 -28 -4 -28
10 0 11 -6 15 -17 12 -23 -5 -75 21 -68 33 4 6 -5 10 -20 10 -14 0 -28 6 -31
14 -3 8 -16 17 -30 20 -13 4 -24 11 -24 17 0 13 63 3 112 -18z m-255 1 c-3 -3
-12 -4 -19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z m368 -14 c3 -5 1 -10 -4 -10
-6 0 -11 5 -11 10 0 6 2 10 4 10 3 0 8 -4 11 -10z m-188 -36 c-3 -3 -12 -4
-19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z m49 -15 c3 -6 20 -17 38 -25 17 -7 24
-13 16 -14 -8 0 -33 11 -55 25 -23 14 -33 25 -23 25 9 0 20 -5 24 -11z m172
-46 c-10 -2 -18 -11 -18 -19 0 -8 -7 -14 -15 -14 -15 0 -21 21 -8 33 3 4 18 6
32 6 17 -1 20 -3 9 -6z m53 -43 c-24 -21 -41 -26 -41 -12 0 5 12 13 28 19 39
15 40 15 13 -7z m-251 0 c0 -5 -2 -10 -4 -10 -3 0 -8 5 -11 10 -3 6 -1 10 4
10 6 0 11 -4 11 -10z m310 3 c0 -17 -36 -43 -59 -43 -28 0 -21 12 21 35 27 15
38 18 38 8z m126 -30 c-7 -7 -26 7 -26 19 0 6 6 6 15 -2 9 -7 13 -15 11 -17z
m-748 10 c-10 -2 -28 -2 -40 0 -13 2 -5 4 17 4 22 1 32 -1 23 -4z m643 -20
c-5 -18 -16 -29 -34 -34 -33 -9 -36 2 -4 16 12 6 23 16 23 23 2 20 4 22 13 22
5 0 6 -12 2 -27z m-734 11 c-3 -3 -12 -4 -19 -1 -8 3 -5 6 6 6 11 1 17 -2 13
-5z m806 -29 c-3 -9 -8 -14 -10 -11 -3 3 -2 9 2 15 9 16 15 13 8 -4z m-243
-15 c0 -5 -4 -10 -10 -10 -5 0 -10 5 -10 10 0 6 5 10 10 10 6 0 10 -4 10 -10z
m-243 -13 c-36 -11 -37 -13 -22 -32 12 -16 14 -17 9 -2 -6 17 7 22 48 19 9 -1
22 5 28 13 7 8 20 15 29 14 11 0 10 -3 -6 -11 -13 -6 -23 -15 -23 -20 0 -4 -7
-8 -17 -8 -9 0 -13 -3 -10 -7 7 -6 -23 -27 -55 -39 -10 -3 -18 -10 -18 -15 0
-13 2 -12 54 17 57 32 75 24 20 -8 l-39 -23 29 -5 c16 -3 31 -12 33 -20 4 -13
3 -13 -7 0 -8 11 -13 12 -16 3 -3 -7 -12 -9 -21 -6 -10 4 -14 2 -9 -5 3 -6 2
-13 -3 -16 -5 -4 -12 -2 -16 4 -4 6 -16 5 -35 -5 -35 -18 -44 -19 -34 -3 5 7
2 9 -7 6 -45 -19 -49 -17 -15 6 28 18 48 23 68 19 35 -7 38 12 3 21 -14 4 -37
0 -53 -7 -38 -20 -99 -30 -107 -18 -3 6 -26 8 -53 5 l-47 -5 40 20 c22 11 75
32 118 47 43 15 76 28 75 30 -4 3 -147 -37 -183 -51 -16 -6 -17 -5 -9 4 14 15
240 90 269 90 11 0 3 -6 -18 -12z m420 -36 c-22 -49 -40 -60 -30 -18 5 22 5
22 -10 2 -13 -15 -16 -16 -17 -5 0 19 46 70 65 70 11 0 9 -11 -8 -49z m-77 23
c0 -2 -7 -4 -15 -4 -8 0 -15 4 -15 10 0 5 7 7 15 4 8 -4 15 -8 15 -10z m-74
-36 c-2 -18 -4 -40 -5 -48 -1 -12 -3 -12 -16 1 -12 12 -13 20 -3 47 16 41 29
41 24 0z m177 -15 c-6 -40 -21 -44 -18 -5 4 52 5 56 14 47 5 -6 7 -25 4 -42z
m-803 17 c0 -6 -27 -10 -62 -10 -45 0 -59 3 -48 10 20 13 110 13 110 0z m550
-35 c-7 -8 -19 -15 -26 -15 -8 0 -14 -4 -14 -10 0 -5 -4 -10 -10 -10 -21 0 -9
29 23 56 27 23 32 24 35 10 2 -9 -2 -23 -8 -31z m277 -2 c-3 -10 -5 -4 -5 12
0 17 2 24 5 18 2 -7 2 -21 0 -30z m-337 23 c0 -2 -8 -10 -17 -17 -16 -13 -17
-12 -4 4 13 16 21 21 21 13z m-400 -30 c0 -2 -7 -7 -16 -10 -8 -3 -12 -2 -9 4
6 10 25 14 25 6z m721 -43 c4 -53 -8 -77 -30 -64 -18 12 -6 81 14 81 8 0 16
-8 16 -17z m-326 -12 c-16 -10 -34 -16 -38 -14 -12 7 25 32 48 33 16 0 14 -4
-10 -19z m125 2 c0 -10 -12 -24 -27 -30 -31 -15 -46 -7 -20 11 9 7 17 18 17
25 0 6 7 11 15 11 8 0 15 -8 15 -17z m70 -9 c0 -8 -6 -14 -12 -14 -7 0 -4 -6
7 -12 41 -23 46 -57 14 -89 -12 -12 -23 -31 -24 -43 -1 -12 -3 -27 -4 -34 0
-7 -6 -10 -11 -7 -7 4 -7 12 0 24 5 10 7 21 4 24 -6 6 -6 77 0 117 3 14 5 33
5 43 1 21 21 13 21 -9z m-161 -8 c-2 -2 -15 -7 -29 -10 l-25 -7 25 15 c21 12
42 14 29 2z m-150 -25 c-41 -27 -146 -50 -133 -30 3 5 12 9 20 9 8 0 14 4 14
9 0 8 95 38 129 40 8 1 -5 -12 -30 -28z m-365 -2 c-3 -5 -10 -7 -15 -3 -5 3
-7 10 -3 15 3 5 10 7 15 3 5 -3 7 -10 3 -15z m456 7 c0 -2 -7 -7 -16 -10 -8
-3 -12 -2 -9 4 6 10 25 14 25 6z m46 -23 c-11 -33 -146 -103 -146 -76 0 16 53
59 90 73 19 7 39 17 44 21 14 12 19 4 12 -18z m80 -28 c-32 -25 -66 -38 -66
-25 0 10 83 60 90 53 3 -3 -8 -15 -24 -28z m-1401 -19 c-23 -16 -65 -30 -65
-22 0 6 59 34 75 35 5 1 1 -5 -10 -13z m1415 -11 c0 -8 -7 -15 -16 -15 -14 0
-14 3 -4 15 7 8 14 15 16 15 2 0 4 -7 4 -15z m-285 -35 c-3 -5 -11 -10 -16
-10 -6 0 -7 5 -4 10 3 6 11 10 16 10 6 0 7 -4 4 -10z m-112 -27 c-7 -2 -29
-16 -49 -29 -20 -14 -39 -23 -41 -20 -3 3 16 19 43 35 26 17 50 28 53 25 3 -4
1 -9 -6 -11z m157 13 c0 -3 -4 -8 -10 -11 -5 -3 -10 -1 -10 4 0 6 5 11 10 11
6 0 10 -2 10 -4z m181 -33 c-12 -20 -14 -14 -5 12 4 9 9 14 11 11 3 -2 0 -13
-6 -23z m-196 -21 c-39 -33 -95 -72 -95 -65 1 10 81 73 95 73 6 0 6 -3 0 -8z
m-705 -12 c-8 -5 -19 -10 -25 -10 -5 0 -3 5 5 10 8 5 20 10 25 10 6 0 3 -5 -5
-10z m965 -19 c-3 -6 -11 -11 -17 -11 -6 0 -6 6 2 15 14 17 26 13 15 -4z m-82
-8 c-7 -3 -13 -9 -13 -15 0 -6 7 -7 16 -4 9 3 12 2 9 -5 -4 -6 -14 -8 -22 -5
-11 4 -13 1 -8 -14 13 -42 -74 -120 -134 -120 -36 0 -38 4 -11 37 11 14 17 30
13 37 -3 6 -3 8 1 5 4 -4 27 15 53 41 35 36 54 47 77 47 17 1 25 -1 19 -4z
m-963 -23 c-14 -10 -27 -16 -30 -13 -6 6 30 33 44 33 6 0 0 -9 -14 -20z m1240
0 c0 -5 -4 -10 -10 -10 -5 0 -10 5 -10 10 0 6 5 10 10 10 6 0 10 -4 10 -10z
m-810 -63 c0 -2 -14 -16 -32 -33 -30 -28 -30 -18 1 18 13 16 31 24 31 15z
m647 -3 c-3 -4 0 -13 8 -21 24 -23 18 -59 -11 -78 -27 -17 -27 -17 -20 6 3 13
8 35 11 49 5 24 5 24 -4 3 -6 -13 -12 -23 -15 -23 -2 0 -2 10 1 23 6 23 22 47
31 47 3 0 3 -3 -1 -6z m120 -286 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4
-12 1 -19z m723 -252 c0 -3 -4 -8 -10 -11 -5 -3 -10 -1 -10 4 0 6 5 11 10 11
6 0 10 -2 10 -4z m-45 -149 c-4 -15 -13 -31 -21 -38 -8 -6 -14 -19 -14 -28 1
-11 8 -7 25 14 14 17 25 25 25 19 0 -13 -39 -49 -61 -57 -24 -8 -43 37 -23 53
8 7 14 18 14 26 0 11 50 44 58 38 2 -1 0 -13 -3 -27z m-493 14 c-10 -6 -7 -10
12 -15 16 -4 26 -13 26 -24 0 -10 4 -22 9 -27 10 -11 3 -65 -8 -65 -14 1 -59
48 -74 78 -14 30 -14 32 1 33 11 0 12 2 4 6 -18 7 -4 23 21 23 16 0 18 -3 9
-9z m428 -30 c0 -6 -4 -13 -10 -16 -5 -3 -10 1 -10 9 0 9 5 16 10 16 6 0 10
-4 10 -9z m-333 -43 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1 -19z
m293 -48 c0 -5 -2 -10 -4 -10 -3 0 -8 5 -11 10 -3 6 -1 10 4 10 6 0 11 -4 11
-10z m56 -27 c-10 -10 -19 5 -10 18 6 11 8 11 12 0 2 -7 1 -15 -2 -18z m-386
-35 c0 -6 7 -20 17 -30 9 -10 11 -18 5 -18 -6 0 -13 6 -15 13 -4 9 -7 9 -15
-1 -6 -9 -9 -4 -10 17 0 17 4 31 9 31 5 0 9 -5 9 -12z m-203 -146 c-8 -9 -18
-27 -21 -39 -4 -13 -13 -23 -19 -23 -7 -1 -1 -10 12 -21 l24 -20 -22 -42 c-13
-23 -34 -63 -48 -89 -13 -26 -31 -52 -39 -59 -8 -6 -13 -26 -13 -43 2 -30 2
-30 6 -3 2 15 9 25 14 22 5 -4 9 3 9 15 0 12 4 19 9 16 5 -4 12 7 16 24 4 17
11 28 16 24 5 -3 9 4 9 16 0 11 7 23 15 26 8 4 13 10 10 14 -3 4 2 13 10 20 8
7 15 20 15 29 0 14 2 14 10 1 14 -22 12 -59 -5 -76 -8 -9 -15 -23 -14 -32 0
-12 3 -11 9 6 10 24 20 29 20 9 0 -21 -80 -138 -90 -132 -6 4 -10 -3 -10 -14
0 -29 -18 -35 -23 -9 -2 13 -5 4 -7 -22 l-3 -45 -7 40 -7 40 -2 -53 c0 -33 -5
-51 -11 -47 -5 3 -10 -3 -10 -14 0 -24 -16 -36 -23 -19 -3 7 -6 1 -6 -14 -1
-42 -11 -60 -26 -48 -9 8 -14 1 -19 -30 -3 -22 -10 -40 -15 -40 -4 0 -22 -30
-40 -67 -17 -38 -47 -95 -66 -128 -19 -33 -32 -67 -29 -76 7 -25 -41 -65 -114
-94 -37 -15 -76 -32 -86 -37 -18 -10 -19 -6 -13 81 7 89 30 186 48 197 5 3 9
22 9 42 0 26 3 33 11 25 8 -8 19 -3 40 18 16 15 29 33 29 39 0 11 76 122 109
158 11 12 20 23 20 25 -5 15 66 125 138 211 18 21 30 42 28 47 -3 4 0 11 6 15
8 4 9 3 5 -4 -5 -8 -1 -10 10 -5 11 4 13 8 6 11 -7 2 -12 8 -12 13 0 11 71
113 82 117 5 2 8 10 8 17 0 8 6 20 13 27 8 7 20 25 28 38 l14 25 3 -23 c2 -13
-3 -31 -11 -40z m3 -62 c0 -5 -5 -10 -11 -10 -5 0 -7 5 -4 10 3 6 8 10 11 10
2 0 4 -4 4 -10z m130 -241 c0 -6 -4 -7 -10 -4 -5 3 -10 11 -10 16 0 6 5 7 10
4 6 -3 10 -11 10 -16z m-100 -8 c0 -5 -6 -12 -12 -14 -8 -3 -6 -6 5 -6 19 -1
24 -36 7 -46 -8 -5 -7 -11 0 -20 7 -8 14 -29 17 -47 8 -51 16 -49 11 4 -3 25
-2 52 3 60 5 8 9 -11 9 -49 0 -36 -4 -63 -10 -63 -5 0 -10 -9 -10 -21 0 -11
-6 -33 -14 -47 -16 -32 -29 -64 -46 -112 -12 -35 -17 -47 -65 -155 -14 -33
-31 -72 -38 -87 -6 -16 -15 -28 -18 -28 -4 0 -16 -22 -28 -49 -17 -41 -31 -55
-76 -81 -30 -17 -61 -30 -68 -27 -6 2 -17 -3 -24 -12 -10 -13 -12 12 -13 126
0 154 2 159 59 183 21 8 28 16 22 24 -5 6 -9 16 -9 21 0 15 31 69 48 82 54 41
55 43 38 37 -10 -3 -18 -1 -18 5 0 6 5 11 10 11 6 0 15 11 20 24 5 13 18 27
29 31 12 3 18 12 15 20 -4 9 3 16 20 21 14 3 31 13 38 22 12 14 10 14 -10 3
-27 -15 -40 -8 -24 12 11 13 76 48 117 62 11 4 15 9 8 12 -6 2 -30 -5 -52 -16
-48 -25 -58 -26 -41 -6 7 8 18 15 25 15 7 0 23 9 36 21 l24 21 -28 -17 c-16
-9 -31 -14 -34 -11 -6 5 56 46 71 46 5 0 7 3 4 6 -4 3 -16 0 -28 -6 -30 -16
-32 -9 -10 28 19 30 40 43 40 23z m27 -23 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13
3 -3 4 -12 1 -19z m560 0 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1 -19z
m-517 -61 c0 -23 -4 -36 -10 -32 -6 3 -9 26 -8 51 2 54 18 37 18 -19z m537 21
c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1 -19z m-53 -65 c1 -7 9 -20 17
-30 26 -30 31 -53 11 -53 -16 -1 -16 -1 0 -11 14 -8 15 -15 8 -35 -6 -14 -8
-29 -5 -34 3 -5 1 -11 -4 -14 -11 -7 -19 12 -16 44 1 14 -5 30 -14 37 -13 10
-13 12 -1 13 10 0 11 3 3 11 -6 6 -9 18 -6 27 5 11 3 13 -7 7 -10 -6 -12 -2
-8 18 3 15 1 28 -5 30 -6 2 -6 13 -2 27 l8 25 9 -25 c6 -14 11 -30 12 -37z
m-452 -5 c-7 -7 -12 -8 -12 -2 0 14 12 26 19 19 2 -3 -1 -11 -7 -17z m-496
-30 c-18 -26 -22 -17 -5 14 6 11 13 17 16 15 3 -3 -2 -16 -11 -29z m514 12
c-8 -5 -19 -10 -25 -10 -5 0 -3 5 5 10 8 5 20 10 25 10 6 0 3 -5 -5 -10z m357
-22 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1 -19z m-387 -8 c0 -5 -7
-10 -16 -10 -8 0 -12 5 -9 10 3 6 10 10 16 10 5 0 9 -4 9 -10z m352 -35 c3
-14 1 -25 -3 -25 -5 0 -9 -7 -9 -17 0 -9 -3 -13 -7 -9 -10 10 -11 56 -1 56 4
0 4 7 1 17 -4 10 -2 14 4 10 6 -4 13 -18 15 -32z m82 -18 c-4 -20 -2 -27 3
-19 7 9 13 -4 21 -39 9 -39 9 -55 1 -66 -7 -7 -9 -16 -6 -20 4 -3 2 -13 -3
-22 -5 -10 -5 -23 -1 -30 6 -9 12 -3 20 20 6 18 13 30 16 27 3 -2 -4 -29 -14
-59 -11 -30 -20 -48 -20 -41 -1 6 -1 14 -1 17 -1 3 -8 24 -17 47 -27 76 -33
120 -21 175 12 56 31 64 22 10z m-425 7 c-12 -15 -11 -16 11 -10 21 6 22 5 8
-4 -10 -6 -18 -22 -18 -35 0 -14 -4 -25 -10 -25 -5 0 -8 14 -6 30 2 17 -1 33
-6 36 -5 3 -1 12 9 19 23 17 30 10 12 -11z m-558 -31 c-12 -20 -14 -14 -5 12
4 9 9 14 11 11 3 -2 0 -13 -6 -23z m519 7 c0 -5 -7 -16 -16 -24 -9 -9 -11 -16
-4 -16 6 0 8 -5 4 -12 -5 -8 -2 -9 10 -5 14 6 15 3 6 -13 -6 -11 -7 -20 -2
-20 5 0 9 -17 10 -38 1 -22 13 -64 27 -96 27 -62 31 -81 14 -70 -8 4 -9 -3 -4
-26 5 -25 4 -31 -5 -25 -8 5 -11 3 -8 -6 3 -8 -1 -36 -8 -64 -8 -27 -14 -62
-14 -76 0 -20 -13 -36 -52 -66 -76 -58 -120 -81 -125 -66 -2 7 -11 9 -19 6 -8
-3 -14 -2 -12 3 1 5 -3 25 -9 44 -6 19 -14 58 -17 87 -6 48 -3 59 45 155 28
57 55 112 60 123 4 11 24 55 43 97 20 43 36 82 36 88 0 5 5 10 10 10 6 0 10 7
10 16 0 8 5 12 10 9 6 -3 10 -10 10 -15z m186 -7 c-10 -10 -19 5 -10 18 6 11
8 11 12 0 2 -7 1 -15 -2 -18z m131 5 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3
4 -12 1 -19z m-61 -20 c4 -6 2 -14 -4 -16 -8 -2 -9 -9 -3 -20 11 -20 9 -72 -1
-72 -5 0 -8 6 -9 13 -6 84 -6 107 1 107 5 0 12 -5 16 -12z m-246 -8 c0 -5 -5
-10 -11 -10 -5 0 -7 5 -4 10 3 6 8 10 11 10 2 0 4 -4 4 -10z m180 -46 c-7 -7
-18 -14 -24 -14 -6 0 -3 7 7 14 9 7 17 18 17 24 0 6 -8 9 -17 6 -15 -5 -15 -3
2 11 19 15 20 15 23 -5 2 -12 -2 -28 -8 -36z m208 13 c-2 -23 -6 -47 -9 -52
-3 -6 -4 -20 -3 -32 0 -13 -3 -23 -7 -23 -12 0 -11 10 2 87 13 72 22 84 17 20z
m-23 23 c3 -5 2 -10 -4 -10 -5 0 -13 5 -16 10 -3 6 -2 10 4 10 5 0 13 -4 16
-10z m-932 -17 c-9 -26 -24 -43 -37 -43 -6 0 -3 7 7 14 9 7 17 21 17 30 0 9 4
16 10 16 5 0 7 -8 3 -17z m657 -50 c0 -7 -4 -13 -10 -13 -5 0 -10 8 -10 18 0
9 -3 28 -5 42 -5 22 -3 21 9 -5 9 -16 15 -36 16 -42z m-35 -13 c-4 -17 -3 -21
3 -11 17 24 22 -5 12 -65 -6 -32 -10 -61 -10 -64 -1 -3 -9 16 -19 41 -15 38
-19 81 -11 129 0 3 5 -2 10 -10 8 -13 10 -13 10 2 0 9 -5 20 -10 23 -6 4 -7
11 -4 17 11 17 26 -33 19 -62z m281 28 c5 -13 7 -25 7 -28 -1 -3 -4 -36 -7
-74 -3 -40 -11 -73 -18 -78 -10 -6 -10 -8 0 -8 17 0 15 -25 -3 -40 -9 -7 -13
-21 -10 -31 5 -13 2 -17 -8 -14 -10 5 -12 25 -9 88 2 45 7 97 12 114 4 18 6
42 3 54 -5 17 -3 20 7 14 10 -6 12 -4 7 8 -3 10 -2 17 3 17 5 0 12 -10 16 -22z
m24 12 c0 -5 -2 -10 -4 -10 -3 0 -8 5 -11 10 -3 6 -1 10 4 10 6 0 11 -4 11
-10z m-135 -108 c4 -4 4 4 0 18 -4 14 -4 22 0 18 9 -9 14 -46 19 -147 2 -42 7
-93 11 -113 4 -24 3 -38 -3 -38 -5 0 -10 6 -9 13 1 36 -4 57 -13 52 -6 -4 -7
1 -3 11 3 10 2 15 -3 12 -5 -4 -10 12 -12 34 -1 22 -9 50 -17 62 -8 11 -15 27
-15 36 0 13 1 13 10 0 6 -9 10 -10 10 -3 0 6 -7 19 -16 27 -14 15 -14 16 1 16
9 0 14 4 10 10 -3 5 -2 22 2 37 l7 28 7 -33 c4 -18 10 -36 14 -40z m205 -1 c0
-10 3 -21 7 -25 4 -4 8 -19 8 -34 0 -15 4 -33 9 -41 5 -8 4 -16 -4 -21 -9 -5
-9 -9 -1 -14 6 -4 11 -12 11 -17 0 -6 -4 -8 -9 -5 -5 4 -7 -3 -4 -15 5 -18 3
-20 -18 -15 -23 7 -23 6 -5 -8 17 -14 17 -19 6 -48 -28 -71 -71 -137 -85 -131
-8 3 -15 -1 -15 -8 0 -7 -3 -10 -6 -6 -13 12 -11 73 3 95 7 12 11 27 8 32 -3
6 -1 10 6 10 6 0 18 8 26 18 14 16 14 16 9 -1 -4 -11 -2 -16 5 -11 7 4 8 20 4
41 -3 19 -2 33 2 30 5 -3 9 11 8 30 0 20 -6 38 -13 41 -6 2 -12 -1 -12 -8 0
-6 5 -8 10 -5 6 3 10 1 10 -4 0 -6 -6 -11 -14 -11 -9 0 -12 -10 -9 -35 2 -25
-2 -41 -17 -57 -15 -16 -20 -18 -20 -7 0 8 6 21 13 28 10 11 9 13 -5 8 -23 -8
-23 7 -1 29 11 11 14 19 7 21 -15 5 -5 53 11 53 7 0 19 33 30 81 17 79 17 80
31 54 8 -15 14 -35 14 -44z m-315 -61 c-10 -11 -21 -18 -23 -15 -3 3 3 12 14
20 27 20 30 18 9 -5z m245 -4 c0 -3 -4 -8 -10 -11 -5 -3 -10 -1 -10 4 0 6 5
11 10 11 6 0 10 -2 10 -4z m-236 -28 c-6 -20 -5 -21 9 -10 14 12 16 11 16 -5
0 -10 3 -35 6 -55 4 -25 3 -38 -4 -38 -6 0 -11 -4 -11 -10 0 -5 7 -10 15 -10
10 0 15 -10 15 -30 0 -30 15 -41 23 -17 3 9 7 9 20 -2 10 -7 17 -10 17 -5 0 4
6 2 12 -4 10 -10 10 -15 -2 -22 -12 -7 -12 -12 -2 -22 16 -16 15 -59 -2 -92
-11 -23 -15 -24 -20 -10 -8 21 2 56 15 49 5 -4 9 5 9 19 0 14 -4 26 -10 26 -5
0 -10 -5 -10 -11 0 -6 -7 -17 -15 -25 -13 -13 -14 -12 -11 13 2 15 1 22 -1 16
-7 -21 -23 -15 -23 8 0 11 -4 17 -10 14 -6 -3 -10 4 -10 16 0 12 -7 27 -16 34
-9 6 -16 24 -16 38 0 15 -5 27 -11 27 -6 0 2 14 19 31 23 24 25 29 9 23 -18
-7 -18 -6 -6 9 9 11 10 17 2 17 -6 0 -11 -4 -11 -10 0 -5 -4 -10 -9 -10 -5 0
-7 10 -4 23 8 37 12 47 18 47 4 0 3 -10 -1 -22z m-24 -18 c0 -5 -5 -10 -11
-10 -5 0 -7 5 -4 10 3 6 8 10 11 10 2 0 4 -4 4 -10z m20 -49 c0 -5 -7 -11 -14
-14 -10 -4 -13 -1 -9 9 6 15 23 19 23 5z m172 -56 c-6 -34 -12 -32 -12 6 0 16
4 28 9 25 4 -3 6 -17 3 -31z m-82 5 c6 -11 8 -20 6 -20 -3 0 -10 9 -16 20 -6
11 -8 20 -6 20 3 0 10 -9 16 -20z m102 -45 c-6 -32 -19 -42 -16 -12 1 12 3 25
3 30 1 4 5 7 10 7 4 0 6 -11 3 -25z m78 -215 c0 -5 -12 -10 -26 -10 -14 0 -23
4 -19 10 3 6 15 10 26 10 10 0 19 -4 19 -10z"/>
<path d="M2275 3400 c-3 -5 -2 -10 4 -10 5 0 13 5 16 10 3 6 2 10 -4 10 -5 0
-13 -4 -16 -10z"/>
<path d="M2658 3315 c2 -14 8 -23 12 -20 10 6 1 45 -10 45 -4 0 -5 -11 -2 -25z"/>
<path d="M2460 3115 c-8 -9 -8 -15 -2 -15 12 0 26 19 19 26 -2 2 -10 -2 -17
-11z"/>
<path d="M2763 1800 c0 -25 2 -35 4 -22 2 12 2 32 0 45 -2 12 -4 2 -4 -23z"/>
<path d="M2732 1735 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z"/>
<path d="M2707 1719 c4 -13 8 -18 11 -10 2 7 -1 18 -6 23 -8 8 -9 4 -5 -13z"/>
<path d="M2636 1673 c-6 -14 -5 -15 5 -6 7 7 10 15 7 18 -3 3 -9 -2 -12 -12z"/>
<path d="M2611 1634 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M2543 1485 c0 -33 2 -45 4 -27 2 18 2 45 0 60 -2 15 -4 0 -4 -33z"/>
<path d="M2511 1515 c1 -19 18 -51 18 -35 0 8 -4 22 -9 30 -5 8 -9 11 -9 5z"/>
<path d="M2515 1427 c-4 -10 -5 -21 -1 -24 10 -10 18 4 13 24 -4 17 -4 17 -12
0z"/>
<path d="M2775 1580 c-16 -11 -25 -20 -19 -20 11 0 64 39 54 40 -3 0 -18 -9
-35 -20z"/>
<path d="M2811 1564 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M2610 1370 c-9 -6 -10 -10 -3 -10 6 0 15 5 18 10 8 12 4 12 -15 0z"/>
<path d="M2819 1323 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17
-17z"/>
<path d="M2789 1269 l-24 -20 28 17 c15 9 27 18 27 20 0 8 -8 4 -31 -17z"/>
<path d="M2850 1235 c-1 -5 -2 -14 -1 -18 1 -4 -5 -15 -14 -23 -16 -16 -20
-34 -7 -34 11 0 34 58 29 72 -3 7 -6 9 -7 3z"/>
<path d="M2870 1221 c0 -6 4 -13 10 -16 6 -3 7 1 4 9 -7 18 -14 21 -14 7z"/>
<path d="M2750 1140 c-9 -6 -10 -10 -3 -10 6 0 15 5 18 10 8 12 4 12 -15 0z"/>
<path d="M2920 1313 c0 -4 5 -15 10 -23 8 -13 10 -13 10 2 0 9 -4 20 -10 23
-5 3 -10 3 -10 -2z"/>
<path d="M3206 1293 c-6 -14 -5 -15 5 -6 7 7 10 15 7 18 -3 3 -9 -2 -12 -12z"/>
<path d="M3293 1275 c-12 -38 -5 -51 15 -29 8 8 9 14 2 14 -6 0 -8 10 -4 25 9
38 -1 30 -13 -10z"/>
<path d="M3270 1215 c0 -2 10 -10 23 -16 20 -11 21 -11 8 4 -13 16 -31 23 -31
12z"/>
<path d="M5160 4695 c0 -5 5 -17 10 -25 5 -8 10 -10 10 -5 0 6 -5 17 -10 25
-5 8 -10 11 -10 5z"/>
<path d="M2635 4680 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0
-7 -4 -4 -10z"/>
<path d="M5732 4580 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z"/>
<path d="M868 4410 c2 -45 5 -80 7 -78 1 1 5 31 8 66 2 35 7 65 10 68 13 13
133 11 155 -3 13 -8 32 -11 44 -7 19 6 20 3 14 -26 -5 -30 -4 -31 11 -19 11
10 14 10 9 2 -4 -8 -2 -13 6 -14 59 -3 116 -13 138 -23 14 -7 39 -16 55 -19
17 -4 -6 11 -50 32 -44 22 -76 40 -72 40 10 1 176 -63 215 -83 45 -23 132 -56
132 -50 0 3 -20 14 -45 25 -24 11 -47 24 -50 29 -3 6 -13 10 -22 10 -9 0 -41
12 -72 26 -31 14 -65 30 -76 34 -11 4 -31 12 -45 18 -14 5 -34 14 -45 18 -14
6 5 10 60 14 l80 6 -70 3 c-38 1 -144 4 -235 7 l-165 6 3 -82z m285 33 c-7 -2
-19 -2 -25 0 -7 3 -2 5 12 5 14 0 19 -2 13 -5z"/>
<path d="M2076 4471 c-4 -5 -2 -12 3 -15 5 -4 12 -2 15 3 4 5 2 12 -3 15 -5 4
-12 2 -15 -3z"/>
<path d="M3388 4453 c34 -2 90 -2 125 0 34 2 6 3 -63 3 -69 0 -97 -1 -62 -3z"/>
<path d="M4208 4453 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z"/>
<path d="M4615 4450 c-96 -5 -86 -6 80 -7 108 -1 179 2 165 7 -28 9 -83 9
-245 0z"/>
<path d="M4937 4448 c-6 -4 43 -7 110 -5 76 1 113 5 100 10 -27 10 -192 6
-210 -5z"/>
<path d="M5200 4450 c0 -5 16 -10 35 -10 19 0 35 -4 35 -10 0 -5 -6 -10 -14
-10 -7 0 -19 -6 -26 -14 -6 -8 -18 -12 -26 -9 -8 3 -20 0 -27 -5 -7 -6 -55
-27 -107 -46 -52 -19 -90 -35 -84 -35 13 -1 101 27 131 41 13 6 26 8 29 5 4
-4 -6 -11 -21 -17 -38 -15 -24 -25 16 -11 18 6 40 11 51 11 29 0 100 51 93 68
-3 9 0 12 9 9 8 -3 20 1 26 9 7 8 19 14 28 14 8 0 13 2 10 5 -10 11 -158 15
-158 5z m48 -75 c-3 -3 -25 -7 -49 -10 -33 -3 -38 -2 -19 4 28 10 76 14 68 6z"/>
<path d="M5485 4449 c-16 -6 -17 -8 -3 -8 12 -1 18 -13 22 -43 12 -89 13 -91
15 -21 0 39 -2 74 -6 76 -5 3 -17 1 -28 -4z"/>
<path d="M3160 4390 c-8 -5 -12 -11 -9 -14 3 -3 14 1 25 9 21 16 8 20 -16 5z"/>
<path d="M1088 4373 c12 -2 30 -2 40 0 9 3 -1 5 -23 4 -22 0 -30 -2 -17 -4z"/>
<path d="M3315 4370 c3 -5 11 -10 16 -10 6 0 7 5 4 10 -3 6 -11 10 -16 10 -6
0 -7 -4 -4 -10z"/>
<path d="M3202 4339 c-7 -10 -8 -19 -3 -19 13 0 33 28 23 33 -4 3 -13 -3 -20
-14z"/>
<path d="M3275 4350 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
-8 -4 -11 -10z"/>
<path d="M1159 4341 c11 -7 10 -10 -5 -14 -18 -4 -18 -5 0 -6 12 -1 16 -6 12
-16 -3 -10 1 -15 14 -15 10 0 36 -16 57 -35 42 -39 133 -95 154 -95 8 0 -18
21 -56 47 -39 25 -92 68 -119 95 -27 26 -54 48 -60 48 -6 0 -5 -4 3 -9z"/>
<path d="M1360 4344 c0 -6 65 -34 77 -34 9 1 -58 39 -69 40 -5 0 -8 -2 -8 -6z"/>
<path d="M3275 4320 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
-8 -4 -11 -10z"/>
<path d="M3378 4323 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z"/>
<path d="M871 4304 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M4991 4294 c-93 -35 -175 -74 -168 -80 3 -3 13 -1 24 4 28 16 83 39
148 62 33 13 64 26 70 31 14 14 -3 10 -74 -17z"/>
<path d="M1450 4304 c0 -7 126 -64 142 -64 7 1 -20 16 -61 35 -78 37 -81 38
-81 29z"/>
<path d="M3412 4298 c-18 -18 -14 -33 4 -17 14 11 18 10 30 -6 8 -11 13 -24
11 -30 -2 -5 2 -16 10 -23 17 -18 17 -36 -1 -29 -10 4 -13 1 -9 -9 3 -7 11
-14 19 -14 17 0 37 -37 30 -56 -3 -8 -2 -13 3 -10 17 11 29 -82 15 -118 -3 -9
-12 -16 -20 -16 -8 0 -14 -7 -14 -15 0 -8 5 -15 10 -15 6 0 10 -23 10 -50 0
-45 -4 -53 -39 -85 -36 -33 -39 -40 -45 -106 -4 -39 -5 -73 -2 -76 6 -6 42 27
49 47 4 10 6 7 6 -8 1 -19 5 -21 27 -16 25 6 26 5 20 -20 -5 -20 -3 -26 9 -26
9 0 13 -5 10 -10 -4 -6 -1 -18 6 -26 15 -18 25 -129 11 -120 -6 3 -4 -14 3
-38 8 -24 10 -48 7 -52 -4 -4 -2 -4 4 -1 16 9 25 -18 11 -32 -8 -8 -8 -11 1
-11 7 0 12 -7 12 -15 0 -8 -6 -15 -12 -15 -10 0 -9 -3 1 -9 7 -5 14 -20 14
-35 0 -19 5 -26 19 -26 10 0 16 -4 13 -10 -3 -5 -2 -10 3 -10 10 0 32 -66 32
-96 0 -16 -2 -16 -14 -4 -8 8 -17 28 -20 44 -8 44 -16 44 -16 0 0 -44 7 -57
33 -67 10 -4 15 -11 12 -17 -8 -13 -35 -13 -35 0 0 6 -6 10 -14 10 -18 0 -27
-36 -16 -63 9 -21 9 -21 7 3 -1 14 3 25 8 26 6 0 9 -7 7 -18 -2 -13 5 -20 27
-24 17 -4 31 -11 31 -15 0 -5 14 -8 30 -7 22 2 30 7 30 21 0 14 5 17 18 13 11
-4 8 1 -8 15 -14 12 -24 25 -23 30 2 5 -2 20 -7 35 -9 21 -7 29 7 40 17 13 17
14 0 8 -13 -5 -16 -2 -11 10 3 9 11 16 18 16 12 0 36 -45 36 -67 0 -7 4 -13 8
-13 9 0 57 -91 54 -102 -4 -16 19 -7 25 10 4 9 11 20 17 24 7 5 5 8 -5 8 -25
0 -55 40 -79 105 -12 33 -32 74 -46 92 -14 19 -24 45 -24 65 0 19 -5 39 -12
46 -9 9 -9 12 -1 12 7 0 15 -4 18 -10 11 -18 26 -10 20 10 -3 11 -4 20 0 20 6
0 24 -44 30 -72 2 -9 9 -26 15 -38 6 -12 11 -38 10 -58 -3 -55 -1 -64 10 -57
6 3 10 -1 10 -9 0 -9 4 -16 8 -16 17 0 21 13 11 31 -6 12 -6 26 -1 35 7 13 12
11 27 -13 10 -15 24 -34 31 -40 7 -7 27 -39 45 -70 18 -31 34 -52 37 -45 2 7
10 12 17 12 8 0 -7 31 -36 74 -27 42 -46 81 -43 89 3 8 1 18 -5 22 -7 4 -8 -1
-4 -12 6 -16 5 -16 -10 -4 -25 20 -21 0 8 -39 26 -35 28 -40 21 -40 -9 0 -96
119 -96 131 0 6 9 0 21 -15 17 -22 24 -24 41 -15 17 9 23 8 31 -6 7 -13 11
-14 17 -5 8 12 70 -57 125 -138 32 -49 84 -96 94 -86 3 4 1 9 -5 11 -7 2 -9
14 -6 25 4 17 2 20 -12 15 -10 -4 -15 -3 -11 3 8 13 -65 117 -137 196 -29 32
-45 55 -35 51 10 -4 30 -7 44 -7 18 0 24 -4 19 -15 -3 -10 1 -15 11 -15 9 0
28 -10 42 -22 14 -13 20 -16 14 -8 -25 28 -12 33 16 6 16 -16 28 -33 27 -40
-2 -6 -1 -8 1 -3 8 13 25 7 20 -7 -3 -7 0 -13 8 -12 12 1 150 -124 157 -143 2
-5 32 -11 68 -15 50 -4 58 -3 35 4 -16 6 -39 12 -50 14 -11 3 -21 5 -22 7 -2
1 -7 2 -12 3 -13 2 -261 238 -261 249 0 4 4 6 8 3 4 -2 14 0 22 5 12 8 12 9
-3 7 -11 -2 -26 12 -44 41 l-26 45 29 -20 c16 -12 36 -24 44 -27 30 -14 90
-53 117 -77 15 -13 33 -25 38 -25 24 -2 65 -30 61 -41 -3 -8 -1 -16 4 -19 4
-3 10 0 12 6 2 6 15 2 31 -11 15 -12 24 -26 21 -32 -5 -7 -2 -8 5 -4 17 11 92
-43 89 -63 -2 -10 3 -15 14 -13 9 1 25 -9 36 -23 29 -37 36 -30 9 9 -20 31
-120 109 -257 202 -25 17 -63 43 -85 59 l-40 27 40 -5 c22 -4 34 -4 28 -1 -7
3 -13 12 -13 20 0 8 -7 14 -17 14 -9 0 -13 3 -10 6 9 9 69 -16 91 -38 10 -11
25 -17 34 -13 9 3 14 3 10 0 -3 -3 42 -31 100 -61 58 -30 120 -65 137 -78 46
-34 64 -41 32 -12 -15 14 -23 26 -17 26 18 0 71 -43 60 -50 -5 -3 1 -12 13
-19 20 -13 21 -13 8 2 -21 27 1 19 38 -13 18 -15 30 -33 26 -39 -3 -6 -11 -8
-17 -5 -7 4 -8 2 -4 -4 9 -14 16 -15 36 -2 12 7 11 13 -10 35 -39 41 -186 138
-288 189 -51 25 -92 52 -92 59 0 7 -19 20 -42 29 -39 15 -40 17 -15 17 29 1
269 -76 346 -111 25 -11 77 -36 114 -55 38 -19 66 -30 63 -24 -4 5 -40 26 -81
47 -41 21 -73 39 -71 41 6 7 255 -120 278 -141 15 -14 22 -16 31 -6 9 9 8 15
-7 25 -10 8 -15 18 -12 24 5 7 -3 9 -21 5 -21 -4 -61 11 -167 65 -77 39 -146
71 -153 71 -7 0 -13 7 -13 15 0 9 -9 15 -25 15 -14 0 -34 9 -45 20 -21 21 -46
28 -34 8 4 -7 3 -8 -5 -4 -6 4 -9 11 -6 16 3 5 -5 11 -17 13 -15 3 -11 5 13 6
19 0 58 -5 85 -13 27 -8 81 -19 119 -26 78 -12 336 -77 420 -105 14 -4 12 -2
-5 9 -28 17 -23 20 13 7 60 -23 127 -39 127 -30 0 5 -7 9 -15 9 -8 0 -15 4
-15 9 0 6 10 8 22 6 12 -3 25 -1 28 3 3 4 16 13 30 20 l25 11 -25 -5 c-43 -9
-93 -9 -129 0 -27 7 -32 6 -25 -5 6 -11 5 -12 -7 -3 -8 7 -30 15 -49 19 -19 4
-46 12 -60 18 -26 11 -36 14 -58 16 -7 0 -11 5 -8 10 3 4 -7 8 -22 8 -37 0
-172 35 -172 45 0 4 7 8 16 8 8 0 12 5 8 11 -5 8 8 10 42 5 27 -3 108 -8 179
-11 72 -3 166 -7 210 -9 l80 -5 -85 14 -85 14 80 -5 c44 -2 91 -6 105 -8 14
-1 50 -6 80 -11 30 -4 61 -12 68 -17 8 -8 12 -6 12 5 0 20 -64 34 -205 43
-106 6 -160 11 -160 14 0 1 -15 3 -33 5 -25 2 -32 -1 -28 -11 4 -11 -8 -14
-62 -13 -44 0 -60 3 -47 8 11 5 26 6 33 4 6 -3 12 0 12 6 0 6 -18 11 -42 11
-24 1 -130 5 -236 10 -169 8 -196 11 -208 27 -13 16 -14 17 -8 1 4 -12 1 -18
-10 -18 -9 0 -16 8 -16 18 0 11 -17 28 -42 42 -36 20 -39 24 -22 31 19 7 19 8
-1 10 -11 1 -27 2 -35 1 -13 -1 -158 45 -215 68 -11 5 -33 12 -50 16 -28 7
-28 8 -7 12 15 2 22 9 18 18 -4 11 7 14 58 14 34 0 87 -4 117 -10 175 -31 242
-41 247 -36 3 3 2 6 -2 6 -4 0 -60 9 -124 20 -64 11 -132 23 -151 26 -18 3
-31 7 -29 10 8 7 280 -35 336 -52 61 -18 68 -27 15 -19 -27 4 -32 3 -18 -4 30
-13 91 -25 82 -15 -4 4 3 14 15 21 13 7 16 13 8 12 -8 -1 -26 -1 -39 0 -20 1
-21 3 -11 16 11 13 5 15 -41 15 -30 0 -92 7 -139 15 -47 8 -102 15 -122 15
-20 0 -39 4 -43 10 -3 5 -12 7 -20 3 -8 -3 -15 -1 -15 5 0 6 -15 9 -32 8 -37
-3 -96 18 -80 28 18 11 212 36 287 37 49 0 64 3 50 9 -19 8 -19 9 2 9 12 1 24
-5 26 -11 4 -10 6 -10 6 0 1 9 35 12 135 12 140 0 194 -8 146 -21 -23 -6 -22
-7 8 -8 20 -1 31 3 27 9 -3 6 -2 10 4 10 5 0 11 -5 13 -12 2 -6 9 -8 16 -4 6
3 9 13 5 21 -4 13 -45 15 -255 15 -162 0 -256 -4 -270 -11 -11 -6 -18 -7 -14
-1 3 6 -22 8 -67 5 -116 -6 -56 22 133 63 36 8 119 27 185 44 174 44 321 70
390 70 54 0 61 3 72 25 11 24 10 25 -20 25 -29 0 -30 1 -11 15 25 19 11 19
-27 -1 -17 -8 -36 -13 -42 -11 -7 2 -20 0 -30 -5 -20 -10 -140 -38 -317 -73
-69 -13 -141 -29 -160 -34 -58 -16 -110 -22 -110 -11 0 5 20 21 45 36 25 14
44 28 41 31 -2 2 -12 0 -23 -6 -10 -5 -52 -24 -93 -41 l-75 -30 50 5 50 5 -35
-17 c-19 -10 -42 -26 -52 -35 -10 -10 -25 -18 -33 -18 -8 0 -15 -6 -15 -14 0
-9 -7 -12 -18 -9 -13 3 -18 -2 -19 -18 -3 -31 -25 -52 -81 -73 -58 -23 -87
-55 -44 -49 l27 3 -25 -11 c-14 -6 -33 -15 -42 -20 -10 -5 -35 -9 -55 -10 -33
0 -35 -1 -15 -9 33 -14 27 -17 -28 -16 -51 2 -63 -6 -33 -22 12 -7 4 -9 -30
-7 -57 4 -87 13 -87 27 0 5 7 8 15 4 8 -3 15 -1 15 3 0 5 -6 11 -12 14 -19 6
43 35 89 43 25 4 40 11 40 20 0 24 -47 28 -122 9 -40 -10 -78 -15 -86 -12 -9
4 -7 6 6 6 22 1 217 92 211 99 -8 8 -140 -42 -158 -60 -10 -10 -28 -18 -40
-18 -23 1 -23 1 2 16 17 10 18 13 5 9 -41 -14 -105 -18 -105 -6 0 6 -6 11 -13
11 -10 0 -14 17 -15 60 0 33 -3 64 -6 69 -4 5 0 11 6 13 9 3 13 31 12 99 -2
123 -5 139 -27 151 -11 5 -16 16 -13 24 3 9 1 12 -4 9 -6 -3 -10 2 -10 12 -1
15 -52 83 -63 83 -2 0 -8 -5 -15 -12z m1473 -256 c-12 -11 -109 -32 -138 -31
-18 1 -16 3 8 10 17 5 53 13 80 18 28 5 52 9 55 10 2 0 0 -3 -5 -7z m68 1 c-7
-2 -19 -2 -25 0 -7 3 -2 5 12 5 14 0 19 -2 13 -5z m-236 -39 c-3 -3 -12 -4
-19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z m-122 -29 c-49 -12 -108 -25 -130 -28
-69 -11 -1 13 95 33 119 25 143 22 35 -5z m-1148 -267 c-3 -8 -6 -5 -6 6 -1
11 2 17 5 13 3 -3 4 -12 1 -19z m746 15 c-7 -2 -19 -2 -25 0 -7 3 -2 5 12 5
14 0 19 -2 13 -5z m-746 -55 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1
-19z m148 12 c3 -5 1 -10 -4 -10 -6 0 -11 5 -11 10 0 6 2 10 4 10 3 0 8 -4 11
-10z m45 -28 c0 -13 -12 -22 -22 -16 -10 6 -1 24 13 24 5 0 9 -4 9 -8z m365
-2 c3 -5 19 -10 34 -10 16 0 44 -6 62 -14 19 -7 45 -17 59 -20 l25 -7 -25 15
c-14 9 -22 15 -19 16 18 1 183 -61 198 -74 10 -9 24 -16 32 -16 12 0 12 -2 0
-9 -11 -7 -9 -10 8 -16 28 -9 22 -29 -10 -32 -53 -3 -292 36 -308 51 -10 9
-20 16 -24 16 -5 0 -75 45 -85 55 -2 2 7 2 20 -1 12 -3 3 4 -21 14 -48 21 -42
38 10 27 22 -5 29 -4 25 4 -4 6 -3 11 3 11 5 0 13 -4 16 -10z m96 -8 c-7 -2
-18 1 -23 6 -8 8 -4 9 13 5 13 -4 18 -8 10 -11z m-501 -17 c13 -14 21 -28 17
-31 -8 -8 -56 28 -57 44 0 19 14 14 40 -13z m115 -15 c5 -8 11 -8 20 0 10 8
21 8 41 -3 l28 -14 -29 0 c-16 0 -32 -8 -38 -18 -8 -15 -11 -13 -25 14 -10 22
-20 30 -31 26 -8 -3 -12 -3 -8 1 4 4 -1 13 -11 21 -12 10 -9 10 15 -1 17 -9
35 -21 38 -26z m-115 -36 c12 -31 12 -34 2 -34 -4 0 -13 14 -19 30 -6 17 -7
30 -2 30 5 0 14 -12 19 -26z m56 2 c20 -21 17 -28 -6 -16 -11 6 -20 15 -20 20
0 14 9 12 26 -4z m290 -21 c-3 -8 2 -16 10 -19 10 -4 13 -1 9 7 -4 6 -1 5 7
-3 7 -8 20 -16 28 -18 26 -6 81 -53 90 -77 5 -12 18 -25 29 -28 32 -8 25 -23
-9 -21 -59 4 -130 26 -123 37 3 7 2 9 -2 4 -13 -11 -64 43 -58 62 4 14 3 14
-6 1 -6 -8 -11 -12 -11 -7 0 18 -2 23 -16 30 -12 7 -12 10 1 23 8 8 20 12 27
8 8 -5 9 -2 5 9 -4 11 -2 14 9 10 8 -3 13 -11 10 -18z m-59 9 c-3 -3 -12 -4
-19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z m-174 -21 c-7 -2 -19 -2 -25 0 -7 3
-2 5 12 5 14 0 19 -2 13 -5z m-68 -13 c3 -5 1 -10 -4 -10 -6 0 -11 5 -11 10 0
6 2 10 4 10 3 0 8 -4 11 -10z m163 -72 c7 -16 8 -28 2 -28 -12 0 -35 36 -45
69 -7 23 -7 24 12 5 10 -10 24 -31 31 -46z m156 -57 c-5 -5 -75 73 -87 98 -5
9 14 -9 42 -39 27 -30 48 -56 45 -59z m-69 52 c27 -32 33 -44 20 -39 -11 5
-28 9 -37 10 -9 0 -15 3 -13 6 2 3 -3 19 -11 35 -8 17 -12 22 -9 12 3 -10 1
-15 -5 -12 -14 9 -12 35 3 35 6 -1 30 -22 52 -47z m-286 7 c7 -23 7 -29 -1
-24 -7 5 -9 0 -6 -13 3 -12 2 -26 -2 -33 -5 -8 -10 -2 -14 16 -4 16 -3 30 2
32 5 1 7 14 4 27 -6 35 4 31 17 -5z m606 20 c3 -5 27 -10 53 -11 26 0 63 -7
82 -14 35 -14 35 -14 5 -11 -16 1 -37 6 -45 9 -8 4 -46 8 -84 9 -45 2 -71 7
-74 16 -5 15 54 17 63 2z m618 -37 c-7 -2 -19 -2 -25 0 -7 3 -2 5 12 5 14 0
19 -2 13 -5z m-608 -21 c-3 -3 -11 0 -18 7 -9 10 -8 11 6 5 10 -3 15 -9 12
-12z m240 8 c13 -6 5 -7 -25 -3 -25 3 -47 7 -49 9 -9 8 55 2 74 -6z m142 4
c-3 -3 -12 -4 -19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z m-802 -44 c10 -11 13
-20 7 -20 -6 0 -16 9 -22 20 -6 11 -9 20 -7 20 2 0 12 -9 22 -20z m110 -1 c11
-17 -1 -21 -15 -4 -8 9 -8 15 -2 15 6 0 14 -5 17 -11z m-192 -19 c3 -11 1 -18
-4 -14 -5 3 -9 12 -9 20 0 20 7 17 13 -6z m502 10 c3 -5 1 -10 -4 -10 -6 0
-11 5 -11 10 0 6 2 10 4 10 3 0 8 -4 11 -10z m458 3 c-7 -2 -19 -2 -25 0 -7 3
-2 5 12 5 14 0 19 -2 13 -5z m-548 -13 c3 -6 -1 -7 -9 -4 -18 7 -21 14 -7 14
6 0 13 -4 16 -10z m705 -16 c30 -8 60 -16 65 -17 6 -1 24 -8 40 -14 17 -7 50
-17 74 -23 23 -6 41 -14 38 -17 -3 -2 -50 9 -104 25 -54 17 -133 38 -175 47
-43 9 -78 19 -78 22 0 7 74 -6 140 -23z m-1200 -47 c0 -16 -18 3 -23 25 -6 22
-5 22 8 4 8 -11 15 -24 15 -29z m374 27 c3 -8 2 -12 -4 -9 -6 3 -10 10 -10 16
0 14 7 11 14 -7z m-334 -30 c0 -8 -4 -12 -10 -9 -5 3 -10 13 -10 21 0 8 5 12
10 9 6 -3 10 -13 10 -21z m170 -16 c0 -12 3 -19 6 -15 10 9 36 -21 29 -33 -4
-7 -16 0 -30 16 -13 15 -22 31 -19 36 3 4 0 8 -6 8 -6 0 -8 5 -5 10 10 16 25
2 25 -22z m155 2 c3 -5 2 -10 -4 -10 -5 0 -13 5 -16 10 -3 6 -2 10 4 10 5 0
13 -4 16 -10z m-323 -35 c7 -32 -8 -54 -14 -23 -3 12 -9 31 -13 41 -5 13 -3
16 8 11 8 -3 17 -16 19 -29z m948 25 c8 -5 11 -10 5 -10 -5 0 -17 5 -25 10 -8
5 -10 10 -5 10 6 0 17 -5 25 -10z m550 -10 c13 -9 13 -10 0 -10 -8 0 -22 5
-30 10 -13 9 -13 10 0 10 8 0 22 -5 30 -10z m-730 -30 c8 -5 11 -10 5 -10 -5
0 -17 5 -25 10 -8 5 -10 10 -5 10 6 0 17 -5 25 -10z m-369 -162 c10 -20 24
-38 31 -41 9 -4 9 -6 1 -6 -6 -1 -26 18 -45 41 -35 46 -57 72 -100 122 -15 17
-28 36 -27 41 0 16 120 -117 140 -157z m476 96 c-5 -5 -77 35 -77 42 0 3 18
-5 41 -16 23 -12 39 -24 36 -26z m-592 -125 c9 -16 12 -29 7 -29 -7 0 -20 20
-40 64 -11 23 20 -9 33 -35z"/>
<path d="M4220 3584 c56 -25 116 -46 105 -36 -13 11 -115 52 -129 52 -6 -1 5
-8 24 -16z"/>
<path d="M5180 4299 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M4916 4291 c-4 -5 3 -7 14 -4 23 6 26 13 6 13 -8 0 -17 -4 -20 -9z"/>
<path d="M1560 4285 c0 -3 27 -17 59 -31 40 -18 58 -30 54 -40 -5 -13 39 -31
66 -25 7 1 10 -2 6 -8 -4 -7 0 -8 11 -4 13 5 15 3 9 -7 -5 -8 -4 -11 3 -6 6 3
17 0 24 -8 8 -7 25 -17 38 -21 14 -4 41 -18 60 -30 19 -12 41 -20 49 -17 8 3
11 0 7 -6 -7 -11 21 -25 35 -16 5 3 8 0 7 -8 -2 -7 2 -12 9 -11 6 1 17 -4 23
-12 13 -16 35 -21 26 -6 -3 5 6 11 21 14 20 4 55 -10 133 -50 113 -60 119 -62
255 -98 121 -31 153 -44 136 -54 -9 -6 -3 -10 17 -14 16 -3 36 -13 44 -21 19
-19 88 -32 88 -17 0 6 -9 11 -19 11 -11 0 -32 11 -48 24 -15 13 -46 29 -68 37
-22 7 -44 16 -50 20 -5 4 -26 10 -45 14 -49 10 -129 34 -167 51 -17 8 -35 14
-39 14 -16 0 -144 64 -144 72 0 4 -8 8 -17 8 -10 0 -27 7 -37 15 -11 8 -27 15
-35 15 -14 0 -14 -1 -1 -10 13 -8 12 -10 -2 -10 -10 0 -18 5 -18 10 0 6 -9 10
-20 10 -11 0 -118 50 -239 110 -214 108 -231 115 -231 105z m165 -85 c3 -5 -4
-6 -15 -3 -11 3 -22 9 -25 13 -3 5 4 6 15 3 11 -3 22 -9 25 -13z"/>
<path d="M4868 4273 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
<path d="M5168 4273 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
<path d="M869 4135 c-3 -66 -8 -129 -11 -140 -3 -11 -3 -14 -1 -7 3 6 8 12 13
12 5 0 11 -6 13 -12 8 -18 6 114 -2 197 -7 69 -7 68 -12 -50z"/>
<path d="M3260 4239 c0 -5 9 -9 19 -9 11 0 22 4 25 9 3 5 -6 9 -19 9 -14 0
-25 -4 -25 -9z"/>
<path d="M4725 4204 c-63 -31 -80 -43 -55 -38 19 3 42 12 51 20 8 8 18 14 21
14 3 0 26 11 49 25 70 40 32 28 -66 -21z"/>
<path d="M4785 4200 c-3 -5 -2 -10 4 -10 5 0 13 5 16 10 3 6 2 10 -4 10 -5 0
-13 -4 -16 -10z"/>
<path d="M4745 4180 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/>
<path d="M4603 4150 c-13 -5 -23 -12 -23 -15 0 -9 16 -5 36 10 22 16 19 18
-13 5z"/>
<path d="M5023 4143 c9 -2 23 -2 30 0 6 3 -1 5 -18 5 -16 0 -22 -2 -12 -5z"/>
<path d="M4980 4124 c-13 -15 -6 -17 24 -5 17 6 17 8 3 14 -8 3 -21 -1 -27 -9z"/>
<path d="M5511 4114 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M4543 4113 c9 -2 14 -9 11 -15 -4 -7 -2 -8 5 -4 15 10 4 26 -17 25
-15 -1 -15 -2 1 -6z"/>
<path d="M4930 4100 c-9 -6 -10 -10 -3 -10 6 0 15 5 18 10 8 12 4 12 -15 0z"/>
<path d="M3430 4087 c0 -8 7 -18 16 -21 11 -4 14 -1 12 11 -4 21 -28 30 -28
10z"/>
<path d="M4471 4081 c-31 -24 -11 -37 26 -17 16 8 22 15 15 16 -7 0 -10 5 -7
10 10 16 -8 11 -34 -9z m29 -5 c0 -2 -7 -7 -16 -10 -8 -3 -12 -2 -9 4 6 10 25
14 25 6z"/>
<path d="M3493 4073 c-18 -7 -16 -33 2 -33 8 0 15 9 15 20 0 11 -1 20 -2 19
-2 0 -9 -3 -15 -6z"/>
<path d="M5506 4062 c-7 -11 1 -263 8 -270 3 -3 6 58 6 136 0 135 -2 153 -14
134z"/>
<path d="M1958 4034 c24 -14 46 -24 49 -21 5 6 -69 47 -84 47 -4 0 11 -12 35
-26z"/>
<path d="M4408 4049 c-10 -5 -18 -15 -18 -21 0 -9 3 -9 12 0 7 7 20 12 31 12
10 0 15 5 12 10 -8 12 -15 12 -37 -1z"/>
<path d="M3430 4038 c0 -4 -7 -5 -15 -2 -8 4 -15 1 -15 -6 0 -6 9 -14 20 -17
11 -3 20 -9 20 -15 0 -5 8 -4 19 2 25 13 17 49 -10 47 -10 -1 -19 -5 -19 -9z"/>
<path d="M3077 4033 c-12 -12 -7 -22 8 -17 8 4 15 10 15 15 0 11 -14 12 -23 2z"/>
<path d="M2082 4014 c10 -10 20 -15 24 -12 3 4 -5 12 -18 18 -22 12 -22 12 -6
-6z"/>
<path d="M878 3925 c2 -19 6 -35 8 -35 2 0 4 16 4 35 0 19 -4 35 -8 35 -4 0
-6 -16 -4 -35z"/>
<path d="M4895 3950 c-3 -5 -2 -10 4 -10 5 0 13 5 16 10 3 6 2 10 -4 10 -5 0
-13 -4 -16 -10z"/>
<path d="M4790 3900 c-9 -6 -10 -10 -3 -10 6 0 15 5 18 10 8 12 4 12 -15 0z"/>
<path d="M3960 3874 l-35 -14 35 6 c19 4 44 10 55 15 l20 8 -20 0 c-11 0 -36
-7 -55 -15z"/>
<path d="M1881 3836 c2 -2 27 -6 54 -9 34 -4 42 -3 25 3 -25 8 -87 13 -79 6z"/>
<path d="M796 3798 c-3 -13 -5 -34 -5 -48 1 -15 5 -7 10 19 9 47 6 68 -5 29z"/>
<path d="M3920 3800 c-8 -5 -12 -11 -10 -14 3 -2 14 2 24 10 22 15 10 19 -14
4z"/>
<path d="M3865 3770 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
-8 -4 -11 -10z"/>
<path d="M3950 3739 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M3387 3683 c-13 -28 -13 -33 -3 -18 9 11 20 25 26 32 12 15 13 23 2
23 -5 0 -16 -17 -25 -37z"/>
<path d="M772 3669 c-5 -19 -6 -38 -3 -44 3 -5 9 8 12 30 9 50 3 58 -9 14z"/>
<path d="M2980 3690 c0 -6 7 -10 15 -10 8 0 15 2 15 4 0 2 -7 6 -15 10 -8 3
-15 1 -15 -4z"/>
<path d="M4110 3685 c0 -8 5 -15 10 -15 6 0 10 7 10 15 0 8 -4 15 -10 15 -5 0
-10 -7 -10 -15z"/>
<path d="M2880 3634 c0 -8 5 -12 10 -9 6 4 8 11 5 16 -9 14 -15 11 -15 -7z"/>
<path d="M4640 3639 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M2970 3620 c0 -5 9 -14 20 -20 22 -12 27 -1 8 18 -14 14 -28 16 -28
2z"/>
<path d="M500 3601 c0 -25 6 -27 13 -6 4 8 2 17 -3 20 -6 4 -10 -3 -10 -14z"/>
<path d="M1815 3610 c3 -5 11 -10 16 -10 6 0 7 5 4 10 -3 6 -11 10 -16 10 -6
0 -7 -4 -4 -10z"/>
<path d="M3452 3595 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z"/>
<path d="M761 3594 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M2278 3603 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
<path d="M3030 3576 c0 -9 5 -16 10 -16 6 0 10 4 10 9 0 6 -4 13 -10 16 -5 3
-10 -1 -10 -9z"/>
<path d="M3416 3565 c-22 -34 -20 -43 4 -15 18 21 26 40 16 40 -2 0 -11 -11
-20 -25z"/>
<path d="M4418 3573 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z"/>
<path d="M3534 3529 c3 -17 6 -40 6 -51 0 -10 4 -17 9 -13 6 3 6 18 1 36 -5
16 -6 37 -3 45 3 8 0 14 -7 14 -9 0 -11 -10 -6 -31z"/>
<path d="M746 3488 c-9 -84 -13 -299 -6 -338 3 -19 8 60 11 175 3 116 4 212 3
213 -1 2 -5 -20 -8 -50z"/>
<path d="M5611 3504 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M3520 3455 c-1 -12 -4 -15 -7 -7 -4 10 -8 9 -19 -5 -16 -21 -18 -62
-4 -83 8 -13 10 -12 10 3 0 19 19 58 34 68 4 4 3 15 -3 25 -10 18 -10 18 -11
-1z"/>
<path d="M4995 3450 c-3 -5 1 -10 10 -10 9 0 13 5 10 10 -3 6 -8 10 -10 10 -2
0 -7 -4 -10 -10z"/>
<path d="M2940 3429 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M3447 3397 c-3 -9 -1 -19 4 -23 7 -4 8 -21 4 -41 -4 -24 -3 -33 4
-28 12 7 13 60 2 89 -7 17 -8 18 -14 3z"/>
<path d="M1358 3393 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
<path d="M5323 3393 c9 -2 23 -2 30 0 6 3 -1 5 -18 5 -16 0 -22 -2 -12 -5z"/>
<path d="M1285 3375 c-40 -14 -40 -14 -5 -8 19 3 42 9 50 14 22 12 1 9 -45 -6z"/>
<path d="M5403 3383 c9 -2 15 -9 12 -14 -4 -5 -2 -9 3 -9 6 0 12 7 16 15 4 11
-2 15 -22 14 -17 -1 -20 -3 -9 -6z"/>
<path d="M2957 3353 c-4 -3 -8 -22 -8 -42 l-2 -36 -6 35 c-4 23 -9 31 -14 23
-6 -10 18 -83 28 -83 1 0 3 21 4 47 2 72 11 64 11 -11 0 -42 4 -65 10 -61 6 4
10 -6 10 -24 0 -16 5 -33 10 -36 14 -9 5 62 -11 81 -6 8 -9 27 -6 43 10 46 -6
85 -26 64z"/>
<path d="M3421 3324 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M1108 3323 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z"/>
<path d="M3382 3270 c0 -27 3 -50 9 -50 5 0 9 12 9 28 0 15 3 37 6 50 5 15 2
22 -8 22 -11 0 -15 -14 -16 -50z"/>
<path d="M3750 3301 c0 -6 4 -13 10 -16 6 -3 7 1 4 9 -7 18 -14 21 -14 7z"/>
<path d="M3451 3224 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M5150 3230 c20 -13 33 -13 25 0 -3 6 -14 10 -23 10 -15 0 -15 -2 -2
-10z"/>
<path d="M1277 3219 c7 -7 15 -10 18 -7 3 3 -2 9 -12 12 -14 6 -15 5 -6 -5z"/>
<path d="M2250 3199 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M5090 3189 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M3020 3179 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M5000 3180 l-45 -7 45 -1 c25 -1 52 3 60 8 18 11 13 11 -60 0z"/>
<path d="M2377 3173 c-3 -5 1 -14 9 -20 14 -14 30 0 19 17 -7 12 -22 13 -28 3z"/>
<path d="M3311 3154 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M3027 3136 c3 -14 16 -34 29 -47 l23 -22 -3 29 c-6 45 -17 64 -36 64
-13 0 -16 -6 -13 -24z"/>
<path d="M4841 3156 c2 -2 15 -9 29 -15 24 -11 24 -11 6 3 -16 13 -49 24 -35
12z"/>
<path d="M2687 3143 c-4 -3 -7 -11 -7 -17 0 -6 5 -5 12 2 6 6 9 14 7 17 -3 3
-9 2 -12 -2z"/>
<path d="M747 3025 c3 -49 9 -94 13 -98 8 -9 3 55 -10 138 -6 36 -7 25 -3 -40z"/>
<path d="M4180 3102 c0 -5 7 -15 15 -22 8 -7 15 -9 15 -6 0 4 -7 14 -15 22 -8
9 -15 11 -15 6z"/>
<path d="M4718 3103 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z"/>
<path d="M3269 3080 c-2 -14 -10 -24 -17 -22 -6 1 -12 -2 -12 -8 0 -15 30 -12
36 4 3 8 4 23 2 33 -4 14 -6 12 -9 -7z"/>
<path d="M3577 3093 c-4 -3 -7 -11 -7 -17 0 -6 5 -5 12 2 6 6 9 14 7 17 -3 3
-9 2 -12 -2z"/>
<path d="M3097 3070 c3 -11 7 -20 9 -20 2 0 4 9 4 20 0 11 -4 20 -9 20 -5 0
-7 -9 -4 -20z"/>
<path d="M1568 3073 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
<path d="M1628 3073 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z"/>
<path d="M4257 3020 c12 -19 27 -36 34 -39 7 -2 2 10 -10 27 -31 44 -50 53
-24 12z"/>
<path d="M4553 3035 c0 -8 5 -11 10 -8 6 3 26 6 46 5 28 0 32 1 16 8 -33 14
-72 12 -72 -5z"/>
<path d="M3130 3016 c0 -8 5 -18 10 -21 6 -3 10 1 10 9 0 8 -4 18 -10 21 -5 3
-10 -1 -10 -9z"/>
<path d="M4160 3010 c0 -9 39 -25 46 -18 2 3 -7 10 -21 16 -14 6 -25 7 -25 2z"/>
<path d="M4003 2993 c-18 -7 -16 -23 2 -23 8 0 15 -4 15 -10 0 -5 7 -10 15
-10 18 0 20 16 3 23 -7 3 -4 6 7 6 17 1 17 3 5 11 -16 10 -28 11 -47 3z"/>
<path d="M2890 2975 c0 -8 4 -15 10 -15 5 0 7 7 4 15 -4 8 -8 15 -10 15 -2 0
-4 -7 -4 -15z"/>
<path d="M4227 2979 c7 -7 15 -10 18 -7 3 3 -2 9 -12 12 -14 6 -15 5 -6 -5z"/>
<path d="M1708 2973 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
<path d="M4465 2970 c3 -5 14 -10 23 -10 15 0 15 2 2 10 -20 13 -33 13 -25 0z"/>
<path d="M2865 2960 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0
-7 -4 -4 -10z"/>
<path d="M2600 2889 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M5510 2660 c-3 -102 -3 -230 0 -285 l5 -100 5 140 c3 77 3 205 0 285
l-6 145 -4 -185z"/>
<path d="M791 2744 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M881 2625 c-8 -48 -9 -156 -4 -230 10 -129 19 -40 12 115 -3 74 -7
126 -8 115z"/>
<path d="M2710 2489 c0 -16 29 -41 40 -34 6 4 5 13 -3 25 -13 21 -37 27 -37 9z"/>
<path d="M1177 2344 c-4 -4 -7 -20 -7 -36 0 -23 4 -29 18 -26 11 3 16 12 14
28 -3 28 -15 44 -25 34z"/>
<path d="M1150 2270 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 -11 10 -2 0
-4 -4 -4 -10z"/>
<path d="M1290 2229 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M2665 2170 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0
-7 -4 -4 -10z"/>
<path d="M1050 2057 c0 -11 70 -137 76 -137 10 0 1 19 -36 80 -22 36 -40 61
-40 57z"/>
<path d="M1141 1894 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M3768 1883 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z"/>
<path d="M1160 1865 c0 -8 4 -15 10 -15 5 0 7 7 4 15 -4 8 -8 15 -10 15 -2 0
-4 -7 -4 -15z"/>
<path d="M3690 1860 c6 -11 13 -20 16 -20 2 0 0 9 -6 20 -6 11 -13 20 -16 20
-2 0 0 -9 6 -20z"/>
<path d="M1190 1822 c6 -13 14 -21 18 -18 3 4 -2 14 -12 24 -18 16 -18 16 -6
-6z"/>
<path d="M3541 1824 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M3650 1790 c0 -5 10 -10 23 -10 18 0 19 2 7 10 -19 13 -30 13 -30 0z"/>
<path d="M3659 1758 c-12 -8 -11 -9 2 -5 9 3 22 0 28 -6 6 -6 13 -8 17 -5 12
12 -31 27 -47 16z"/>
<path d="M3710 1707 c8 -7 20 -19 27 -26 9 -11 11 -10 9 3 -2 18 -23 36 -41
36 -5 -1 -3 -6 5 -13z"/>
<path d="M3760 1630 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 -11 10 -2 0
-4 -4 -4 -10z"/>
<path d="M3532 1580 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z"/>
<path d="M3791 1574 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M3876 1473 c-12 -12 -6 -31 12 -38 14 -5 15 -4 5 6 -18 18 -16 30 2
23 8 -4 15 -4 15 -1 0 8 -28 16 -34 10z"/>
<path d="M3861 1374 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M3935 1360 c3 -5 11 -10 16 -10 6 0 7 5 4 10 -3 6 -11 10 -16 10 -6
0 -7 -4 -4 -10z"/>
<path d="M4110 1300 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 -11 10 -2 0
-4 -4 -4 -10z"/>
<path d="M3700 1236 c0 -2 7 -7 16 -10 8 -3 12 -2 9 4 -6 10 -25 14 -25 6z"/>
<path d="M4130 1211 c0 -6 4 -13 10 -16 6 -3 7 1 4 9 -7 18 -14 21 -14 7z"/>
<path d="M4133 1160 c14 -63 18 -138 9 -144 -22 -13 -211 91 -237 130 -9 14
-19 23 -22 20 -3 -2 -5 -56 -4 -119 2 -119 -3 -151 -23 -140 -6 4 -5 0 2 -9 8
-9 20 -14 28 -11 19 7 21 36 8 172 -5 50 -3 62 8 59 7 -3 13 -12 12 -20 -1 -8
9 -15 24 -16 13 -1 39 -11 57 -22 18 -11 38 -20 45 -20 6 0 24 -8 38 -19 15
-10 39 -21 54 -25 25 -6 27 -4 30 31 4 46 -11 134 -25 148 -6 7 -8 2 -4 -15z"/>
<path d="M1561 1144 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M4173 1153 c9 -2 23 -2 30 0 6 3 -1 5 -18 5 -16 0 -22 -2 -12 -5z"/>
<path d="M872 1125 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z"/>
<path d="M866 1053 c-3 -21 -6 -82 -6 -135 l0 -98 53 2 52 1 -45 6 -45 6 4
128 c3 128 -1 159 -13 90z"/>
<path d="M3624 1067 c-3 -10 -2 -23 4 -29 6 -6 8 -17 5 -25 -3 -7 -2 -13 3
-11 15 5 74 -24 68 -33 -3 -5 3 -9 13 -9 10 0 35 -11 55 -25 20 -14 45 -25 55
-25 10 0 1 9 -22 21 -61 32 -144 99 -160 128 -12 23 -14 24 -21 8z"/>
<path d="M3655 1070 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
-8 -4 -11 -10z"/>
<path d="M3400 951 c0 -42 -4 -39 125 -81 21 -7 46 -19 53 -27 19 -19 45 -13
48 12 11 68 15 139 8 132 -4 -4 -9 -20 -11 -35 -4 -41 -25 -102 -35 -102 -4 0
-8 5 -8 10 0 6 -5 10 -11 10 -6 0 -24 9 -39 20 -20 14 -29 17 -32 8 -3 -8 -19
-1 -48 20 -27 20 -37 32 -27 32 10 0 25 -9 34 -20 10 -11 22 -20 27 -20 6 0
-8 18 -29 40 -21 22 -43 40 -47 40 -4 0 -8 -17 -8 -39z"/>
<path d="M3355 939 c-4 -6 -5 -13 -2 -16 7 -7 27 6 27 18 0 12 -17 12 -25 -2z"/>
<path d="M5500 883 l0 -62 -57 -6 c-98 -10 -101 -13 -15 -12 l84 2 4 53 c2 29
0 61 -6 70 -7 13 -9 0 -10 -45z"/>
<path d="M3374 808 c-3 -4 15 -8 40 -8 25 0 47 4 50 8 2 4 -16 8 -40 8 -25 0
-48 -4 -50 -8z"/>
<path d="M3488 813 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 61 KiB

View File

@ -1,19 +0,0 @@
{
"name": "Nexus Cognitron",
"short_name": "Nexus Cognitron",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

View File

@ -0,0 +1,4 @@
{
// https://v3.nuxtjs.org/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

View File

@ -26,25 +26,24 @@ py3_image(
requirement("aiodns"),
requirement("aiohttp"),
requirement("aiohttp_socks"),
requirement("aioipfs"),
requirement("cchardet"),
requirement("orjson"),
requirement("prometheus-client"),
requirement("psycopg2-binary"),
requirement("python-socks"),
requirement("tenacity"),
requirement("uvloop"),
"//idm/api/proto:idm_proto_py",
"//idm/api/proto:proto_py",
requirement("aiogrobid"),
"//library/aiogrpctools",
"//library/aioipfs",
requirement("aiokit"),
"//library/aiopostgres",
"//library/configurator",
"//library/telegram",
"//nexus/hub/proto:hub_grpc_py",
"//nexus/hub/proto:hub_proto_py",
"//nexus/hub/proto:grpc_py",
"//nexus/hub/proto:proto_py",
"//nexus/meta_api/aioclient",
"//nexus/models/proto:models_proto_py",
"//nexus/models/proto:proto_py",
"//nexus/pylon",
"//nexus/views/telegram",
],
@ -54,7 +53,7 @@ container_push(
name = "push-latest",
format = "Docker",
image = ":image",
registry = "registry.example.com",
registry = "registry.infra.svc.cluster.local",
repository = "nexus-hub",
tag = "latest",
)
@ -63,7 +62,7 @@ container_push(
name = "push-testing",
format = "Docker",
image = ":image",
registry = "registry.example.com",
registry = "registry.infra.svc.cluster.local",
repository = "nexus-hub",
tag = "testing",
)

View File

@ -6,11 +6,11 @@ py_library(
visibility = ["//visibility:public"],
deps = [
requirement("grpcio"),
"//idm/api/proto:idm_proto_py",
"//idm/api/proto:proto_py",
requirement("aiogrpcclient"),
requirement("aiokit"),
"//nexus/hub/proto:hub_grpc_py",
"//nexus/hub/proto:hub_proto_py",
"//nexus/models/proto:models_proto_py",
"//nexus/hub/proto:grpc_py",
"//nexus/hub/proto:proto_py",
"//nexus/models/proto:proto_py",
],
)

View File

@ -26,13 +26,15 @@ class HubGrpcClient(BaseGrpcClient):
self,
typed_document_pb: TypedDocumentPb,
chat: ChatPb,
request_id: Optional[str],
session_id: Optional[str],
bot_name: str,
request_id: Optional[str] = None,
session_id: Optional[str] = None,
) -> StartDeliveryResponsePb:
return await self.stubs['delivery'].start_delivery(
StartDeliveryRequestPb(
typed_document=typed_document_pb,
chat=chat,
bot_name=bot_name,
),
metadata=(('request-id', request_id), ('session-id', session_id))
)
@ -42,6 +44,7 @@ class HubGrpcClient(BaseGrpcClient):
telegram_document: bytes,
telegram_file_id: str,
chat: ChatPb,
bot_name: str,
request_id: Optional[str] = None,
session_id: Optional[str] = None,
) -> SubmitResponsePb:
@ -50,6 +53,7 @@ class HubGrpcClient(BaseGrpcClient):
telegram_document=telegram_document,
telegram_file_id=telegram_file_id,
chat=chat,
bot_name=bot_name,
),
metadata=(('request-id', request_id), ('session-id', session_id))
)

View File

@ -23,7 +23,7 @@ grpc:
# Listen address
address: 0.0.0.0
# Listen port
port: 9090
port: 82
ipfs:
address: localhost
port: 4001
@ -36,19 +36,20 @@ pylon:
# 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:
# 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:
# Telethon database for keeping cache
database:
session_id: /tmp/nexus-hub.db
# Frequency of updating downloading progress
progress_throttle_seconds: 5
# Send files using stored telegram_file_id
should_use_telegram_file_id: true
bots:
_policy: merge
libgen_scihub_1_bot:
# Telegram App Hash from https://my.telegram.org/
app_hash:
# Telegram App ID from https://my.telegram.org/
app_id: 00000
# Internal bot name used in logging
bot_name: nexus-hub
bot_token:
# Telethon database for keeping cache
database:
session_id: /tmp/nexus-hub.db
# Frequency of updating downloading progress
progress_throttle_seconds: 5
# Send files using stored telegram_file_id
should_use_telegram_file_id: true

View File

@ -19,37 +19,37 @@ class GrpcServer(AioGrpcServer):
self.pool_holder = None
if config['database']['enabled']:
self.pool_holder = AioPostgresPoolHolder(
dsn=f'dbname={config["database"]["database"]} '
conninfo=f'dbname={config["database"]["database"]} '
f'user={config["database"]["username"]} '
f'password={config["database"]["password"]} '
f'host={config["database"]["host"]}',
timeout=30,
pool_recycle=60,
maxsize=4,
)
self.waits.append(self.pool_holder)
self.telegram_client = BaseTelegramClient(
app_id=config['telegram']['app_id'],
app_hash=config['telegram']['app_hash'],
bot_token=config['telegram']['bot_token'],
database=config['telegram'].get('database'),
mtproxy=config['telegram'].get('mtproxy'),
)
self.starts.append(self.telegram_client)
self.telegram_clients = {}
for telegram_bot in config['telegram']['bots']:
telegram_bot_config = config['telegram']['bots'][telegram_bot]
telegram_client = BaseTelegramClient(
app_id=telegram_bot_config['app_id'],
app_hash=telegram_bot_config['app_hash'],
bot_token=telegram_bot_config['bot_token'],
database=telegram_bot_config.get('database'),
mtproxy=telegram_bot_config.get('mtproxy'),
)
self.telegram_clients[telegram_bot] = telegram_client
self.starts.extend(self.telegram_clients.values())
self.delivery_service = DeliveryService(
server=self.server,
service_name=config['application']['service_name'],
bot_external_name=config['telegram']['bot_external_name'],
ipfs_config=config['ipfs'],
is_sharience_enabled=config['application']['is_sharience_enabled'],
maintenance_picture_url=config['application'].get('maintenance_picture_url', ''),
pool_holder=self.pool_holder,
pylon_config=config['pylon'],
should_store_hashes=config['application']['should_store_hashes'],
should_use_telegram_file_id=config['telegram']['should_use_telegram_file_id'],
telegram_client=self.telegram_client,
telegram_clients=self.telegram_clients,
telegram_bot_configs=config['telegram']['bots'],
)
self.starts.append(self.delivery_service)
@ -57,11 +57,10 @@ class GrpcServer(AioGrpcServer):
self.submitter_service = SubmitterService(
server=self.server,
service_name=config['application']['service_name'],
bot_external_name=config['telegram']['bot_external_name'],
grobid_config=config['grobid'],
ipfs_config=config['ipfs'],
meta_api_config=config['meta_api'],
telegram_client=self.telegram_client,
telegram_clients=self.telegram_clients,
)
self.starts.append(self.submitter_service)

View File

@ -4,24 +4,24 @@ load("@rules_proto//proto:defs.bzl", "proto_library")
package(default_visibility = ["//visibility:public"])
proto_library(
name = "hub_proto",
name = "proto",
srcs = glob([
"*.proto",
]),
deps = [
"//idm/api/proto:idm_proto",
"//nexus/models/proto:models_proto",
"//idm/api/proto",
"//nexus/models/proto",
"@com_google_protobuf//:wrappers_proto",
],
)
py_proto_library(
name = "hub_proto_py",
deps = [":hub_proto"],
name = "proto_py",
deps = [":proto"],
)
py_grpc_library(
name = "hub_grpc_py",
srcs = [":hub_proto"],
deps = [":hub_proto_py"],
name = "grpc_py",
srcs = [":proto"],
deps = [":proto_py"],
)

View File

@ -7,6 +7,7 @@ import "idm/api/proto/chat_manager_service.proto";
message StartDeliveryRequest {
nexus.models.proto.TypedDocument typed_document = 1;
idm.api.proto.Chat chat = 2;
string bot_name = 3;
}
message StartDeliveryResponse {

View File

@ -1,184 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: nexus/hub/proto/delivery_service.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()
from idm.api.proto import \
chat_manager_service_pb2 as \
idm_dot_api_dot_proto_dot_chat__manager__service__pb2
from nexus.models.proto import \
typed_document_pb2 as nexus_dot_models_dot_proto_dot_typed__document__pb2
DESCRIPTOR = _descriptor.FileDescriptor(
name='nexus/hub/proto/delivery_service.proto',
package='nexus.hub.proto',
syntax='proto3',
serialized_options=None,
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/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_api_dot_proto_dot_chat__manager__service__pb2.DESCRIPTOR,])
_STARTDELIVERYRESPONSE_STATUS = _descriptor.EnumDescriptor(
name='Status',
full_name='nexus.hub.proto.StartDeliveryResponse.Status',
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name='OK', index=0, number=0,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='TOO_MANY_DOWNLOADS', index=1, number=1,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='ALREADY_DOWNLOADING', index=2, number=2,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
],
containing_type=None,
serialized_options=None,
serialized_start=349,
serialized_end=414,
)
_sym_db.RegisterEnumDescriptor(_STARTDELIVERYRESPONSE_STATUS)
_STARTDELIVERYREQUEST = _descriptor.Descriptor(
name='StartDeliveryRequest',
full_name='nexus.hub.proto.StartDeliveryRequest',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='typed_document', full_name='nexus.hub.proto.StartDeliveryRequest.typed_document', index=0,
number=1, 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),
_descriptor.FieldDescriptor(
name='chat', full_name='nexus.hub.proto.StartDeliveryRequest.chat', index=1,
number=2, 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=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=142,
serialized_end=258,
)
_STARTDELIVERYRESPONSE = _descriptor.Descriptor(
name='StartDeliveryResponse',
full_name='nexus.hub.proto.StartDeliveryResponse',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='status', full_name='nexus.hub.proto.StartDeliveryResponse.status', index=0,
number=1, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=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=[
_STARTDELIVERYRESPONSE_STATUS,
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=261,
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['chat'].message_type = idm_dot_api_dot_proto_dot_chat__manager__service__pb2._CHAT
_STARTDELIVERYRESPONSE.fields_by_name['status'].enum_type = _STARTDELIVERYRESPONSE_STATUS
_STARTDELIVERYRESPONSE_STATUS.containing_type = _STARTDELIVERYRESPONSE
DESCRIPTOR.message_types_by_name['StartDeliveryRequest'] = _STARTDELIVERYREQUEST
DESCRIPTOR.message_types_by_name['StartDeliveryResponse'] = _STARTDELIVERYRESPONSE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
StartDeliveryRequest = _reflection.GeneratedProtocolMessageType('StartDeliveryRequest', (_message.Message,), {
'DESCRIPTOR' : _STARTDELIVERYREQUEST,
'__module__' : 'nexus.hub.proto.delivery_service_pb2'
# @@protoc_insertion_point(class_scope:nexus.hub.proto.StartDeliveryRequest)
})
_sym_db.RegisterMessage(StartDeliveryRequest)
StartDeliveryResponse = _reflection.GeneratedProtocolMessageType('StartDeliveryResponse', (_message.Message,), {
'DESCRIPTOR' : _STARTDELIVERYRESPONSE,
'__module__' : 'nexus.hub.proto.delivery_service_pb2'
# @@protoc_insertion_point(class_scope:nexus.hub.proto.StartDeliveryResponse)
})
_sym_db.RegisterMessage(StartDeliveryResponse)
_DELIVERY = _descriptor.ServiceDescriptor(
name='Delivery',
full_name='nexus.hub.proto.Delivery',
file=DESCRIPTOR,
index=0,
serialized_options=None,
create_key=_descriptor._internal_create_key,
serialized_start=416,
serialized_end=525,
methods=[
_descriptor.MethodDescriptor(
name='start_delivery',
full_name='nexus.hub.proto.Delivery.start_delivery',
index=0,
containing_service=None,
input_type=_STARTDELIVERYREQUEST,
output_type=_STARTDELIVERYRESPONSE,
serialized_options=None,
create_key=_descriptor._internal_create_key,
),
])
_sym_db.RegisterServiceDescriptor(_DELIVERY)
DESCRIPTOR.services_by_name['Delivery'] = _DELIVERY
# @@protoc_insertion_point(module_scope)

View File

@ -1,66 +0,0 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
from nexus.hub.proto import \
delivery_service_pb2 as nexus_dot_hub_dot_proto_dot_delivery__service__pb2
class DeliveryStub(object):
"""Missing associated documentation comment in .proto file."""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.start_delivery = channel.unary_unary(
'/nexus.hub.proto.Delivery/start_delivery',
request_serializer=nexus_dot_hub_dot_proto_dot_delivery__service__pb2.StartDeliveryRequest.SerializeToString,
response_deserializer=nexus_dot_hub_dot_proto_dot_delivery__service__pb2.StartDeliveryResponse.FromString,
)
class DeliveryServicer(object):
"""Missing associated documentation comment in .proto file."""
def start_delivery(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_DeliveryServicer_to_server(servicer, server):
rpc_method_handlers = {
'start_delivery': grpc.unary_unary_rpc_method_handler(
servicer.start_delivery,
request_deserializer=nexus_dot_hub_dot_proto_dot_delivery__service__pb2.StartDeliveryRequest.FromString,
response_serializer=nexus_dot_hub_dot_proto_dot_delivery__service__pb2.StartDeliveryResponse.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'nexus.hub.proto.Delivery', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
# This class is part of an EXPERIMENTAL API.
class Delivery(object):
"""Missing associated documentation comment in .proto file."""
@staticmethod
def start_delivery(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/nexus.hub.proto.Delivery/start_delivery',
nexus_dot_hub_dot_proto_dot_delivery__service__pb2.StartDeliveryRequest.SerializeToString,
nexus_dot_hub_dot_proto_dot_delivery__service__pb2.StartDeliveryResponse.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

View File

@ -7,6 +7,7 @@ message SubmitRequest {
bytes telegram_document = 1;
string telegram_file_id = 2;
idm.api.proto.Chat chat = 3;
string bot_name = 4;
}
message SubmitResponse { }

View File

@ -1,148 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: nexus/hub/proto/submitter_service.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()
from idm.api.proto import \
chat_manager_service_pb2 as \
idm_dot_api_dot_proto_dot_chat__manager__service__pb2
DESCRIPTOR = _descriptor.FileDescriptor(
name='nexus/hub/proto/submitter_service.proto',
package='nexus.hub.proto',
syntax='proto3',
serialized_options=None,
create_key=_descriptor._internal_create_key,
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_api_dot_proto_dot_chat__manager__service__pb2.DESCRIPTOR,])
_SUBMITREQUEST = _descriptor.Descriptor(
name='SubmitRequest',
full_name='nexus.hub.proto.SubmitRequest',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='telegram_document', full_name='nexus.hub.proto.SubmitRequest.telegram_document', index=0,
number=1, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
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='telegram_file_id', full_name='nexus.hub.proto.SubmitRequest.telegram_file_id', index=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,
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=102,
serialized_end=205,
)
_SUBMITRESPONSE = _descriptor.Descriptor(
name='SubmitResponse',
full_name='nexus.hub.proto.SubmitResponse',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=207,
serialized_end=223,
)
_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['SubmitResponse'] = _SUBMITRESPONSE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
SubmitRequest = _reflection.GeneratedProtocolMessageType('SubmitRequest', (_message.Message,), {
'DESCRIPTOR' : _SUBMITREQUEST,
'__module__' : 'nexus.hub.proto.submitter_service_pb2'
# @@protoc_insertion_point(class_scope:nexus.hub.proto.SubmitRequest)
})
_sym_db.RegisterMessage(SubmitRequest)
SubmitResponse = _reflection.GeneratedProtocolMessageType('SubmitResponse', (_message.Message,), {
'DESCRIPTOR' : _SUBMITRESPONSE,
'__module__' : 'nexus.hub.proto.submitter_service_pb2'
# @@protoc_insertion_point(class_scope:nexus.hub.proto.SubmitResponse)
})
_sym_db.RegisterMessage(SubmitResponse)
_SUBMITTER = _descriptor.ServiceDescriptor(
name='Submitter',
full_name='nexus.hub.proto.Submitter',
file=DESCRIPTOR,
index=0,
serialized_options=None,
create_key=_descriptor._internal_create_key,
serialized_start=225,
serialized_end=313,
methods=[
_descriptor.MethodDescriptor(
name='submit',
full_name='nexus.hub.proto.Submitter.submit',
index=0,
containing_service=None,
input_type=_SUBMITREQUEST,
output_type=_SUBMITRESPONSE,
serialized_options=None,
create_key=_descriptor._internal_create_key,
),
])
_sym_db.RegisterServiceDescriptor(_SUBMITTER)
DESCRIPTOR.services_by_name['Submitter'] = _SUBMITTER
# @@protoc_insertion_point(module_scope)

View File

@ -1,67 +0,0 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
from nexus.hub.proto import \
submitter_service_pb2 as \
nexus_dot_hub_dot_proto_dot_submitter__service__pb2
class SubmitterStub(object):
"""Missing associated documentation comment in .proto file."""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.submit = channel.unary_unary(
'/nexus.hub.proto.Submitter/submit',
request_serializer=nexus_dot_hub_dot_proto_dot_submitter__service__pb2.SubmitRequest.SerializeToString,
response_deserializer=nexus_dot_hub_dot_proto_dot_submitter__service__pb2.SubmitResponse.FromString,
)
class SubmitterServicer(object):
"""Missing associated documentation comment in .proto file."""
def submit(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_SubmitterServicer_to_server(servicer, server):
rpc_method_handlers = {
'submit': grpc.unary_unary_rpc_method_handler(
servicer.submit,
request_deserializer=nexus_dot_hub_dot_proto_dot_submitter__service__pb2.SubmitRequest.FromString,
response_serializer=nexus_dot_hub_dot_proto_dot_submitter__service__pb2.SubmitResponse.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'nexus.hub.proto.Submitter', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
# This class is part of an EXPERIMENTAL API.
class Submitter(object):
"""Missing associated documentation comment in .proto file."""
@staticmethod
def submit(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/nexus.hub.proto.Submitter/submit',
nexus_dot_hub_dot_proto_dot_submitter__service__pb2.SubmitRequest.SerializeToString,
nexus_dot_hub_dot_proto_dot_submitter__service__pb2.SubmitResponse.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

Some files were not shown because too many files have changed in this diff Show More