hyperboria/nexus/views/telegram/base_view_builder.py
the-superpirate 51ae1fc5d9 - [nexus] Use non-vendored version of nlptools
- [nexus] Remove protos

GitOrigin-RevId: 1bc308a6c3056b2509952e4b53122f4fd1a9960d
2022-09-02 19:36:45 +03:00

390 lines
13 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import datetime
import math
import re
from typing import Optional
from urllib.parse import quote
import numpy as np
from izihawa_nlptools.utils import (
despace_full,
escape_format,
)
from izihawa_types.datetime import CustomDatetime
from library.telegram.common import close_button
from nexus.translations import t
from telethon import Button
from .common import (
TooLongQueryError,
encode_query_to_deep_link,
)
def highlight_markdown(snippet):
markdowned = b''
start_from = 0
for highlight in snippet.highlights:
from_ = getattr(highlight, 'from')
to = highlight.to
markdowned += escape_format(snippet.fragment[start_from:from_])
markdowned += b'**'
markdowned += escape_format(snippet.fragment[from_:to])
markdowned += b'**'
start_from = to
markdowned += escape_format(snippet.fragment[start_from:])
markdowned = markdowned.decode()
markdowned = despace_full(re.sub(r'\n+', ' ', markdowned))
if markdowned[0].islower() or (markdowned[:2] == '**' and markdowned[2].islower()):
markdowned = '...' + markdowned
markdowned = markdowned + '...'
markdowned = f'__{markdowned}__'
return markdowned
class TextPart:
def __init__(self, part):
self.part = str(part)
self._bold = False
self._italic = False
self._brackets = False
self._escaped = False
self._clickable = False
self._unbreakable = False
def escaped(self):
self._escaped = True
return self
def bold(self):
self._bold = True
return self
def italic(self):
self._italic = True
return self
def within_brackets(self):
self._brackets = True
return self
def clickable(self):
self._clickable = True
return self
def unbreakable(self):
self._unbreakable = True
return self
def limits(self, limit, with_dots: bool = False):
if len(self.part) > limit:
if self._unbreakable:
self.part = ''
else:
self.part = self.part[:limit]
if with_dots:
self.part += '...'
def __add__(self, other):
self.part += str(other)
def __len__(self):
return len(self.part)
def __str__(self):
r = self.part
if not self._escaped:
r = escape_format(r)
if self._clickable:
r = f'`{r}`'
if self._brackets:
r = f'({r})'
if self._bold:
r = f'**{r}**'
if self._italic:
r = f'__{r}__'
return r
class BaseViewBuilder:
icon = ''
def __init__(self, document_holder, user_language):
self.document_holder = document_holder
self.user_language = user_language
self.last_limit_part = None
self.last_limit = None
self.parts = []
self.has_cover = False
def has_field(self, name):
return self.document_holder.has_field(name)
def add(self, el, bold=False, italic=False, escaped=False, clickable=False, lower=False, with_brackets=False, unbreakable=False,
eol: Optional[str] = ' '):
if el:
if not isinstance(el, TextPart):
el = TextPart(el if not lower else el.lower())
if bold:
el.bold()
if italic:
el.italic()
if escaped:
el.escaped()
if clickable:
el.clickable()
if with_brackets:
el.within_brackets()
if unbreakable:
el.unbreakable()
self.parts.append(el)
if eol is not None:
self.parts.append(TextPart(eol))
return self
def limits(self, limit=None, with_dots: bool = False):
if limit:
current_length = 0
for i, part in enumerate(self.parts):
current_length += len(part)
if current_length > limit:
part.limits(limit + len(part) - current_length, with_dots=with_dots)
if len(part) > 0:
self.parts = self.parts[:i + 1]
else:
self.parts = self.parts[:i]
return self
return self
def limited(self, limit):
self.last_limit_part = len(self.parts)
self.last_limit = limit
return self
def end_limited(self, with_dots: bool = False):
if self.last_limit is not None:
current_length = 0
for i, part in enumerate(self.parts[self.last_limit_part:]):
current_length += len(part)
if current_length > self.last_limit:
part.limits(current_length - self.last_limit, with_dots=with_dots)
if len(part) > 0:
self.parts = self.parts[:self.last_limit_part + i + 1]
else:
self.parts = self.parts[:self.last_limit_part + i]
return self
self.last_limit = None
self.last_limit_part = None
return self
def add_icon(self):
return self.add(self.icon)
def add_label(self, label_name, bold=True, lower=False):
return self.add(t(label_name, self.user_language) + ':', bold=bold, lower=lower)
def add_new_line(self, n: int = 1):
return self.add('\n' * n, eol=None)
def add_formatted_datetime(self):
if self.has_field('issued_at') and self.document_holder.issued_at != -62135596800:
dt = CustomDatetime(dt=np.datetime64(self.document_holder.issued_at, 's'))
try:
ct = datetime.date(dt.year, dt.month, 1)
if datetime.date.today() - datetime.timedelta(days=365) < ct:
self.add(TextPart(f'{dt.year}.{dt.month:02d}').within_brackets())
else:
self.add(TextPart(str(dt.year)).within_brackets())
except ValueError:
pass
return self
def add_downloads_count(self):
return self.add(f'{math.log1p(self.document_holder.downloads_count):.1f}')
def add_links(self, bot_name):
self.add_label("LINKS")
self.add(" - ".join(self.document_holder.generate_links(bot_name)), escaped=True)
return self
def add_tags(self, bot_name):
tag_links = self.document_holder.generate_tags_links(bot_name)
if not tag_links:
return self
self.add(tag_links[0], escaped=True, unbreakable=True)
for tag_link in tag_links[1:]:
self.add('- ' + tag_link, escaped=True, unbreakable=True)
return self
def add_authors(self, et_al=True, first_n_authors=1, on_newline=True):
et_al_suffix = (' et al' if et_al else '')
if self.document_holder.authors:
if on_newline:
self.add_new_line()
if len(self.document_holder.authors) > first_n_authors:
self.add('; '.join(self.document_holder.authors[:first_n_authors]) + et_al_suffix)
elif len(self.document_holder.authors) == 1:
if self.document_holder.authors[0].count(';') >= 1:
comma_authors = list(map(str.strip, self.document_holder.authors[0].split(';')))
if len(comma_authors) > first_n_authors:
self.add('; '.join(comma_authors[:first_n_authors]) + et_al_suffix)
else:
self.add('; '.join(comma_authors))
elif self.document_holder.authors[0].count(',') >= 1:
comma_authors = list(map(str.strip, self.document_holder.authors[0].split(',')))
if len(comma_authors) > first_n_authors:
self.add('; '.join(comma_authors[:first_n_authors]) + et_al_suffix)
else:
self.add('; '.join(comma_authors))
else:
self.add(self.document_holder.authors[0])
else:
self.add('; '.join(self.document_holder.authors))
return self
def add_doi(self, clickable=True, with_brackets=False, with_leading_pipe=False):
if self.document_holder.doi:
if with_leading_pipe:
self.add('|')
return self.add(self.document_holder.doi, clickable=clickable, with_brackets=with_brackets)
def add_doi_link(self, with_leading_pipe=False, on_newline=False, label=False, text=None, end_newline=False):
if self.document_holder.doi:
if on_newline:
self.add_new_line()
if with_leading_pipe:
self.add('|')
if label:
self.add('DOI:', bold=True)
escaped_doi = escape_format(self.document_holder.doi)
if text is None:
text = escaped_doi
self.add(f'[{text}](https://doi.org/{quote(escaped_doi)})', escaped=True)
if end_newline:
self.add_new_line()
return self
def add_references_counter(self, bot_name, with_leading_pipe=False):
if self.has_field('referenced_by_count') and self.document_holder.referenced_by_count:
if with_leading_pipe:
self.add('|')
text = f'🔗 {self.document_holder.referenced_by_count}'
if self.document_holder.doi:
try:
link = encode_query_to_deep_link(f'refs:{self.document_holder.doi}', bot_name=bot_name)
text = f'[{text}]({link})'
except TooLongQueryError:
pass
self.add(text, escaped=True)
return self
def add_short_description(self):
return (
self.add_icon()
.add_title()
.limits(250, with_dots=True)
.add_locator()
)
def add_view(self, bot_name):
return (
self.add_cover_url()
.add_icon()
.add_title()
.limits(400, with_dots=True)
.add_locator(first_n_authors=5)
.add_new_line(2)
.add_stats()
.add_links(bot_name=bot_name)
.add_new_line(2)
.add_description()
.limits(1500, with_dots=True)
.add_new_line(2)
.add_tags(bot_name=bot_name)
.limits(3000)
)
def add_title(self):
raise NotImplementedError()
def add_description(self):
raise NotImplementedError()
def add_locator(self, first_n_authors=1, markup=True):
raise NotImplementedError()
def add_filedata(self, with_leading_pipe=False):
raise NotImplementedError()
def add_stats(self, end_newline=True):
return self
def add_cover_url(self):
cover_url = self.document_holder.get_cover_url()
if cover_url:
self.has_cover = True
self.add(f'[]({cover_url})', escaped=True, unbreakable=True)
return self
def add_isbns(self, on_newline=False, label=False, end_newline=False):
if self.document_holder.isbns:
if on_newline:
self.add_new_line()
if label:
self.add('ISBN:', bold=True)
self.add(', '.join(self.document_holder.isbns[:2]))
if end_newline:
self.add_new_line()
return self
def build(self):
text = ''.join(map(str, self.parts)).strip()
text = re.sub('\n\n+', '\n\n', text)
return text
class BaseButtonsBuilder:
def __init__(self, document_holder, user_language):
self.document_holder = document_holder
self.user_language = user_language
self.buttons = [[]]
def add_back_button(self, back_command):
self.buttons[-1].append(
Button.inline(
text='⬅️',
data=back_command
)
)
return self
def add_download_button(self, session_id, position: int = 0):
# ⬇️ is a mark, Find+F over sources before replacing
self.buttons[-1].append(
Button.inline(
text=f'⬇️ GET',
data=self.document_holder.get_download_command(session_id=session_id, position=position),
)
)
return self
def add_remote_download_button(self, bot_name):
# ⬇️ is a mark, Find+F over sources before replacing
self.buttons[-1].append(
Button.url('', self.document_holder.get_deep_id_link(bot_name))
)
return self
def add_close_button(self, session_id):
self.buttons[-1].append(close_button(session_id))
return self
def add_new_line(self):
self.buttons.append([])
return self
def add_default_layout(self, bot_name, session_id, position: int = 0):
raise NotImplementedError()
def build(self):
return self.buttons