mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 07:17:17 +00:00
add telegram integraion
This commit is contained in:
parent
b8e17c4879
commit
1a83e55d64
@ -114,6 +114,17 @@ Section name must be either `html` (plus optional architecture name, e.g. `html:
|
||||
* `link_path` - prefix for HTML links, string, required.
|
||||
* `template_path` - path to Jinja2 template, string, required.
|
||||
|
||||
### `telegram` type
|
||||
|
||||
Section name must be either `telegram` (plus optional architecture name, e.g. `telegram:x86_64`) or random name with `type` set.
|
||||
|
||||
* `type` - type of the report, string, optional, must be set to `telegram` if exists.
|
||||
* `api_key` - telegram bot API key, string, required. Please refer FAQ about how to create chat and bot
|
||||
* `chat_id` - telegram chat id, either string with `@` or integer value, required.
|
||||
* `homepage` - link to homepage, string, optional.
|
||||
* `link_path` - prefix for HTML links, string, required.
|
||||
* `template_path` - path to Jinja2 template, string, required.
|
||||
|
||||
## `upload` group
|
||||
|
||||
Remote synchronization settings.
|
||||
|
40
docs/faq.md
40
docs/faq.md
@ -402,6 +402,46 @@ There are several choices:
|
||||
|
||||
After these steps `index.html` file will be automatically synced to S3
|
||||
|
||||
### I would like to get messages to my telegram account/channel
|
||||
|
||||
1. It still requires additional dependencies:
|
||||
|
||||
```shell
|
||||
yay -S python-jinja
|
||||
```
|
||||
|
||||
2. Register bot in telegram. You can do it by using by talking with [@BotFather](https://t.me/botfather). For more details please refer to [official documentation](https://core.telegram.org/bots).
|
||||
|
||||
3. Optionally (if you want to post message in chat):
|
||||
|
||||
1. Create telegram channel.
|
||||
2. Invite your bot into the channel.
|
||||
3. Make your channel public
|
||||
|
||||
4. Get chat id if you want to use by numerical id or just use id prefixed with `@` (e.g. `@ahriman`). If you are not using chat the chat id is your user id. If you don't want to make channel public you can use [this guide](https://stackoverflow.com/a/33862907).
|
||||
|
||||
5. Configure the service:
|
||||
|
||||
```ini
|
||||
[report]
|
||||
target = telegram
|
||||
|
||||
[telegram]
|
||||
api_key = aaAAbbBBccCC
|
||||
chat_id = @ahriman
|
||||
link_path = http://example.com/x86_64
|
||||
```
|
||||
|
||||
`api_key` is the one sent by [@BotFather](https://t.me/botfather), `chat_id` is the value retrieved from previous step.
|
||||
|
||||
If you did everything fine you should receive the message with the next update. Quick credentials check can be done by using the following command:
|
||||
|
||||
```shell
|
||||
curl 'https://api.telegram.org/bot${CHAT_ID}/sendMessage?chat_id=${API_KEY}&text=hello'
|
||||
```
|
||||
|
||||
(replace `${CHAT_ID}` and `${API_KEY}` with the values from configuration).
|
||||
|
||||
## Web service
|
||||
|
||||
### Readme mentions web interface, how do I use it?
|
||||
|
@ -45,6 +45,9 @@ ssl = disabled
|
||||
[html]
|
||||
template_path = /usr/share/ahriman/templates/repo-index.jinja2
|
||||
|
||||
[telegram]
|
||||
template_path = /usr/share/ahriman/templates/telegram.jinja2
|
||||
|
||||
[upload]
|
||||
target =
|
||||
|
||||
|
10
package/share/ahriman/templates/telegram-index.jinja2
Normal file
10
package/share/ahriman/templates/telegram-index.jinja2
Normal file
@ -0,0 +1,10 @@
|
||||
{#simplified version of full report, now in markdown#}
|
||||
## {{ repository }} update
|
||||
|
||||
{% for package in packages %}
|
||||
* [{{ package.name }}]({{ link_path }}/{{ package.filename }}) {{ package.version }} at {{ package.build_date }}
|
||||
{% endfor %}
|
||||
|
||||
{% if homepage is not none %}
|
||||
[Repository]({{ homepage }})
|
||||
{% endif %}
|
@ -68,6 +68,9 @@ class Report:
|
||||
if provider == ReportSettings.Console:
|
||||
from ahriman.core.report.console import Console
|
||||
return Console(architecture, configuration, section)
|
||||
if provider == ReportSettings.Telegram:
|
||||
from ahriman.core.report.telegram import Telegram
|
||||
return Telegram(architecture, configuration, section)
|
||||
return cls(architecture, configuration) # should never happen
|
||||
|
||||
def generate(self, packages: Iterable[Package], result: Result) -> None:
|
||||
|
93
src/ahriman/core/report/telegram.py
Normal file
93
src/ahriman/core/report/telegram.py
Normal file
@ -0,0 +1,93 @@
|
||||
#
|
||||
# Copyright (c) 2021-2022 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# technically we could use python-telegram-bot, but it is just a single request, cmon
|
||||
import requests
|
||||
|
||||
from typing import Iterable
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.report.jinja_template import JinjaTemplate
|
||||
from ahriman.core.report.report import Report
|
||||
from ahriman.core.util import exception_response_text
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
class Telegram(Report, JinjaTemplate):
|
||||
"""
|
||||
telegram report generator
|
||||
:cvar TELEGRAM_API_URL: telegram api base url
|
||||
:cvar TELEGRAM_MAX_CONTENT_LENGTH: max content length of the message
|
||||
:ivar api_key: bot api key
|
||||
:ivar chat_id: chat id to post message, either string with @ or integer
|
||||
:ivar template_path: path to template for built packages
|
||||
"""
|
||||
|
||||
TELEGRAM_API_URL = "https://api.telegram.org"
|
||||
TELEGRAM_MAX_CONTENT_LENGTH = 4096
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration, section: str) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param section: settings section name
|
||||
"""
|
||||
Report.__init__(self, architecture, configuration)
|
||||
JinjaTemplate.__init__(self, section, configuration)
|
||||
|
||||
self.api_key = configuration.get(section, "api_key")
|
||||
self.chat_id = configuration.get(section, "chat_id")
|
||||
self.template_path = configuration.getpath(section, "template_path")
|
||||
|
||||
def _send(self, text: str) -> None:
|
||||
"""
|
||||
send message to telegram channel
|
||||
:param text: message body text
|
||||
"""
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self.TELEGRAM_API_URL}/bot{self.api_key}/sendMessage",
|
||||
data={"chat_id": self.chat_id, "text": text})
|
||||
response.raise_for_status()
|
||||
except requests.HTTPError as e:
|
||||
self.logger.exception("could not perform request: %s", exception_response_text(e))
|
||||
raise
|
||||
except Exception:
|
||||
self.logger.exception("could not perform request")
|
||||
raise
|
||||
|
||||
def generate(self, packages: Iterable[Package], result: Result) -> None:
|
||||
"""
|
||||
generate report for the specified packages
|
||||
:param packages: list of packages to generate report
|
||||
:param result: build result
|
||||
"""
|
||||
if not result.success:
|
||||
return
|
||||
text = self.make_html(result, self.template_path)
|
||||
# telegram content is limited by 4096 symbols, so we are going to split the message by new lines
|
||||
# to fit into this restriction
|
||||
if len(text) > self.TELEGRAM_MAX_CONTENT_LENGTH:
|
||||
position = text.rfind("\n", 0, self.TELEGRAM_MAX_CONTENT_LENGTH)
|
||||
portion, text = text[:position], text[position + 1:] # +1 to exclude newline we split
|
||||
self._send(portion)
|
||||
# send remaining (or full in case if size is less than max length) text
|
||||
self._send(text)
|
@ -32,12 +32,14 @@ class ReportSettings(Enum):
|
||||
:cvar HTML: html report generation
|
||||
:cvar Email: email report generation
|
||||
:cvar Console: print result to console
|
||||
:cvar Telegram: markdown report to telegram channel
|
||||
"""
|
||||
|
||||
Disabled = "disabled" # for testing purpose
|
||||
HTML = "html"
|
||||
Email = "email"
|
||||
Console = "console"
|
||||
Telegram = "telegram"
|
||||
|
||||
@classmethod
|
||||
def from_option(cls: Type[ReportSettings], value: str) -> ReportSettings:
|
||||
@ -52,4 +54,6 @@ class ReportSettings(Enum):
|
||||
return cls.Email
|
||||
if value.lower() in ("console",):
|
||||
return cls.Console
|
||||
if value.lower() in ("telegram",):
|
||||
return cls.Telegram
|
||||
raise InvalidOption(value)
|
||||
|
@ -53,3 +53,12 @@ def test_report_html(configuration: Configuration, result: Result, mocker: Mocke
|
||||
report_mock = mocker.patch("ahriman.core.report.html.HTML.generate")
|
||||
Report.load("x86_64", configuration, "html").run([], result)
|
||||
report_mock.assert_called_once_with([], result)
|
||||
|
||||
|
||||
def test_report_telegram(configuration: Configuration, result: Result, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate telegram report
|
||||
"""
|
||||
report_mock = mocker.patch("ahriman.core.report.telegram.Telegram.generate")
|
||||
Report.load("x86_64", configuration, "telegram").run([], result)
|
||||
report_mock.assert_called_once_with([], result)
|
||||
|
83
tests/ahriman/core/report/test_telegram.py
Normal file
83
tests/ahriman/core/report/test_telegram.py
Normal file
@ -0,0 +1,83 @@
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest import mock
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.report.telegram import Telegram
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_send(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must send a message
|
||||
"""
|
||||
request_mock = mocker.patch("requests.post")
|
||||
report = Telegram("x86_64", configuration, "telegram")
|
||||
|
||||
report._send("a text")
|
||||
request_mock.assert_called_once_with(
|
||||
pytest.helpers.anyvar(str, strict=True),
|
||||
data={"chat_id": pytest.helpers.anyvar(str, strict=True), "text": "a text"})
|
||||
|
||||
|
||||
def test_send_failed(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must reraise generic exception
|
||||
"""
|
||||
mocker.patch("requests.post", side_effect=Exception())
|
||||
report = Telegram("x86_64", configuration, "telegram")
|
||||
|
||||
with pytest.raises(Exception):
|
||||
report._send("a text")
|
||||
|
||||
|
||||
def test_make_request_failed_http_error(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must reraise http exception
|
||||
"""
|
||||
mocker.patch("requests.post", side_effect=requests.exceptions.HTTPError())
|
||||
report = Telegram("x86_64", configuration, "telegram")
|
||||
|
||||
with pytest.raises(requests.exceptions.HTTPError):
|
||||
report._send("a text")
|
||||
|
||||
|
||||
def test_generate(configuration: Configuration, package_ahriman: Package, result: Result,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate report
|
||||
"""
|
||||
send_mock = mocker.patch("ahriman.core.report.telegram.Telegram._send")
|
||||
|
||||
report = Telegram("x86_64", configuration, "telegram")
|
||||
report.generate([package_ahriman], result)
|
||||
send_mock.assert_called_once_with(pytest.helpers.anyvar(int))
|
||||
|
||||
|
||||
def test_generate_big_text(configuration: Configuration, package_ahriman: Package, result: Result,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate report with big text
|
||||
"""
|
||||
mocker.patch("ahriman.core.report.jinja_template.JinjaTemplate.make_html", return_value="a\n" * 4096)
|
||||
send_mock = mocker.patch("ahriman.core.report.telegram.Telegram._send")
|
||||
|
||||
report = Telegram("x86_64", configuration, "telegram")
|
||||
report.generate([package_ahriman], result)
|
||||
send_mock.assert_has_calls([
|
||||
mock.call(pytest.helpers.anyvar(str, strict=True)), mock.call(pytest.helpers.anyvar(str, strict=True))
|
||||
])
|
||||
|
||||
|
||||
def test_generate_no_empty(configuration: Configuration, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate report
|
||||
"""
|
||||
send_mock = mocker.patch("ahriman.core.report.telegram.Telegram._send")
|
||||
|
||||
report = Telegram("x86_64", configuration, "telegram")
|
||||
report.generate([package_ahriman], Result())
|
||||
send_mock.assert_not_called()
|
@ -329,6 +329,7 @@ def test_walk(resource_path_root: Path) -> None:
|
||||
resource_path_root / "web" / "templates" / "build-status.jinja2",
|
||||
resource_path_root / "web" / "templates" / "email-index.jinja2",
|
||||
resource_path_root / "web" / "templates" / "repo-index.jinja2",
|
||||
resource_path_root / "web" / "templates" / "telegram-index.jinja2",
|
||||
])
|
||||
local_files = list(sorted(walk(resource_path_root)))
|
||||
assert local_files == expected
|
||||
|
@ -24,3 +24,6 @@ def test_from_option_valid() -> None:
|
||||
|
||||
assert ReportSettings.from_option("console") == ReportSettings.Console
|
||||
assert ReportSettings.from_option("conSOle") == ReportSettings.Console
|
||||
|
||||
assert ReportSettings.from_option("telegram") == ReportSettings.Telegram
|
||||
assert ReportSettings.from_option("TElegraM") == ReportSettings.Telegram
|
||||
|
@ -52,6 +52,13 @@ homepage =
|
||||
link_path =
|
||||
template_path = ../web/templates/repo-index.jinja2
|
||||
|
||||
[telegram]
|
||||
api_key = apikey
|
||||
chat_id = @ahrimantestchat
|
||||
homepage =
|
||||
link_path =
|
||||
template_path = ../web/templates/telegram-index.jinja2
|
||||
|
||||
[upload]
|
||||
target =
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user