Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
Tishka17 committed Sep 25, 2023
2 parents 4582ee8 + e66af59 commit ae955be
Show file tree
Hide file tree
Showing 23 changed files with 444 additions and 24 deletions.
10 changes: 8 additions & 2 deletions ruff.toml → .ruff.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
line-length = 79
src = ["src/sulguk"]
src = ["src"]

select = [
"E",
Expand All @@ -19,4 +19,10 @@ select = [

ignore = [
"RUF001",
]
]

[isort]
no-lines-before = ["local-folder"]

[flake8-tidy-imports]
ban-relative-imports = "parents"
41 changes: 35 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ await bot.send_message(
For all supported tags unknown attributes are ignored as well as unknown classes.
Unsupported tags are raising an error.

Standard telegram tags (with some changes):
#### Standard telegram tags (with some changes):
* `<a>` - a hyperlink with `href` attribute
* `<b>`, `<strong>` - a bold text
* `<i>`, `<em>` - an italic text
Expand All @@ -125,7 +125,10 @@ Standard telegram tags (with some changes):
* `<pre>` with optional `class="language-<name>"` - a preformatted block with code. `<name>` will be sent as a language attribute in telegram.
* `<code>` - an inline preformatted element.

Additional tags:
**Note:** In standard Telegram HTML you can set a preformatted text language nesting `<code class="language-<name>">` in `<pre>` tag. This works when it is an only child. But any additional symbol outside of `<code>` breaks it.
The same behavior is supported in sulguk. Otherwise, you can set the language on `<pre>` tag itself.

#### Additional tags:
* `<br/>` - new line
* `<hr/>` - horizontal line
* `<ul>` - unordered list
Expand All @@ -144,14 +147,40 @@ Additional tags:
* `<cite>`, `<var>` - italic
* `<progress>`, `<meter>` are rendered using emoji (🟩🟩🟩🟨⬜️⬜️)
* `<kbd>`, `<samp>` - preformatted text
* `<img>` - as a link with picture emoji before. `alt` text is used if provided.

Tags which are treated as block elements (like `<div>`):
#### Tags which are treated as block elements (like `<div>`):
`<footer>`, `<header>`, `<main>`, `<nav>`, `<section>`

Tags which are treated as inline elements (like `<span>`):
#### Tags which are treated as inline elements (like `<span>`):
`<html>`, `<body>`, `<output>`, `<data>`, `<time>`


Tags which contents is ignored:
#### Tags which contents is ignored:

`<head>`, `<link>`, `<meta>`, `<script>`, `<style>`, `<template>`, `<title>`


## Command line utility for channel management

1. Install with addons
```shell
pip install 'sulguk[cli]'
```

2. Set environment variable `BOT_TOKEN`

```shell
export BOT_TOKEN="your telegram token"
```

3. Send HTML file as a message to your channel. Additional files will be sent as comments to the first one. You can provide a channel name or a public link

```shell
sulguk send @chat_id file.html
```

4. If you want to, edit using the link from shell or from your tg client. Edition of comments is supported as well.

```shell
sulguk edit 'https://t.me/channel/1?comment=42' file.html
```
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ classifiers = [
"Operating System :: OS Independent",
]
dependencies = []
[project.optional-dependencies]
cli = [
"aiogram",
]
[project.scripts]
sulguk = "sulguk.post_manager.cli:cli"

[project.urls]
"Homepage" = "https://github.com/tishka17/sulguk"
Expand Down
1 change: 0 additions & 1 deletion src/sulguk/aiogram_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
)

from sulguk.data import SULGUK_PARSE_MODE

from .wrapper import transform_html

logger = logging.getLogger(__name__)
Expand Down
18 changes: 16 additions & 2 deletions src/sulguk/entities/decoration.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

from sulguk.data import MessageEntity
from sulguk.render import State, TextMode

from .base import DecoratedEntity, Group


Expand Down Expand Up @@ -51,6 +50,8 @@ def _get_entity(self, offset: int, length: int) -> MessageEntity:

@dataclass
class Code(DecoratedEntity):
language: Optional[str] = None

def _get_entity(self, offset: int, length: int) -> MessageEntity:
return MessageEntity(
type="code", offset=offset, length=length,
Expand Down Expand Up @@ -108,7 +109,20 @@ def render(self, state: State) -> None:
state.canvas.text_mode = text_mode
state.canvas.add_empty_line()

def _get_language(self):
if self.language:
return self.language
if len(self.entities) != 1:
return None
nested = self.entities[0]
if not isinstance(nested, Code):
return None
return nested.language

def _get_entity(self, offset: int, length: int) -> MessageEntity:
return MessageEntity(
type="pre", offset=offset, length=length, language=self.language,
type="pre",
offset=offset,
length=length,
language=self._get_language(),
)
1 change: 0 additions & 1 deletion src/sulguk/entities/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

from sulguk.data import NumberFormat
from sulguk.render import State, int_to_number

from .base import Entity, Group


Expand Down
1 change: 0 additions & 1 deletion src/sulguk/entities/no_contents.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from abc import ABC

from sulguk.render import State

from .base import Entity


Expand Down
1 change: 0 additions & 1 deletion src/sulguk/entities/progress.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from dataclasses import dataclass

from sulguk.render import State

from .base import Entity


Expand Down
1 change: 0 additions & 1 deletion src/sulguk/entities/stub.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from dataclasses import dataclass

from sulguk.render import State

from .base import Entity


Expand Down
1 change: 0 additions & 1 deletion src/sulguk/entities/text.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from dataclasses import dataclass

from sulguk.render import State

from .base import Entity


Expand Down
Empty file.
19 changes: 19 additions & 0 deletions src/sulguk/post_manager/chat_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from logging import getLogger
from typing import Union

from aiogram import Bot
from aiogram.exceptions import TelegramBadRequest

from .exceptions import ChatNotFound

logger = getLogger(__name__)


async def get_chat(bot: Bot, chat_id: Union[str, int]):
try:
return await bot.get_chat(chat_id)
except TelegramBadRequest as e:
if "chat not found" in e.message:
logger.error("Chat %s not found", chat_id)
raise ChatNotFound
raise
40 changes: 40 additions & 0 deletions src/sulguk/post_manager/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import asyncio
import logging
import os

from aiogram import Bot

from .editor import edit
from .exceptions import ManagerError
from .params import parse_args
from .sender import send


async def main():
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
)
logging.getLogger("aiogram").setLevel(logging.WARNING)
bot = Bot(token=os.getenv("BOT_TOKEN"))
args = parse_args()
try:
if args.command == "edit":
await edit(bot, args)
else:
await send(bot, args)
except ManagerError:
logging.error("There were errors during execution. See above")
finally:
await bot.session.close()


def cli():
try:
asyncio.run(main())
except (KeyboardInterrupt, SystemExit):
pass


if __name__ == '__main__':
cli()
36 changes: 36 additions & 0 deletions src/sulguk/post_manager/editor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import logging

from aiogram import Bot
from aiogram.exceptions import TelegramBadRequest

from .chat_info import get_chat
from .file import load_file
from .params import EditArgs

logger = logging.getLogger(__name__)


async def edit(bot: Bot, args: EditArgs):
chat = await get_chat(bot, args.destination.group_id)
if not args.destination.post_id:
raise ValueError("No post provided to edit")
if args.destination.comment_id:
chat_id = chat.linked_chat_id
message_id = args.destination.comment_id
else:
chat_id = chat.id
message_id = args.destination.post_id

data = load_file(args.file)
try:
await bot.edit_message_text(
chat_id=chat_id,
message_id=message_id,
text=data.text,
entities=data.entities,
)
except TelegramBadRequest as e:
if "message is not modified" in e.message:
logger.debug("Nothing changed")
return
raise
10 changes: 10 additions & 0 deletions src/sulguk/post_manager/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class ManagerError(Exception):
pass


class LinkedMessageNotFound(ManagerError):
pass


class ChatNotFound(ManagerError):
pass
15 changes: 15 additions & 0 deletions src/sulguk/post_manager/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import logging

from sulguk import transform_html, RenderResult
from .exceptions import ManagerError

logger = logging.getLogger(__name__)


def load_file(filename) -> RenderResult:
try:
with open(filename) as f:
return transform_html(f.read())
except FileNotFoundError:
logger.error("File `%s` not found", filename)
raise ManagerError
Loading

0 comments on commit ae955be

Please sign in to comment.