From cd27baa8d65b36cfb1d030bc5a578ac6efb45445 Mon Sep 17 00:00:00 2001 From: firedotguy Date: Sat, 28 Feb 2026 23:27:41 +0300 Subject: [PATCH] feat: add spans --- itd/enums.py | 11 ++++- itd/models/post.py | 1 + itd/request.py | 2 +- itd/utils.py | 114 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 itd/utils.py diff --git a/itd/enums.py b/itd/enums.py index 8eb094e..201373f 100644 --- a/itd/enums.py +++ b/itd/enums.py @@ -39,4 +39,13 @@ class AccessType(Enum): NOBODY = 'nobody' # никто MUTUAL = 'mutual' # взаимные FOLLOWERS = 'followers' # подписчики - EVERYONE = 'everyone' # все \ No newline at end of file + EVERYONE = 'everyone' # все + +class SpanType(Enum): + MONOSPACE = 'monospace' # моноширный (код) + STRIKE = 'strike' # зачеркнутый + BOLD = 'bold' # жирный + ITALIC = 'italic' # курсив + SPOILER = 'spoiler' # спойлер + UNDERLINE = 'underline' # подчеркнутый + HASHTAG = 'hashtag' # хэштэг ? (появляется только при получении постов, при создании нету) diff --git a/itd/models/post.py b/itd/models/post.py index 5a8eaa1..affc289 100644 --- a/itd/models/post.py +++ b/itd/models/post.py @@ -7,6 +7,7 @@ from itd.models.user import UserPost, UserNewPost from itd.models._text import TextObject from itd.models.file import PostAttach from itd.models.comment import Comment +from itd.enums import SpanType class NewPollOption(BaseModel): diff --git a/itd/request.py b/itd/request.py index 391025c..a5acaa0 100644 --- a/itd/request.py +++ b/itd/request.py @@ -39,7 +39,7 @@ def fetch(token: str, method: str, url: str, params: dict = {}, files: dict[str, raise RateLimitExceeded(res.json()['error'].get('retryAfter', 0)) if res.json().get('error', {}).get('code') == 'UNAUTHORIZED': raise Unauthorized() - if res.json().get('error', {}).get('code') == 'ACCOUNT_BANNED': + if res.json().get('error', {}).get('code') in ('ACCOUNT_BANNED', 'USER_BLOCKED'): raise AccountBanned() if res.json().get('error', {}).get('code') == 'PROFILE_REQUIRED': raise ProfileRequired() diff --git a/itd/utils.py b/itd/utils.py new file mode 100644 index 0000000..5074ea6 --- /dev/null +++ b/itd/utils.py @@ -0,0 +1,114 @@ +# новая версия от чат гпт. у меня самого не получилось сделать +from itd.models.post import Span +from itd.enums import SpanType + + +class Tag: + def __init__(self, open: str, close: str, type: SpanType): + self.open = open + self.close = close + self.type = type + + +def _parse_spans(text: str, tags: list[Tag]) -> tuple[str, list[Span]]: + spans: list[Span] = [] + stack: list[tuple[int, SpanType, int, int]] = [] + clean_chars: list[str] = [] + i = 0 + + while i < len(text): + closed = False + for idx, tag in enumerate(tags): + if text.startswith(tag.close, i) and stack and stack[-1][0] == idx: + _, span_type, offset, _ = stack.pop() + spans.append(Span(length=len(clean_chars) - offset, offset=offset, type=span_type)) + i += len(tag.close) + closed = True + break + if closed: + continue + + opened = False + for idx, tag in enumerate(tags): + if text.startswith(tag.open, i): + stack.append((idx, tag.type, len(clean_chars), i)) + i += len(tag.open) + opened = True + break + if opened: + continue + + clean_chars.append(text[i]) + i += 1 + + if stack: + _, last_type, _, raw_pos = stack[-1] + raise ValueError(f'No closing tag for {last_type.value} at pos {raw_pos}') + + spans.sort(key=lambda span: span.offset) + return ''.join(clean_chars), spans + + +def parse_html(text: str) -> tuple[str, list[Span]]: + return _parse_spans( + text, + [ + Tag('', '', SpanType.BOLD), + Tag('', '', SpanType.ITALIC), + Tag('', '', SpanType.STRIKE), + Tag('', '', SpanType.UNDERLINE), + Tag('', '', SpanType.MONOSPACE), + Tag('', '', SpanType.SPOILER), + ], + ) + + +# версия от человека (не работает с вложенными тэгами) +# from re import finditer, Match + +# from itd.models.post import Span +# from itd.enums import SpanType + + +# class Tag: +# def __init__(self, open: str, close: str, type: SpanType): +# self.open = open +# self.close = close +# self.type = type + +# def raise_error(self, pos: int): +# raise ValueError(f'No closing tag for {self.type.value} at pos {pos - len(self.open)}') + +# def to_span(self, start: int, end: int) -> Span: +# return Span(length=end - (start - len(self.open)), offset=start - len(self.open), type=self.type) + +# def get_pos(self, match: Match[str], text: str, offset: int) -> tuple[int, int, str]: +# start = match.end() - offset +# text = text[:match.start() - offset] + text[start:] +# end = text.find(self.close, start) +# if end == -1: +# self.raise_error(start) + +# return start - len(self.open), end, text[:end] + text[end + len(self.close):] + + +# def parse_html(text: str) -> tuple[str, list[Span]]: +# spans = [] + +# for tag in [ +# Tag('', '', SpanType.BOLD), +# Tag('', '', SpanType.ITALIC), +# Tag('', '', SpanType.STRIKE), +# Tag('', '', SpanType.UNDERLINE), +# Tag('', '', SpanType.MONOSPACE), +# Tag('', '', SpanType.SPOILER), +# ]: + +# offset = 0 +# full_text = text +# for match in finditer(tag.open, full_text): +# start, end, text = tag.get_pos(match, text, offset) +# spans.append(tag.to_span(start, end)) +# offset += len(tag.open) + len(tag.close) + +# return text, spans