diff --git a/README.md b/README.md
index 498adf5..9af5c34 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ on:
permissions:
issues: read
- pull_request: read
+ pull_requests: read
jobs:
notify:
diff --git a/notifier/__main__.py b/notifier/__main__.py
index 45b4baa..03d31f9 100644
--- a/notifier/__main__.py
+++ b/notifier/__main__.py
@@ -24,26 +24,20 @@ def get_interactor(url: str) -> type[SendIssue] | type[SendPR]:
if __name__ == "__main__":
- html_template = os.environ.get("HTML_TEMPLATE", "").strip()
+ bot_token = os.environ["TELEGRAM_BOT_TOKEN"]
+ attempt_count = os.environ["ATTEMPT_COUNT"]
- telegram_gateway = TelegramGateway(
- chat_id=os.environ["TELEGRAM_CHAT_ID"],
- bot_token=os.environ["TELEGRAM_BOT_TOKEN"],
- attempt_count=int(os.environ["ATTEMPT_COUNT"]),
- message_thread_id=os.environ.get("TELEGRAM_MESSAGE_THREAD_ID"),
- )
+ telegram_gateway = TelegramGateway(bot_token, int(attempt_count))
+ gh_token = os.environ["GITHUB_TOKEN"]
event_url = os.environ["EVENT_URL"]
- github_gateway = GithubGateway(
- token=(os.environ.get("GITHUB_TOKEN") or "").strip(),
- event_url=event_url,
- )
+ github_gateway = GithubGateway(gh_token, event_url)
+ html_template = os.environ.get("HTML_TEMPLATE", "").strip()
custom_labels = os.environ.get("CUSTOM_LABELS", "").split(",")
if custom_labels == [""]:
custom_labels = []
-
render_service = RenderService(
custom_labels=custom_labels,
join_input_with_list=os.environ.get("JOIN_INPUT_WITH_LIST") == "1",
@@ -56,8 +50,11 @@ def get_interactor(url: str) -> type[SendIssue] | type[SendPR]:
render_service=render_service,
)
+ chat_id = os.environ["TELEGRAM_CHAT_ID"]
+ message_thread_id=os.environ.get("TELEGRAM_MESSAGE_THREAD_ID")
+
try:
- interactor.handler()
+ interactor.handler(chat_id, message_thread_id)
except Exception as e:
print(f"Error processing event: {e}", file=sys.stderr)
sys.exit(1)
diff --git a/notifier/application/interactors.py b/notifier/application/interactors.py
index d53a7b1..f915533 100644
--- a/notifier/application/interactors.py
+++ b/notifier/application/interactors.py
@@ -5,10 +5,10 @@
from notifier.application import interfaces
from notifier.application.services import RenderService
from notifier.domain.entities import Issue, PullRequest
+from notifier.infrastructure.telegram_gateway import TgPayload
TG_MESSAGE_LIMIT: typing.Final = 4096
-
ISSUE_TEMPLATE: typing.Final = (
"🚀 New issue to {repository} by @{user}
"
"📝 {title} (#{id})
"
@@ -41,7 +41,7 @@ def __init__(
self._telegram = telegram
self._render_service = render_service
- def handler(self) -> None:
+ def handler(self, chat_id: str, thread_id: str | None) -> None:
issue = self._github.get_issue()
labels = self._render_service.format_labels(issue.labels)
@@ -54,8 +54,19 @@ def handler(self) -> None:
base_url="https://github.com",
)
+ for e in render_result.entities:
+ e.pop("language", None)
+
+ tg_body=TgPayload(
+ text=render_result.text,
+ entities=render_result.entities,
+ disable_web_page_preview=True,
+ chat_id=chat_id,
+ message_thread_id=thread_id,
+ )
+
if len(render_result.text) <= TG_MESSAGE_LIMIT:
- return self._telegram.send_message(render_result)
+ return self._telegram.send_message(tg_body)
message_without_description = self._create_message(issue, "
", labels)
@@ -90,7 +101,7 @@ def __init__(
self._telegram = telegram
self._render_service = render_service
- def handler(self) -> None:
+ def handler(self, chat_id: str, thread_id: str | None) -> None:
pr = self._github.get_pull_request()
labels = self._render_service.format_labels(pr.labels)
@@ -103,8 +114,19 @@ def handler(self) -> None:
base_url="https://github.com",
)
+ for e in render_result.entities:
+ e.pop("language", None)
+
+ tg_body = TgPayload(
+ text=render_result.text,
+ entities=render_result.entities,
+ disable_web_page_preview=True,
+ chat_id=chat_id,
+ message_thread_id=thread_id,
+ )
+
if len(render_result.text) <= TG_MESSAGE_LIMIT:
- return self._telegram.send_message(render_result)
+ return self._telegram.send_message(tg_body)
message_without_description = self._create_message(pr, "", labels)
diff --git a/notifier/application/interfaces.py b/notifier/application/interfaces.py
index 4e815f4..9b7fcdc 100644
--- a/notifier/application/interfaces.py
+++ b/notifier/application/interfaces.py
@@ -1,9 +1,8 @@
import abc
import typing
-import sulguk
-
from notifier.domain.entities import PullRequest, Issue
+from notifier.infrastructure.telegram_gateway import TgPayload
class Github(typing.Protocol):
@@ -16,4 +15,4 @@ def get_pull_request(self) -> PullRequest: ...
class Telegram(typing.Protocol):
@abc.abstractmethod
- def send_message(self, render_result: sulguk.RenderResult) -> None: ...
+ def send_message(self, body: TgPayload) -> typing.Any: ...
diff --git a/notifier/infrastructure/github_gateway.py b/notifier/infrastructure/github_gateway.py
index 06db82c..d3a0db8 100644
--- a/notifier/infrastructure/github_gateway.py
+++ b/notifier/infrastructure/github_gateway.py
@@ -1,57 +1,89 @@
-import requests
+import os
+from typing import Any
+
+from adaptix import P, Retort, loader, name_mapping, Chain
+from descanso import RestBuilder
+from descanso import request_transformers as rt
+from descanso.http.requests import RequestsClient
+from requests import Session
from notifier.application import interfaces
from notifier.domain.entities import Issue, PullRequest
-class GithubGateway(interfaces.Github):
- def __init__(self, token: str, event_url: str) -> None:
+def body_pre_loader(data: dict[str, Any]) -> dict[str, Any]:
+ if "body_html" not in data:
+ data["body_html"] = ""
+ return data
+
+
+issue_recipe = [
+ name_mapping(
+ Issue,
+ map={
+ "id": "number",
+ "url": "html_url",
+ "user": ["user", "login"],
+ "body": "body_html",
+ },
+ ),
+ loader(P[Issue], body_pre_loader, Chain.FIRST),
+ loader(P[Issue].labels, lambda labels: [label["name"] for label in labels]),
+]
+
+pr_recipe = [
+ name_mapping(
+ PullRequest,
+ map={
+ "id": "number",
+ "url": "html_url",
+ "user": ["user", "login"],
+ "head_ref": ["head", "label"],
+ "base_ref": ["base", "ref"],
+ "repository": ["base", "repo", "full_name"],
+ "body": "body_html",
+ },
+ ),
+ loader(P[PullRequest], body_pre_loader, Chain.FIRST),
+ loader(P[PullRequest].labels, lambda labels: [label["name"] for label in labels]),
+]
+
+gh_recipe = [
+ *issue_recipe,
+ *pr_recipe,
+
+]
+
+rest = RestBuilder(
+ request_body_dumper=Retort(),
+ response_body_loader=Retort(recipe=gh_recipe),
+ query_param_dumper=Retort(),
+)
+
+
+class GithubGateway(RequestsClient, interfaces.Github):
+ def __init__(
+ self,
+ token: str,
+ event_url: str,
+ session: Session | None = None
+ ) -> None:
self._token = token
- self._url = event_url
-
- def get_issue(self) -> Issue:
- headers = {
- "Accept": "application/vnd.github.v3.html+json",
- "X-GitHub-Api-Version": "2022-11-28",
- "Authorization": f"Bearer {self._token}",
- }
-
- response = requests.get(self._url, headers=headers, timeout=30)
- response.raise_for_status()
-
- data = response.json()
-
- return Issue(
- id=data["number"],
- title=data["title"],
- labels=[label["name"] for label in data["labels"]],
- url=(data["html_url"] or "").strip(),
- user=data["user"]["login"],
- body=(data.get("body_html", "") or "").strip(),
+ self._event_url = event_url
+ super().__init__(
+ "",
+ session or Session(),
+ (
+ rt.Header("Accept", "application/vnd.github.v3.html+json"),
+ rt.Header("X-GitHub-Api-Version", "2022-11-28"),
+ rt.Header("Authorization", f"Bearer {self._token}"),
+ ),
)
- def get_pull_request(self) -> PullRequest:
- headers = {
- "Accept": "application/vnd.github.v3.html+json",
- "X-GitHub-Api-Version": "2022-11-28",
- "Authorization": f"Bearer {self._token}",
- }
-
- response = requests.get(self._url, headers=headers, timeout=30)
- response.raise_for_status()
-
- data = response.json()
-
- return PullRequest(
- id=data["number"],
- title=data["title"],
- labels=[label["name"] for label in data["labels"]],
- url=data["html_url"],
- user=data["user"]["login"],
- body=(data.get("body_html", "") or "").strip(),
- additions=data["additions"],
- deletions=data["deletions"],
- head_ref=data["head"]["label"],
- base_ref=data["base"]["ref"],
- repository=data["base"]["repo"]["full_name"],
- )
+ @rest.get("{self._event_url}")
+ def get_issue(self) -> Issue: # type: ignore[empty-body]
+ pass
+
+ @rest.get("{self._event_url}")
+ def get_pull_request(self) -> PullRequest: # type: ignore[empty-body]
+ pass
diff --git a/notifier/infrastructure/telegram_gateway.py b/notifier/infrastructure/telegram_gateway.py
index 95bab39..2b2fe1e 100644
--- a/notifier/infrastructure/telegram_gateway.py
+++ b/notifier/infrastructure/telegram_gateway.py
@@ -1,53 +1,45 @@
-import sys
-import time
-
-import requests
+from dataclasses import dataclass
+from typing import Any
+
+from adaptix import Retort
+from requests import Session
+from requests.adapters import HTTPAdapter
+from descanso import RestBuilder
+from descanso.http.requests import RequestsClient
import sulguk
from notifier.application import interfaces
+rest = RestBuilder(
+ request_body_dumper=Retort(),
+ response_body_loader=Retort(),
+ query_param_dumper=Retort(),
+)
+
+
+@dataclass
+class TgPayload:
+ text: str
+ entities: list[sulguk.data.MessageEntity]
+ disable_web_page_preview: bool
+ chat_id: str
+ message_thread_id: str | None
+
+
+class TelegramGateway(RequestsClient, interfaces.Telegram):
-class TelegramGateway(interfaces.Telegram):
def __init__(
self,
- chat_id: str,
- bot_token: str,
- attempt_count: int,
- message_thread_id: str | int | None,
+ token: str,
+ attemp_count: int,
+ base_url: str = "https://api.telegram.org",
+ session: Session | None = None,
) -> None:
- self._chat_id = chat_id
- self._bot_token = bot_token
- self._attempt_count = attempt_count
- self._message_thread_id = message_thread_id
-
- def send_message(self, render_result: sulguk.RenderResult) -> None:
- count = 0
- payload = self._create_payload(render_result)
- url = f"https://api.telegram.org/bot{self._bot_token}/sendMessage"
- while count < self._attempt_count:
- response = requests.post(url, json=payload, timeout=30)
- try:
- response.raise_for_status()
- except requests.exceptions.HTTPError:
- print(response.content, file=sys.stderr)
- count += 1
- time.sleep(count * 2)
- else:
- print(response.json(), file=sys.stdout)
- return
-
- def _create_payload(self, render_result: sulguk.RenderResult) -> dict:
- for e in render_result.entities:
- e.pop("language", None)
-
- payload = {
- "text": render_result.text,
- "entities": render_result.entities,
- "disable_web_page_preview": True,
- }
- payload["chat_id"] = self._chat_id
-
- if self._message_thread_id is not None:
- payload["message_thread_id"] = self._message_thread_id
-
- return payload
+ self._token = token
+ tg_session = session or Session()
+ tg_session.mount("https", HTTPAdapter(max_retries=attemp_count))
+ super().__init__(base_url, tg_session)
+
+ @rest.post("/bot{self._token}/sendMessage")
+ def send_message(self, body: interfaces.TgPayload) -> Any: # type: ignore[empty-body]
+ pass
diff --git a/requirements.txt b/requirements.txt
index 4617dbf..6cfcf4d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,3 +2,5 @@ sulguk==0.10.1
requests==2.32.5
beautifulsoup4==4.14.2
lxml==6.0.2
+descanso==0.7.1
+adaptix==3.0.0b11
\ No newline at end of file