async def handler(self, event: events.ChatAction, request_context: RequestContext): schema, 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) has_found_old_widget = old_message_id == self.application.user_manager.last_widget.get(request_context.chat.chat_id) try: message_id, link_preview = await self.process_widgeting( has_found_old_widget=has_found_old_widget, old_message_id=old_message_id, request_context=request_context ) document_view = await self.resolve_document( schema, document_id, position, session_id, request_context, ) try: back_command = await self.compose_back_command( session_id=session_id, message_id=message_id, page=page, ) except MessageHasBeenDeletedError: return await event.respond( t('REPLY_MESSAGE_HAS_BEEN_DELETED', language=request_context.chat.language), ) 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'], position=position, back_command=back_command, ) actions = [ self.application.telegram_client.edit_message( request_context.chat.chat_id, message_id, view, buttons=buttons, link_preview=link_preview, ), event.delete(), ] if not has_found_old_widget: actions.append( self.application.telegram_client.delete_messages( request_context.chat.chat_id, [old_message_id], ) ) return await asyncio.gather(*actions) except MessageIdInvalidError: await event.reply(t("VIEWS_CANNOT_BE_SHARED", language=request_context.chat.language))
async def handler(self, event: events.ChatAction, request_context: RequestContext): query = event.pattern_match.group(1) if query: request_context.statbox( action='show', mode='copyright', query=query, ) await self.application.telegram_client.forward_messages( self.application.config['telegram'] ['copyright_infringement_account'], event.message, ) await event.reply( t( 'COPYRIGHT_INFRINGEMENT_ACCEPTED', language=request_context.chat.language, )) else: request_context.statbox(action='show', mode='copyright') await event.reply( t( 'COPYRIGHT_DESCRIPTION', language=request_context.chat.language, ))
async def handler(self, event: events.ChatAction, request_context: RequestContext): raw_query = event.pattern_match.group(1) query = None request_context.statbox(action='start', mode='start') try: query = decode_deep_query(raw_query) except DecodeDeepQueryError as e: request_context.error_log(e, mode='start', raw_query=raw_query) if query: request_context.statbox(action='query', mode='start', query=query) request_message = await self.application.telegram_client.send_message(event.chat, query) prefetch_message = await request_message.reply( t("SEARCHING", language=request_context.chat.language), ) self.application.user_manager.last_widget[request_context.chat.chat_id] = prefetch_message.id await asyncio.gather( event.delete(), self.do_search(event, request_context, prefetch_message, query=query, is_shortpath_enabled=True), ) else: request_context.statbox(action='show', mode='start') await event.reply(t('HELP', language=request_context.chat.language))
async def handler(self, event: events.ChatAction, request_context: RequestContext): request_context.statbox(action='show', mode='help') if event.is_group or event.is_channel: await event.reply(t('HELP_FOR_GROUPS', language=request_context.chat.language), buttons=Button.clear()) else: await event.reply(t('HELP', language=request_context.chat.language), buttons=Button.clear())
async def handler(self, event: events.ChatAction, request_context: RequestContext): session_id, message_id, page = self.parse_pattern(event) request_context.add_default_fields(mode='search_paging', session_id=session_id) start_time = time.time() message = await event.get_message() if not message: return await event.answer() reply_message = await message.get_reply_message() try: if not reply_message: raise MessageHasBeenDeletedError() query = self.preprocess_query(reply_message.raw_text) search_widget = await SearchWidget.create( application=self.application, chat=request_context.chat, session_id=session_id, message_id=message_id, request_id=request_context.request_id, query=query, page=page, ) except MessageHasBeenDeletedError: return await event.respond( t('REPLY_MESSAGE_HAS_BEEN_DELETED', language=request_context.chat.language), ) except AioRpcError as e: if e.code() == StatusCode.INVALID_ARGUMENT or e.code( ) == StatusCode.CANCELLED: request_context.error_log(e) return await event.answer( t('MAINTENANCE_WO_PIC', language=request_context.chat.language), ) raise e action = 'documents_found' if len(search_widget.scored_documents) == 0: action = 'documents_not_found' request_context.statbox( action=action, duration=time.time() - start_time, query=f'page:{page} query:{query}', ) serp, buttons = await search_widget.render() return await asyncio.gather( event.answer(), message.edit(serp, buttons=buttons, link_preview=False))
async def handler(self, event: events.ChatAction, request_context: RequestContext): session_id = self.generate_session_id() request_context.add_default_fields(session_id=session_id) request_context.statbox(action='show', mode='submit') if event.document.mime_type != 'application/pdf': request_context.statbox(action='unknown_file_format') request_context.error_log( UnknownFileFormatError(format=event.document.mime_type)) return await asyncio.gather( event.reply( t('UNKNOWN_FILE_FORMAT_ERROR', language=request_context.chat.language), buttons=[close_button()], ), event.delete(), ) return await asyncio.gather( self.application.hub_client.submit( telegram_document=bytes(event.document), telegram_file_id=event.file.id, chat=request_context.chat, request_id=request_context.request_id, session_id=session_id, ), event.delete(), )
async def handler(self, event: events.ChatAction, request_context: RequestContext): schema, 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, ) document_operation_pb = DocumentOperationPb(vote=VotePb( document_id=document_id, value=vote_value, voter_id=request_context.chat.chat_id, ), ) logging.getLogger('operation').info( msg=MessageToDict(document_operation_pb), ) message = await event.get_message() # ToDo: Generalize nexus.views.telegram.common.remove_button and use it here return await asyncio.gather( self.application.telegram_client.edit_message( request_context.chat.chat_id, message.id, message.text, buttons=None, ), event.answer(t('TANKS_BRUH')), )
async def handler(self, event: events.ChatAction, request_context: RequestContext): search_prefix, query, is_group_mode = self.parse_pattern(event) request_context.add_default_fields(mode='search_edit') if is_group_mode and not search_prefix: return if not is_group_mode and search_prefix: query = event.raw_text last_messages = await self.get_last_messages_in_chat(event) try: if not last_messages: raise MessageHasBeenDeletedError() for next_message in last_messages.messages: if next_message.is_reply and event.id == next_message.reply_to_msg_id: request_context.statbox(action='resolved') return await self.do_search( event=event, request_context=request_context, prefetch_message=next_message, query=query, is_group_mode=is_group_mode, ) raise MessageHasBeenDeletedError() except MessageHasBeenDeletedError as e: request_context.error_log(e) return await event.reply( t('REPLY_MESSAGE_HAS_BEEN_DELETED', language=request_context.chat.language), )
async def _send_fail_response(self, event, request_context: RequestContext): try: await event.answer( t('MAINTENANCE_WO_PIC', language=request_context.chat.language)) except (ConnectionError, QueryIdInvalidError) as e: request_context.error_log(e)
async def respond_not_found(self, request_context: RequestContext, document_view): return await self.delivery_service.telegram_client.send_message( request_context.chat.chat_id, t("SOURCES_UNAVAILABLE", language=request_context.chat.language).format( document=document_view.get_robust_title()), buttons=[close_button()])
async def _on_fail(): await self.delivery_service.telegram_client.send_message( request_context.chat.chat_id, t('MAINTENANCE', language=request_context.chat.language).format( maintenance_picture_url=self.delivery_service. maintenance_picture_url), buttons=[close_button()])
async def _send_fail_response(self, event: events.ChatAction, request_context: RequestContext): try: await event.reply(t( 'MAINTENANCE', language=request_context.chat.language).format( maintenance_picture_url=self.application. config['application']['maintenance_picture_url'], ), buttons=[close_button()]) except (ConnectionError, QueryIdInvalidError) as e: request_context.error_log(e)
async def _check_maintenance(self, event: events.ChatAction): if (self.application.config['application']['is_maintenance_mode'] and event.chat_id not in self.application.config['application'] ['bypass_maintenance']): await event.reply( t('UPGRADE_MAINTENANCE', language='en').format(upgrade_maintenance_picture_url=self. application.config['application'] ['upgrade_maintenance_picture_url']), ) raise events.StopPropagation()
async def handler(self, event: events.ChatAction, request_context: RequestContext): query = event.pattern_match.group(1) request_context.statbox(action='start', mode='shortlink', query=query) try: bot_name = self.application.config["telegram"]["bot_external_name"] text = encode_query_to_deep_link(query, bot_name) except TooLongQueryError: text = t('TOO_LONG_QUERY_FOR_SHORTLINK', language=request_context.chat.language), return await event.reply(f'`{text}`', link_preview=False)
async def handler(self, event: events.ChatAction, request_context: RequestContext): query = event.pattern_match.group(1) if query: request_context.statbox(action='show', mode='contact', query=query) await event.reply( t('THANK_YOU_FOR_CONTACT', language=request_context.chat.language).format( related_channel=self.application.config['telegram'] ['related_channel'], ), ) else: request_context.statbox(action='show', mode='contact') await event.reply( t('CONTACT', language=request_context.chat.language).format( btc_donate_address=config['application'] ['btc_donate_address'], libera_pay_url=config['application']['libera_pay_url'], related_channel=config['telegram']['related_channel'], ), link_preview=False, )
async def handler(self, event: events.ChatAction, request_context: RequestContext): try: self.check_search_ban_timeout( user_id=str(request_context.chat.chat_id)) except BannedUserError as e: request_context.error_log(e) return await event.reply( t('BANNED_FOR_SECONDS', language=request_context.chat.language).format( seconds=e.ban_timeout, reason=t('BAN_MESSAGE_TOO_MANY_REQUESTS', language=request_context.chat.language), )) search_prefix, query, is_group_mode = self.parse_pattern(event) if is_group_mode and not search_prefix: return if not is_group_mode and search_prefix: query = event.raw_text prefetch_message = await event.reply( t("SEARCHING", language=request_context.chat.language), ) self.application.user_manager.last_widget[ request_context.chat.chat_id] = prefetch_message.id try: await self.do_search( event=event, request_context=request_context, prefetch_message=prefetch_message, query=query, is_group_mode=is_group_mode, is_shortpath_enabled=True, ) except (AioRpcError, asyncio.CancelledError) as e: await asyncio.gather( event.delete(), prefetch_message.delete(), ) raise e
async def external_cancel(self): self.task.cancel() self.request_context.statbox( action='externally_canceled', document_id=self.document_view.id, schema=self.document_view.schema, ) await self.delivery_service.telegram_client.send_message( self.request_context.chat.chat_id, t("DOWNLOAD_CANCELED", language=self.request_context.chat.language).format( document=self.document_view.get_robust_title()), buttons=[close_button()])
async def handler(self, event, request_context: RequestContext): session_id = self.generate_session_id() request_context.add_default_fields(mode='top_missed', session_id=session_id) request_context.statbox() prefetch_message = await event.reply(t("SEARCHING", language=request_context.chat.language)) message_id = prefetch_message.id return await self.do_request( request_context=request_context, session_id=session_id, message_id=message_id, page=0, )
async def _check_ban(self, event: events.ChatAction, request_context: RequestContext, chat: ChatPb): if is_banned(chat): if chat.ban_message is not None: async with safe_execution( request_context=request_context, on_fail=lambda: self._send_fail_response( event, request_context), ): await event.reply( t('BANNED', language=chat.language).format( datetime=str(time.ctime(chat.ban_until)), reason=chat.ban_message, )) raise events.StopPropagation()
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)): async with safe_execution( request_context=request_context, on_fail=lambda: self._send_fail_response( event, request_context), ): await event.reply( t('SUBSCRIBE_TO_CHANNEL', language=chat.language).format( related_channel=self.application.config['telegram'] ['related_channel'])) raise events.StopPropagation()
async def handler(self, event: events.ChatAction, request_context: RequestContext): request_context.statbox(action='show', mode='donate') await event.reply( t('DONATE', language=request_context.chat.language).format( amazon_gift_card_recipient=config['application'].get( 'amazon_gift_card_recipient', '🚫'), amazon_gift_card_url=config['application'].get( 'amazon_gift_card_url', '🚫'), btc_donate_address=config['application'].get( 'btc_donate_address', '🚫'), libera_pay_url=config['application'].get( 'libera_pay_url', '🚫'), related_channel=config['telegram'].get('related_channel', '🚫'), ))
async def process_widgeting(self, has_found_old_widget, old_message_id, request_context: RequestContext): if has_found_old_widget: message_id = old_message_id link_preview = None else: old_message = (await self.application.telegram_client( functions.messages.GetMessagesRequest(id=[old_message_id]) )).messages[0] prefetch_message = await self.application.telegram_client.send_message( request_context.chat.chat_id, t("SEARCHING", language=request_context.chat.language), reply_to=old_message.reply_to_msg_id, ) self.application.user_manager.last_widget[request_context.chat.chat_id] = prefetch_message.id message_id = prefetch_message.id link_preview = True return message_id, link_preview
async def render(self) -> tuple[str, Optional[list]]: if not len(self.typed_documents): return t('COULD_NOT_FIND_ANYTHING', language=self.chat.language), [ close_button(self.session_id) ] serp_elements = [] for position, typed_document in enumerate(self.typed_documents): view = parse_typed_document_to_view(typed_document) serp_elements.append( view.get_snippet( language=self.chat.language, limit=512 + 128, )) promo = self.application.promotioner.choose_promotion( language=self.chat.language).format( related_channel=self.application.config['telegram'] ['related_channel'], ) serp_elements.append(promo) serp = '\n\n'.join(serp_elements) buttons = [] if self.has_next or self.page > 0: buttons = [ Button.inline( text='<<1' if self.page > 1 else ' ', data=f'/{self.cmd}_{self.session_id}_{self.message_id}_0' if self.page > 1 else '/noop', ), Button.inline( text=f'<{self.page}' if self.page > 0 else ' ', data= f'/{self.cmd}_{self.session_id}_{self.message_id}_{self.page - 1}' if self.page > 0 else '/noop', ), Button.inline( text=f'{self.page + 2}>' if self.has_next else ' ', data= f'/{self.cmd}_{self.session_id}_{self.message_id}_{self.page + 1}' if self.has_next else '/noop', ) ] buttons.append(close_button(self.session_id)) return serp, buttons
async def render(self): text = t('SETTINGS_TEMPLATE', language=self.chat.language).format( bot_version=self.application.config['application']['bot_version'], nexus_version=self.application.config['application']['nexus_version'], language=top_languages.get(self.chat.language, self.chat.language), ) if not self.is_group_mode and self.application.config['application']['views']['settings']['has_discovery_button']: text = f"{text}\n\n{t('NEXUS_DISCOVERY_DESCRIPTION', language=self.chat.language)}" buttons = [] if self.has_language_buttons: buttons.append([]) for language in sorted(top_languages): if len(buttons[-1]) >= 4: buttons.append([]) buttons[-1].append( Button.inline( text=top_languages[language], data=f'/settings_sl_{language}' ) ) if self.is_group_mode: return text, buttons if self.application.config['application']['views']['settings']['has_system_messaging_button']: buttons.append([ Button.inline( text=( f'{t("SYSTEM_MESSAGING_OPTION", language=self.chat.language)}: ' f'{boolean_emoji[self.chat.is_system_messaging_enabled]}' ), data=f'/settings_ssm_{1 - int(self.chat.is_system_messaging_enabled)}' ) ]) if self.application.config['application']['views']['settings']['has_discovery_button']: buttons.append([ Button.inline( text=( f'{t("DISCOVERY_OPTION", language=self.chat.language)}: ' f'{boolean_emoji[self.chat.is_discovery_enabled]}' ), data=f'/settings_sd_{1 - int(self.chat.is_discovery_enabled)}' ) ]) return text, buttons
async def handler(self, event: events.ChatAction, request_context: RequestContext): request_context.statbox(action='show', mode='emoji') await event.reply( t('TANKS_BRUH', language=request_context.chat.language))
async def render(self) -> tuple[str, Optional[list]]: if not len(self.scored_documents): 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'] for scored_document in self.scored_documents: view = parse_typed_document_to_view(scored_document.typed_document) if not self.is_group_mode: view_command = view.get_view_command( session_id=self.session_id, message_id=self.message_id, position=scored_document.position, ) else: view_command = view.get_deep_link(bot_external_name, text='⬇️') serp_elements.append( view.get_snippet( language=self.chat.language, view_command=view_command, limit=512 + 128, )) serp = '\n\n'.join(serp_elements) if self.is_group_mode: try: encoded_query = encode_query_to_deep_link( self.query, bot_external_name, ) serp = ( f"{serp}\n\n**{t('DOWNLOAD_AND_SEARCH_MORE', language=self.chat.language)}: **" f'[@{bot_external_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})') if not self.is_group_mode: promo = self.application.promotioner.choose_promotion( language=self.chat.language).format( related_channel=self.application.config['telegram'] ['related_channel'], ) serp = f'{serp}\n\n{promo}\n' buttons = None if not self.is_group_mode: buttons = [] if self.has_next or self.page > 0: buttons = [ Button.inline( text='<<1' if self.page > 1 else ' ', data=f'/search_{self.session_id}_{self.message_id}_0' if self.page > 1 else '/noop', ), Button.inline( text=f'<{self.page}' if self.page > 0 else ' ', data= f'/search_{self.session_id}_{self.message_id}_{self.page - 1}' if self.page > 0 else '/noop', ), Button.inline( text=f'{self.page + 2}>' if self.has_next else ' ', data= f'/search_{self.session_id}_{self.message_id}_{self.page + 1}' if self.has_next else '/noop', ) ] buttons.append(close_button(self.session_id)) return serp, buttons
async def submit( self, request: SubmitRequestPb, context: ServicerContext, metadata: dict, ) -> SubmitResponsePb: session_id = metadata.get('session-id') request_context = RequestContext( bot_name=self.service_name, chat=request.chat, request_id=metadata.get('request-id'), ) request_context.add_default_fields( mode='submit', session_id=metadata.get('session-id'), **self.get_default_service_fields(), ) document = BinaryReader(request.telegram_document).tgread_object() if document.size > 20 * 1024 * 1024: request_context.error_log(FileTooBigError(size=document.size)) request_context.statbox(action='file_too_big') await self.telegram_client.send_message( request_context.chat.chat_id, t('FILE_TOO_BIG_ERROR', language=request_context.chat.language), buttons=[close_button()], ) return SubmitResponsePb() processing_message = await self.telegram_client.send_message( request_context.chat.chat_id, t("PROCESSING_PAPER", language=request_context.chat.language).format( filename=document.attributes[0].file_name, ), ) try: file = await self.telegram_client.download_document( document=document, file=bytes) try: processed_document = await self.grobid_client.process_fulltext_document( pdf_file=file) except BadRequestError as e: request_context.statbox(action='unparsable_document') request_context.error_log(e) await self.telegram_client.send_message( request_context.chat.chat_id, t('UNPARSABLE_DOCUMENT_ERROR', language=request_context.chat.language).format( filename=document.attributes[0].file_name, ), buttons=[close_button()], ) return SubmitResponsePb() if not processed_document.get('doi'): request_context.statbox(action='unparsable_doi') request_context.error_log(UnparsableDoiError()) await self.telegram_client.send_message( request_context.chat.chat_id, t('UNPARSABLE_DOI_ERROR', language=request_context.chat.language).format( filename=document.attributes[0].file_name, ), buttons=[close_button()], ) return SubmitResponsePb() search_response_pb = await self.meta_api_client.search( schemas=('scimag', ), query=processed_document['doi'], page=0, page_size=1, request_id=request_context.request_id, session_id=session_id, user_id=str(request_context.chat.chat_id), language=request_context.chat.language, ) if len(search_response_pb.scored_documents) == 0: request_context.statbox(action='unavailable_metadata') request_context.error_log( UnavailableMetadataError(doi=processed_document['doi'])) await self.telegram_client.send_message( request_context.chat.chat_id, t('UNAVAILABLE_METADATA_ERROR', language=request_context.chat.language).format( doi=processed_document['doi']), buttons=[close_button()], ) return SubmitResponsePb() document_view = ScimagView( search_response_pb.scored_documents[0].typed_document.scimag) uploaded_message = await self.send_file( document_view=document_view, file=file, request_context=request_context, session_id=session_id, voting=False, ) finally: await processing_message.delete() document_operation_pb = DocumentOperationPb( update_document=UpdateDocumentPb( typed_document=TypedDocumentPb(sharience=ShariencePb( parent_id=document_view.id, uploader_id=request_context.chat.chat_id, updated_at=int(time.time()), md5=hashlib.md5(file).hexdigest(), filesize=document.size, ipfs_multihashes=await self.get_ipfs_hashes(file=file), telegram_file_id=uploaded_message.file.id, )), ), ) request_context.statbox( action='success', document_id=document_view.id, schema='scimag', ) await operation_log(document_operation_pb) return SubmitResponsePb()
async def _check_read_only(self, event: events.ChatAction): if self.application.config['application']['is_read_only_mode']: await event.reply(t("READ_ONLY_MODE", language='en'), ) raise events.StopPropagation()
async def download_task(self, request_context: RequestContext, document_view): throttle_secs = 2.0 async def _on_fail(): await self.delivery_service.telegram_client.send_message( request_context.chat.chat_id, t('MAINTENANCE', language=request_context.chat.language).format( maintenance_picture_url=self.delivery_service. maintenance_picture_url), buttons=[close_button()]) async with safe_execution( request_context=request_context, on_fail=_on_fail, ): progress_bar_download = ProgressBar( telegram_client=self.delivery_service.telegram_client, request_context=request_context, banner=t("LOOKING_AT", language=request_context.chat.language), header=f'⬇️ {document_view.get_filename()}', tail_text=t('TRANSMITTED_FROM', language=request_context.chat.language), throttle_secs=throttle_secs, ) downloads_gauge.inc() start_time = time.time() try: file = await self.download( document_view=document_view, progress_bar=progress_bar_download, ) if not file: request_context.statbox( action='missed', duration=time.time() - start_time, document_id=document_view.id, schema=document_view.schema, ) is_served_from_sharience = False if self.delivery_service.is_sharience_enabled: is_served_from_sharience = await self.try_sharience( request_context=request_context, document_view=document_view, ) if not is_served_from_sharience: request_context.statbox( action='not_found', document_id=document_view.id, duration=time.time() - start_time, schema=document_view.schema, ) await self.respond_not_found( request_context=request_context, document_view=document_view, ) return else: request_context.statbox( action='downloaded', duration=time.time() - start_time, document_id=document_view.id, len=len(file), schema=document_view.schema, ) progress_bar_upload = ProgressBar( telegram_client=self.delivery_service.telegram_client, request_context=request_context, message=progress_bar_download.message, banner=t("LOOKING_AT", language=request_context.chat.language), header=f'⬇️ {document_view.get_filename()}', tail_text=t('UPLOADED_TO_TELEGRAM', language=request_context.chat.language), throttle_secs=throttle_secs) uploaded_message = await self.delivery_service.send_file( document_view=self.document_view, file=file, progress_callback=progress_bar_upload.callback, request_context=self.request_context, session_id=self.session_id, voting=not is_group_or_channel( self.request_context.chat.chat_id), ) request_context.statbox( action='uploaded', duration=time.time() - start_time, document_id=document_view.id, schema=document_view.schema, ) if self.delivery_service.should_store_hashes: asyncio.create_task( self.store_hashes( document_view=document_view, telegram_file_id=uploaded_message.file.id, file=file, )) except DownloadError: await self.external_cancel() except ProgressBarLostMessageError: self.request_context.statbox( action='user_canceled', duration=time.time() - start_time, document_id=document_view.id, schema=document_view.schema, ) except asyncio.CancelledError: pass finally: downloads_gauge.dec() messages = filter_none([progress_bar_download.message]) await self.delivery_service.telegram_client.delete_messages( request_context.chat.chat_id, messages)
data=f'/close_{session_id}', ) else: return Button.inline( text='✖️', data='/close', ) def vote_button(language: str, session_id: str, schema: str, document_id: int, case: str): label = f"REPORT_{case.upper()}_FILE" case = {'broken': 'b', 'ok': 'o'}[case] schema = {'scimag': 'a', 'scitech': 'b'}[schema] return Button.inline( text=t(label, language=language), data=f'/vote{schema}_{session_id}_{document_id}_{case}', ) def encode_query_to_deep_link(query, bot_name): encoded_query = encode_deep_query(query) if len(encoded_query) <= 64: return f'https://t.me/{bot_name}?start={encoded_query}' raise TooLongQueryError() def encode_deep_query(query): return base64.b64encode(query.encode(), altchars=b'-_').decode()