Skip to content

Commit

Permalink
Merge pull request #58 from sudoskys/dev
Browse files Browse the repository at this point in the history
feat():optional-dependencies, remove pydantic :(
  • Loading branch information
sudoskys authored Jan 13, 2025
2 parents cab67d0 + 6436d87 commit 31ac0f8
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:

- name: Install dependencies
run: |
pdm install --dev --no-lock
pdm install --no-lock -G mermaid -G tests
- name: Run Tests
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,16 @@ To install the library, run:

```bash
pip install telegramify-markdown
pip install telegramify-markdown[mermaid]
```

or, if you use `pdm`:

```shell
pdm add telegramify-markdown
pdm add telegramify-markdown -G mermaid
# -G mermaid -G tests
# -> https://pdm-project.org/en/latest/reference/pep621/#optional-dependencies
```

### 🤔 What you want to do?
Expand All @@ -50,9 +54,10 @@ We have two main functions: `markdownify` and `telegramify`.

`markdownify`: Just converts raw Markdown text to Telegram's MarkdownV2 format.

`telegramify`: Spilt long text into multiple chunks, convert format and use Interpreter to render code block to File, Image etc.
`telegramify`: Spilt long text into multiple chunks, convert format and use Interpreter to render code block to File,
Image etc.

>`Interpreter` can be easily customized to inspect the rendering process in `telegramify`.
> `Interpreter` can be easily customized to inspect the rendering process in `telegramify`.
## 👀 Use case

Expand Down Expand Up @@ -147,7 +152,8 @@ print(converted)

### `telegramify`

please check: [playground/telegramify_case.py](https://github.com/sudoskys/telegramify-markdown/blob/main/playground/telegramify_case.py)
please
check: [playground/telegramify_case.py](https://github.com/sudoskys/telegramify-markdown/blob/main/playground/telegramify_case.py)

## 🔨 Supported Input

Expand Down
13 changes: 12 additions & 1 deletion pdm.lock

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

52 changes: 52 additions & 0 deletions playground/inspect_markdownify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import textwrap

import telegramify_markdown

# Use textwrap.dedent to remove the leading whitespace from the text.
md = textwrap.dedent(r"""
# Title
**Latex Math**
Function Change:
\(\Delta y = f(x_2) - f(x_1)\) can represent the change in the value of a function.
Average Rate of Change:
\(\frac{\Delta y}{\Delta x} = \frac{f(x_2) - f(x_1)}{x_2 - x_1}\) is used to denote the average rate of change of a function over the interval \([x_1, x_2]\).
- Slope:222
\[
F = G\frac{{m_1m_2}}{{r^2}}
\]
\[
F = G\frac{{m_1m_2}}{{r^2}}
- Inline: \(F = G\frac{{m_1m_2}}{{r^4}}\)
\(A = X × \left( (P)/100 \right) × (V)/365\)
\(\text{R} = \frac{\text{EXAMPLE}}{\text{Any}}\)
""")

# export Markdown to Telegram MarkdownV2 style.
converted = telegramify_markdown.markdownify(
md,
max_line_length=None, # If you want to change the max line length for links, images, set it to the desired value.
normalize_whitespace=False,
latex_escape=False,
)
print(converted)
# export Markdown to Telegram MarkdownV2 style.
from dotenv import load_dotenv
import os
from telebot import TeleBot
load_dotenv()
telegram_bot_token = os.getenv("TELEGRAM_BOT_TOKEN", None)
chat_id = os.getenv("TELEGRAM_CHAT_ID", None)
bot = TeleBot(telegram_bot_token)
bot.send_message(
chat_id,
converted,
parse_mode="MarkdownV2" # IMPORTANT: Need Send in MarkdownV2 Mode.
)
50 changes: 50 additions & 0 deletions playground/latex_render_case.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import matplotlib.pyplot as plt
from io import BytesIO


def render_latex_with_matplotlib(latex_expr, bg_color="#FFFFFF", text_color="#000000", dpi=300, font_size=20):
"""
Renders a LaTeX math formula using matplotlib and saves it to a BytesIO object.
Parameters:
latex_expr (str): LaTeX math formula (e.g., r"$\int_a^b f(x) \, dx = F(b) - F(a)$")
bg_color (str): Background color in HEX format (default is white: "#FFFFFF")
text_color (str): Text (formula) color in HEX format (default is black: "#000000")
dpi (int): Dots per inch for rendered output (default: 300).
font_size (int): Font size for the formula text (default: 20).
Returns:
BytesIO: A BytesIO object containing the rendered formula as PNG.
"""
# Create a figure with transparent axes and no ticks
plt.figure(figsize=(4, 2), dpi=dpi)
plt.axis('off') # Hide axes

# Using the 'text' function to render LaTeX
plt.text(0.5, 0.5, latex_expr, color=text_color, fontsize=font_size, ha='center', va='center',
transform=plt.gca().transAxes, usetex=True) # `usetex=True` enables LaTeX rendering in matplotlib

# Set the figure background color
plt.gcf().patch.set_facecolor(bg_color)

# Save the figure directly to BytesIO
buffer = BytesIO()
plt.savefig(buffer, format="png", bbox_inches='tight', pad_inches=0, transparent=True)
buffer.seek(0)
plt.close()

return buffer

# Example usage:
latex_formula = r"$F = G\frac{{m_1m_2}}{{r^2}}$"
bg_color = "#F0F8FF" # AliceBlue background
text_color = "#000000" # Black text color

# Render the LaTeX math expression
latex_bytesio = render_latex_with_matplotlib(latex_formula, bg_color=bg_color, text_color=text_color, font_size=30)

# Save the rendered formula as a PNG file (optional)
from PIL import Image

img = Image.open(latex_bytesio)
img.save("latex_matplotlib_rendered.png")
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ mermaid = [
"Pillow>=10.4.0",
"aiohttp>=3.10.11",
]

[dependency-groups]
dev = [
tests = [
'pytest < 6',
"pytelegrambotapi>=4.22.0",
"python-dotenv>=1.0.1",
'mock >= 1.0.1, < 4; python_version < "3.4"',
]

[project.urls]
Expand Down
3 changes: 1 addition & 2 deletions src/telegramify_markdown/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
from .interpreters import Text, File, Photo, BaseInterpreter, MermaidInterpreter
from .latex_escape.const import LATEX_SYMBOLS, NOT_MAP, LATEX_STYLES
from .latex_escape.helper import LatexToUnicodeHelper
from loguru import logger
from .mermaid import render_mermaid
from .logger import logger
from .mime import get_filename
from .render import TelegramMarkdownRenderer, escape_markdown
from .type import Text, File, Photo, ContentTypes
Expand Down
22 changes: 17 additions & 5 deletions src/telegramify_markdown/interpreters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
from typing import TYPE_CHECKING

import mistletoe

from loguru import logger
from telegramify_markdown.mermaid import render_mermaid

from telegramify_markdown.mermaid import render_mermaid, support_mermaid
from telegramify_markdown.mime import get_filename
from telegramify_markdown.type import TaskType, File, Text, Photo, SentType, ContentTrace

if TYPE_CHECKING:
from aiohttp import ClientSession
try:
from aiohttp import ClientSession
except ImportError:
ClientSession = None


class BaseInterpreter(object):
Expand Down Expand Up @@ -47,7 +50,7 @@ async def render_task(self,
"""
task_type, token_pairs = task
if task_type != "base":
logger.warn("Invalid task type for BaseInterpreter.")
logger.warning("Invalid task type for BaseInterpreter.")
token1_l = list(__token1 for __token1, __token2 in token_pairs)
token2_l = list(__token2 for __token1, __token2 in token_pairs)
# 处理超过最大字数限制的情况
Expand Down Expand Up @@ -96,8 +99,14 @@ async def render_task(self,
class MermaidInterpreter(BaseInterpreter):
name = "mermaid"
session = None
support = support_mermaid()

def __init__(self, session: "ClientSession" = None):
if not self.support:
logger.error(
"Mermaid is not supported because the required libraries are not installed. "
"Run `pip install telegramify-markdown[mermaid]` or remove MermaidInterpreter"
)
self.session = session

async def merge(self, tasks: List[TaskType]) -> List[TaskType]:
Expand All @@ -118,6 +127,9 @@ async def split(self, task: TaskType) -> List[TaskType]:
# 只处理 base 块
if task_type != "base":
return [task]
# Do not produce new tasks if Mermaid is not supported
if not self.support:
return [task]
# 用于存放生成的新任务
tasks = []
# 临时缓存非 Mermaid 块
Expand Down Expand Up @@ -182,7 +194,7 @@ async def render_task(self,
)
message = f"[edit in mermaid.live]({url})"
except Exception as e:
logger.warn(f"Mermaid render error: {e}")
logger.warning(f"Mermaid render error: {e}")
return [
File(
file_name="invalid_mermaid.txt",
Expand Down
5 changes: 5 additions & 0 deletions src/telegramify_markdown/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import logging

# Configure the logger
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger('telegramify_markdown')
38 changes: 31 additions & 7 deletions src/telegramify_markdown/mermaid.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
import json
import zlib
from io import BytesIO
from typing import TYPE_CHECKING
from typing import Union, Tuple

from PIL import Image
from aiohttp import ClientSession
from telegramify_markdown.logger import logger

from loguru import logger
if TYPE_CHECKING:
try:
from aiohttp import ClientSession
except ImportError:
ClientSession = None


@dataclasses.dataclass
Expand All @@ -19,7 +23,7 @@ class MermaidConfig:

async def download_image(
url: str,
session: ClientSession = None,
session: "ClientSession" = None,
) -> BytesIO:
"""
Download the image from the URL asynchronously.
Expand All @@ -37,7 +41,11 @@ async def download_image(
needs_closing = False

if session is None:
session = ClientSession()
try:
from aiohttp import ClientSession
session = ClientSession()
except ImportError as e:
raise ImportError("aiohttp and Pillow libraries are required but not installed.") from e
needs_closing = True

try:
Expand All @@ -62,9 +70,12 @@ def is_image(data: BytesIO) -> bool:
"""
try:
# 使用 Pillow 验证是否是合法图片
from PIL import Image
with Image.open(data) as img:
img.verify() # 验证图片
return True
except ImportError:
raise ImportError("Pillow library is required but not installed.")
except Exception as e:
logger.debug(f"telegramify_markdown: Image verification failed: {e}")
return False
Expand Down Expand Up @@ -150,7 +161,7 @@ def get_mermaid_ink_url(graph_markdown: str) -> str:

async def render_mermaid(
diagram: str,
session: ClientSession = None,
session: "ClientSession" = None,
) -> Tuple[BytesIO, str]:
# render picture
img_url = get_mermaid_ink_url(diagram)
Expand All @@ -166,6 +177,15 @@ async def render_mermaid(
return img_data, caption


def support_mermaid():
try:
from PIL import Image
from aiohttp import ClientSession
except ImportError:
return False
return True


if __name__ == '__main__':
mermaid_md = """
sequenceDiagram
Expand All @@ -181,7 +201,11 @@ async def run():
t1 = await render_mermaid(mermaid_md)
print(t1)
# 展示图片
Image.open(t1[0]).show()
try:
from PIL import Image
Image.open(t1[0]).show()
except ImportError as e:
print("Pillow library is required but not installed.")


asyncio.run(run())

0 comments on commit 31ac0f8

Please sign in to comment.