Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

πŸ”§ refactor(mermaid): replace aiohttp_client_cache with aiohttp #50

Merged
merged 2 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 1 addition & 54 deletions pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion playground/markdownify_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
print("Hello, World!")
```
This is `inline code`
1. First ordered list item
1. **First ordered list item**
2. Another item
- Unordered sub-list.
- Another item.
Expand Down
16 changes: 10 additions & 6 deletions playground/telegramify_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pathlib
from time import sleep

from aiohttp import ClientSession
from dotenv import load_dotenv
from telebot import TeleBot

Expand Down Expand Up @@ -32,12 +33,15 @@

# Write an async function to send message
async def send_message():
boxs = await telegramify_markdown.telegramify(
content=md,
interpreters_use=[BaseInterpreter(), MermaidInterpreter()], # Render mermaid diagram
latex_escape=True,
max_word_count=4090 # The maximum number of words in a single message.
)
global_session = ClientSession()
async with global_session:
boxs = await telegramify_markdown.telegramify(
content=md,
interpreters_use=[BaseInterpreter(), MermaidInterpreter(session=global_session)], # Render mermaid diagram
latex_escape=True,
normalize_whitespace=True,
max_word_count=4090 # The maximum number of words in a single message.
)
for item in boxs:
print("Sent one item")
sleep(0.2)
Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "telegramify-markdown"
version = "0.3.0"
version = "0.3.1"
description = "Makes it easy to send Markdown in Telegram MarkdownV2 style"
authors = [
{ name = "sudoskys", email = "coldlando@hotmail.com" },
Expand All @@ -12,7 +12,6 @@ dependencies = [
"Pillow>=10.4.0",
"pydantic>=2.10.3",
"aiohttp>=3.10.11",
"aiohttp-client-cache>=0.12.4",
]
requires-python = ">=3.8"
readme = "README.md"
Expand Down
13 changes: 12 additions & 1 deletion src/telegramify_markdown/interpreters.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import List, Any, Callable
from typing import TYPE_CHECKING

import mistletoe

Expand All @@ -7,6 +8,9 @@
from telegramify_markdown.mime import get_filename
from telegramify_markdown.type import TaskType, File, Text, Photo, SentType

if TYPE_CHECKING:
from aiohttp import ClientSession


class BaseInterpreter(object):
name = "base"
Expand Down Expand Up @@ -72,6 +76,10 @@ async def render_task(self,

class MermaidInterpreter(BaseInterpreter):
name = "mermaid"
session = None

def __init__(self, session: "ClientSession" = None):
self.session = session

async def merge(self, tasks: List[TaskType]) -> List[TaskType]:
"""
Expand Down Expand Up @@ -149,7 +157,10 @@ async def render_task(self,
if isinstance(_raw_text, mistletoe.span_token.RawText):
file_content = _raw_text.content
try:
img_io, url = await render_mermaid(file_content.replace("```mermaid", "").replace("```", ""))
img_io, url = await render_mermaid(
diagram=file_content.replace("```mermaid", "").replace("```", ""),
session=self.session
)
message = f"[edit in mermaid.live]({url})"
except Exception as e:
logger.warn(f"Mermaid render error: {e}")
Expand Down
49 changes: 35 additions & 14 deletions src/telegramify_markdown/mermaid.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
from typing import Union, Tuple

from PIL import Image
from aiohttp_client_cache import CachedSession
from aiohttp_client_cache.backends import CacheBackend
from aiohttp import ClientSession

from telegramify_markdown.logger import logger

Expand All @@ -18,25 +17,41 @@ class MermaidConfig:
theme: str = "neutral"


async def download_image(url: str) -> BytesIO:
async def download_image(
url: str,
session: ClientSession = None,
) -> BytesIO:
"""
Download the image from the URL asynchronously.
:param url: Image URL
:raises: aiohttp.ClientError, asyncio.TimeoutError
:param session: Optional aiohttp.ClientSession. If not provided, a new session will be created.
:raises ValueError: If the request fails or the image cannot be downloaded.
:return: BytesIO object containing the image data.
"""
logger.debug(f"telegramify_markdown: Downloading mermaid image from {url}")
headers = {
"User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36")
}
async with CachedSession(cache=CacheBackend(expire_after=60 * 60)) as session:
try:
async with session.get(url, headers=headers, timeout=10) as response:
response.raise_for_status()
content = await response.read()
except Exception as e:
raise ValueError(f"telegramify_markdown: Render failed on the mermaid graph from {url}") from e
return BytesIO(content)

needs_closing = False

if session is None:
session = ClientSession()
needs_closing = True

try:
async with session.get(url, headers=headers, timeout=10) as response:
response.raise_for_status() # Raise exception for HTTP errors (e.g., 404, 500)
content = await response.read() # Read response content as bytes
return BytesIO(content)

except Exception as e:
raise ValueError(f"telegramify_markdown: Render failed on the mermaid graph from {url}") from e
finally:
# Only close the session if we created it
if needs_closing:
await session.close()


def is_image(data: BytesIO) -> bool:
Expand Down Expand Up @@ -133,12 +148,18 @@ def get_mermaid_ink_url(graph_markdown: str) -> str:
return f'https://mermaid.ink/img/{generate_pako(graph_markdown)}?theme=neutral&width=500&scale=2&type=webp'


async def render_mermaid(diagram: str) -> Tuple[BytesIO, str]:
async def render_mermaid(
diagram: str,
session: ClientSession = None,
) -> Tuple[BytesIO, str]:
# render picture
img_url = get_mermaid_ink_url(diagram)
caption = get_mermaid_live_url(diagram)
# Download the image
img_data = await download_image(img_url)
img_data = await download_image(
url=img_url,
session=session
)
if not is_image(img_data):
raise ValueError("The URL does not return an image.")
img_data.seek(0) # Reset the file pointer to the beginning
Expand Down
4 changes: 3 additions & 1 deletion src/telegramify_markdown/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,9 @@ def render_list_item(
) -> Iterable[str]:
token_origin = str(token.leader).strip()
if token_origin.endswith("."):
token.leader = formatting.escape_markdown(token.leader) + " "
if not token.leader.endswith(" "):
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found that since tokens cannot be deeply copied, the render function will modify the token every time it determines whether the limit is exceeded.
The longer the text, the more modifications are made.

token.leader += " "
token.leader = formatting.escape_markdown(token.leader)
else:
token.leader = formatting.escape_markdown("⦁")
return super().render_list_item(token, max_line_length)
Expand Down
Loading