[MIG] mail_gateway_telegram: Migration to 16.0

pull/1305/head
Enric Tobella 2024-01-31 19:12:11 +01:00
parent b37ac894db
commit 721c8511dc
29 changed files with 1530 additions and 757 deletions

View File

@ -1,13 +1,13 @@
====================
Mail Telegram Broker
====================
=====================
Mail Telegram Gateway
=====================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:57eb6593eeb39836977570fe7bdf9b1824ade94c6b1dfc7a161fc57f2c8e564d
!! source digest: sha256:789836d72dab5a0af634604d4bc88ec56bf51c4134b20099438f14dfaaf9ca76
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
@ -17,10 +17,10 @@ Mail Telegram Broker
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github
:target: https://github.com/OCA/social/tree/16.0/mail_broker_telegram
:target: https://github.com/OCA/social/tree/16.0/mail_gateway_telegram
:alt: OCA/social
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/social-16-0/social-16-0-mail_broker_telegram
:target: https://translation.odoo-community.org/projects/social-16-0/social-16-0-mail_gateway_telegram
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/social&target_branch=16.0
@ -56,9 +56,14 @@ Configure Odoo
~~~~~~~~~~~~~~
1. Access on debug mode
2. Access `Settings > Technical Settings > Email > Mail Broker`.
3. Create a bot and assign the token. Mark it as `Show on App`
4. Press on `Generate webhook` in order to Open the webhook
2. Access `Settings > Technical Settings > Email > Mail Gateway`.
3. Access Telegram and start a converstation with BotFather.
4. Create a bot using the command /newbot. The system will ask for a bot name. Remember that it needs to end with the word bot.
5. Copy the token to access the HTTP API to the token field.
6. Define Webhook key an webhook secret of your choice in its corresponding field, in order to secure the connection.
7. Press save button and the integrate webhook smart button will appear.
8. Press the Integrate webhook button.
9. If you want to add an extra layer of security, you can check Has New Channel Security and define a Telegram security key. New chats will be created only with the command /start SECURITY_KEY.
Limitations
~~~~~~~~~~~
@ -85,7 +90,7 @@ to you on an external process with the following code.
Usage
=====
1. Access `Broker`
1. Access `Gateway`
2. Wait until someone starts a conversation with your bot.
3. Now you will be able to respond and receive messages to this person.
@ -95,7 +100,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues <https://github.com/OCA/social/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/social/issues/new?body=module:%20mail_broker_telegram%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
`feedback <https://github.com/OCA/social/issues/new?body=module:%20mail_gateway_telegram%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
@ -106,6 +111,7 @@ Authors
~~~~~~~
* Creu Blanca
* Dixmit
Contributors
~~~~~~~~~~~~
@ -116,7 +122,7 @@ Contributors
Other credits
~~~~~~~~~~~~~
- AEODOO
This work has been funded by AEOdoo (Asociación Española de Odoo - https://www.aeodoo.org)
Maintainers
~~~~~~~~~~~
@ -131,6 +137,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/social <https://github.com/OCA/social/tree/16.0/mail_broker_telegram>`_ project on GitHub.
This module is part of the `OCA/social <https://github.com/OCA/social/tree/16.0/mail_gateway_telegram>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -1,2 +1,3 @@
from . import models
from . import services
# from . import services

View File

@ -1,18 +1,21 @@
# Copyright 2020 Creu Blanca
# Copyright 2024 Dixmit
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Mail Telegram Broker",
"name": "Mail Telegram Gateway",
"summary": """
Set a broker for telegram""",
Set a gateway for telegram""",
"version": "16.0.1.0.0",
"license": "AGPL-3",
"author": "Creu Blanca,Odoo Community Association (OCA)",
"author": "Creu Blanca,Dixmit,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/social",
"depends": ["mail_broker"],
"data": ["views/mail_broker.xml"],
"qweb": ["/static/src/xml/thread.xml"],
"external_dependencies": {
"python": ["python-telegram-bot<=13.11", "cairosvg", "lottie"]
"depends": ["mail_gateway"],
"data": ["views/mail_gateway.xml"],
"external_dependencies": {"python": ["python-telegram-bot", "lottie", "cairosvg"]},
"assets": {
"mail.assets_messaging": [
"mail_gateway_telegram/static/src/models/**/*.js",
"mail_gateway_telegram/static/src/components/**/*.xml",
],
},
}

View File

@ -1,2 +1,3 @@
from . import mail_broker_channel
from . import mail_broker
from . import mail_gateway_channel
from . import mail_gateway
from . import mail_gateway_telegram

View File

@ -1,13 +0,0 @@
# Copyright 2020 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models
class MailChannel(models.Model):
_inherit = "mail.channel"
@api.returns("mail.message.broker", lambda value: value.id)
def telegram_message_post_broker(self, body=False, **kwargs):
return self.message_post_broker(body=body, broker_type="telegram", **kwargs)

View File

@ -1,13 +1,13 @@
# Copyright 2021 Creu Blanca
# Copyright 2024 Dixmit
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class MailBroker(models.Model):
class MailGateway(models.Model):
_inherit = "mail.broker"
_inherit = "mail.gateway"
telegram_security_key = fields.Char()
broker_type = fields.Selection(
gateway_type = fields.Selection(
selection_add=[("telegram", "Telegram")], ondelete={"telegram": "cascade"}
)

View File

@ -0,0 +1,25 @@
# Copyright 2024 Dixmit
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
from odoo.modules.module import get_resource_path
from odoo.addons.base.models.avatar_mixin import get_hsl_from_seed
class MailChannel(models.Model):
_inherit = "mail.channel"
def _generate_avatar_gateway(self):
if self.gateway_id.gateway_type == "telegram":
path = get_resource_path(
"mail_gateway_telegram", "static/description", "icon.svg"
)
with open(path, "r") as f:
avatar = f.read()
bgcolor = get_hsl_from_seed(self.uuid)
avatar = avatar.replace("fill:#875a7b", f"fill:{bgcolor}")
return avatar
return super()._generate_avatar_gateway()

View File

@ -0,0 +1,391 @@
# Copyright 2024 Dixmit
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
import mimetypes
import traceback
from io import BytesIO, StringIO
from odoo import _, models
from odoo.http import request
from odoo.tools import html2plaintext
from odoo.tools.mimetypes import guess_mimetype
from odoo.addons.base.models.ir_mail_server import MailDeliveryException
_logger = logging.getLogger(__name__)
try:
import asyncio
import telegram
from lottie.exporters import exporters
from lottie.importers import importers
except (ImportError, IOError) as err:
_logger.debug(err)
class MailGatewayTelegramService(models.AbstractModel):
_inherit = "mail.gateway.abstract"
_name = "mail.gateway.telegram"
_description = "Telegram Gateway services"
def _get_telegram_bot(self, token=False):
return telegram.Bot(token)
def _set_webhook(self, gateway):
bot = self._get_telegram_bot(gateway.token)
asyncio.run(
bot.setWebhook(
url=gateway.webhook_url,
api_kwargs={"secret_token": gateway.webhook_secret},
)
)
return super()._set_webhook(gateway)
async def _remove_webhook_telegram(self, gateway):
bot = self._get_telegram_bot(gateway.token)
await bot.initialize()
webhookinfo = await bot.get_webhook_info()
if webhookinfo.url:
await bot.delete_webhook(drop_pending_updates=False)
def _remove_webhook(self, gateway):
asyncio.run(self._remove_webhook_telegram(gateway))
return super()._remove_webhook(gateway)
def _verify_update(self, bot_data, kwargs):
if not bot_data["webhook_secret"]:
return True
return (
request.httprequest.headers.get("X-Telegram-Bot-Api-Secret-Token")
== bot_data["webhook_secret"]
)
def _get_channel_vals(self, gateway, token, update):
result = super()._get_channel_vals(gateway, token, update)
names = []
for name in [
update.message.chat.first_name or False,
update.message.chat.last_name or False,
update.message.chat.description or False,
update.message.chat.title or False,
]:
if name:
names.append(name)
result["name"] = " ".join(names)
result["anonymous_name"] = " ".join(names)
return result
def _preprocess_update(self, gateway, update):
for entity in update.message.entities:
if not entity.offset == 0:
continue
if not entity.type == "bot_command":
continue
command = update.message.parse_entity(entity).split("/")[1]
if hasattr(self, "_command_%s" % (command)):
return getattr(self, "_command_%s" % (command))(gateway, update)
return False
def _command_start(self, gateway, update):
if (
not gateway.has_new_channel_security
or update.message.text == "/start %s" % gateway.telegram_security_key
):
return self._get_channel(gateway, update.message.chat_id, update, True)
return True
def _receive_update(self, gateway, update):
telegram_update = telegram.Update.de_json(
update, self._get_telegram_bot(token=gateway.token)
)
if self._preprocess_update(gateway, telegram_update):
return
chat = self._get_channel(
gateway, telegram_update.message.chat_id, telegram_update
)
if not chat:
return
return self._process_update(chat, telegram_update)
def _telegram_sticker_input_options(self):
return {}
def _telegram_sticker_output_options(self):
return {}
def _get_telegram_attachment_name(self, attachment):
if hasattr(attachment, "title"):
if attachment.title:
return attachment.title
if hasattr(attachment, "file_name"):
if attachment.file_name:
return attachment.file_name
if isinstance(attachment, telegram.Sticker):
return attachment.set_name or attachment.emoji or "sticker"
if isinstance(attachment, telegram.Contact):
return attachment.first_name
return attachment.file_id
async def _process_telegram_attachment(self, attachment):
if isinstance(attachment, tuple):
attachment = attachment[-1]
# That might happen with images, we will get the last one as it is the bigger one.
if isinstance(
attachment,
(
telegram.Game,
telegram.Invoice,
telegram.Location,
telegram.SuccessfulPayment,
telegram.Venue,
),
):
return
if isinstance(attachment, telegram.Contact):
data = attachment.vcard.encode("utf-8")
else:
file = await attachment.get_file()
data = bytes(await file.download_as_bytearray())
file_name = self._get_telegram_attachment_name(attachment)
if isinstance(attachment, telegram.Sticker):
_logger.debug("Processing sticker %s", attachment)
suf = "tgs"
for p in importers:
if suf in p.extensions:
importer = p
break
exporter = exporters.get("gif")
inpt = BytesIO(data)
an = importer.process(inpt, **self._telegram_sticker_input_options())
output_options = self._telegram_sticker_output_options()
fps = output_options.pop("fps", False)
if fps:
an.frame_rate = fps
output = BytesIO()
exporter.process(an, output, **output_options)
data = output.getvalue()
mimetype = guess_mimetype(data)
return (
"{}{}".format(file_name, mimetypes.guess_extension(mimetype)),
data,
{},
)
def _process_update(self, chat, update):
chat.ensure_one()
body = ""
attachments = []
if update.message.text_html:
body = update.message.text_html
if update.message.effective_attachment:
effective_attachment = update.message.effective_attachment
if isinstance(effective_attachment, list):
current_attachment = effective_attachment[0]
for attachment in effective_attachment[1:]:
if getattr(attachment, "file_size", 0) > getattr(
current_attachment, "file_size", 0
):
current_attachment = attachment
effective_attachment = current_attachment
if isinstance(effective_attachment, telegram.Location):
body += (
'<a target="_blank" href="https://www.google.com/'
'maps/search/?api=1&query=%s,%s">Location</a>'
% (
effective_attachment.latitude,
effective_attachment.longitude,
)
)
attachment_data = asyncio.run(
self._process_telegram_attachment(effective_attachment)
)
if attachment_data:
attachments.append(attachment_data)
if len(body) > 0 or attachments:
author = self._get_author(chat.gateway_id, update)
new_message = chat.message_post(
body=body,
author_id=author._name == "res.partner" and author.id,
gateway_type="telegram",
date=update.message.date.replace(tzinfo=None),
# message_id=update.message.message_id,
subtype_xmlid="mail.mt_comment",
message_type="comment",
attachments=attachments,
)
self._post_process_message(new_message, chat)
related_message_id = (
update.message.reply_to_message and update.message.reply_to_message.id
)
if related_message_id:
related_message = (
self.env["mail.notification"]
.search(
[
("gateway_channel_id", "=", chat.id),
("gateway_message_id", "=", related_message_id),
]
)
.mail_message_id
)
if related_message and related_message.gateway_message_id:
new_related_message = (
self.env[related_message.gateway_message_id.model]
.browse(related_message.gateway_message_id.res_id)
.message_post(
body=body,
author_id=author._name == "res.partner" and author.id,
gateway_type="telegram",
date=update.message.date.replace(tzinfo=None),
# message_id=update.message.message_id,
subtype_xmlid="mail.mt_comment",
message_type="comment",
attachments=attachments,
)
)
new_message.gateway_message_id = new_related_message
self._post_process_reply(related_message)
return new_message
async def _send_telegram(
self,
gateway,
record,
auto_commit=False,
raise_exception=False,
parse_mode=False,
):
bot = self._get_telegram_bot(gateway.token)
await bot.initialize()
chat = await bot.get_chat(record.gateway_channel_id.gateway_channel_token)
message = False
body = self._get_message_body(record)
if body:
message = await chat.send_message(
html2plaintext(body), parse_mode=parse_mode
)
for attachment in record.mail_message_id.attachment_ids:
# Remember that files are limited to 50 Mb on Telegram
# https://core.telegram.org/bots/faq#handling-media
if attachment.mimetype.split("/")[0] == "image":
new_message = await chat.send_photo(BytesIO(attachment.raw))
else:
new_message = await chat.send_document(
BytesIO(attachment.raw),
filename=attachment.name,
)
if not message:
message = new_message
return message
def _send(
self,
gateway,
record,
auto_commit=False,
raise_exception=False,
parse_mode=False,
):
message = False
try:
message = asyncio.run(
self._send_telegram(
gateway,
record,
auto_commit=auto_commit,
raise_exception=raise_exception,
parse_mode=parse_mode,
)
)
except Exception as exc:
buff = StringIO()
traceback.print_exc(file=buff)
_logger.error(buff.getvalue())
if raise_exception:
raise MailDeliveryException(
_("Unable to send the telegram message"), exc
) from None
else:
_logger.warning(
"Issue sending message with id {}: {}".format(record.id, exc)
)
record.sudo().write(
{
"notification_status": "exception",
"failure_reason": exc,
"failure_type": "unknown",
}
)
if message:
record.sudo().write(
{
"notification_status": "sent",
"failure_reason": False,
"failure_type": False,
"gateway_message_id": message.id,
}
)
self.env["bus.bus"]._sendone(
record.gateway_channel_id,
"mail.message/insert",
{
"id": record.mail_message_id.id,
"gateway_type": record.mail_message_id.gateway_type,
},
)
if auto_commit is True:
# pylint: disable=invalid-commit
self.env.cr.commit()
def _get_author_vals(self, gateway, update):
names = []
for name in [
update.message.from_user.first_name or False,
update.message.from_user.last_name or False,
]:
if name:
names.append(name)
return {
"name": " ".join(names),
"gateway_id": gateway.id,
"gateway_token": str(update.message.from_user.id),
}
def _get_author(self, gateway, update):
author_id = update.message.from_user.id
if author_id:
gateway_partner = self.env["res.partner.gateway.channel"].search(
[
("gateway_id", "=", gateway.id),
("gateway_token", "=", str(author_id)),
]
)
if gateway_partner:
return gateway_partner.partner_id
guest = self.env["mail.guest"].search(
[
("gateway_id", "=", gateway.id),
("gateway_token", "=", str(author_id)),
]
)
if guest:
return guest
return self.env["mail.guest"].create(self._get_author_vals(gateway, update))
return super()._get_author(gateway, update)
async def _async_update_content_after_hook(self, channel, message):
bot = self._get_telegram_bot(channel.gateway_id.token)
await bot.initialize()
await bot.edit_message_text(
html2plaintext(message.body),
chat_id=int(channel.gateway_channel_token),
message_id=int(
message.gateway_notification_ids.mapped("gateway_message_id")[0]
),
)
def _update_content_after_hook(self, channel, message):
asyncio.run(self._async_update_content_after_hook(channel, message))

View File

@ -10,9 +10,14 @@ Configure Odoo
~~~~~~~~~~~~~~
1. Access on debug mode
2. Access `Settings > Technical Settings > Email > Mail Broker`.
3. Create a bot and assign the token. Mark it as `Show on App`
4. Press on `Generate webhook` in order to Open the webhook
2. Access `Settings > Technical Settings > Email > Mail Gateway`.
3. Access Telegram and start a converstation with BotFather.
4. Create a bot using the command /newbot. The system will ask for a bot name. Remember that it needs to end with the word bot.
5. Copy the token to access the HTTP API to the token field.
6. Define Webhook key an webhook secret of your choice in its corresponding field, in order to secure the connection.
7. Press save button and the integrate webhook smart button will appear.
8. Press the Integrate webhook button.
9. If you want to add an extra layer of security, you can check Has New Channel Security and define a Telegram security key. New chats will be created only with the command /start SECURITY_KEY.
Limitations
~~~~~~~~~~~

View File

@ -1 +1 @@
- AEODOO
This work has been funded by AEOdoo (Asociación Española de Odoo - https://www.aeodoo.org)

View File

@ -1,3 +1,3 @@
1. Access `Broker`
1. Access `Gateway`
2. Wait until someone starts a conversation with your bot.
3. Now you will be able to respond and receive messages to this person.

View File

@ -1 +0,0 @@
from . import mail_broker_service

View File

@ -1,246 +0,0 @@
# Copyright 2018 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import base64
import logging
import mimetypes
import traceback
from io import BytesIO, StringIO
from odoo import _
from odoo.http import request
from odoo.tools import html2plaintext
from odoo.tools.mimetypes import guess_mimetype
from odoo.addons.base.models.ir_mail_server import MailDeliveryException
from odoo.addons.component.core import Component
_logger = logging.getLogger(__name__)
try:
import telegram
from lottie.exporters import exporters
from lottie.importers import importers
except (ImportError, IOError) as err:
_logger.debug(err)
class MailBrokerTelegramService(Component):
_inherit = "mail.broker.base.service"
_name = "mail.broker.telegram.service"
_usage = "telegram"
_description = "Telegram Broker services"
def _get_telegram_bot(self, token=False):
return telegram.Bot(token or self.collection.token)
def _set_webhook(self):
bot = self._get_telegram_bot()
bot.setWebhook(
url=self.collection.webhook_url,
api_kwargs={"secret_token": self.collection.webhook_secret},
)
super()._set_webhook()
def _remove_webhook(self):
bot = self._get_telegram_bot()
webhookinfo = bot.get_webhook_info()
if webhookinfo.url:
bot.delete_webhook(drop_pending_updates=False)
super()._remove_webhook()
def _verify_update(self, bot_data, kwargs):
if not bot_data["webhook_secret"]:
return True
return (
request.httprequest.headers.get("X-Telegram-Bot-Api-Secret-Token")
== bot_data["webhook_secret"]
)
def _get_channel_vals(self, broker, token, update):
result = super()._get_channel_vals(broker, token, update)
names = []
for name in [
update.message.chat.first_name or False,
update.message.chat.last_name or False,
update.message.chat.description or False,
update.message.chat.title or False,
]:
if name:
names.append(name)
result["name"] = " ".join(names)
return result
def _preprocess_update(self, broker, update):
for entity in update.message.entities:
if not entity.offset == 0:
continue
if not entity.type == "bot_command":
continue
command = update.message.parse_entity(entity).split("/")[1]
if hasattr(self, "_command_%s" % (command)):
return getattr(self, "_command_%s" % (command))(broker, update)
return False
def _command_start(self, broker, update):
if (
not broker.has_new_channel_security
or update.message.text == "/start %s" % broker.telegram_security_key
):
return self._get_channel(broker, update.message.chat_id, update, True)
return True
def _receive_update(self, broker, update):
telegram_update = telegram.Update.de_json(
update, self._get_telegram_bot(token=broker.token)
)
if self._preprocess_update(broker, telegram_update):
return
chat = self._get_channel(
broker, telegram_update.message.chat_id, telegram_update
)
if not chat:
return
return self._process_update(chat, telegram_update)
def _telegram_sticker_input_options(self):
return {}
def _telegram_sticker_output_options(self):
return {}
def _get_telegram_attachment_name(self, attachment):
if hasattr(attachment, "title"):
if attachment.title:
return attachment.title
if hasattr(attachment, "file_name"):
if attachment.file_name:
return attachment.file_name
if isinstance(attachment, telegram.Sticker):
return attachment.set_name or attachment.emoji or "sticker"
if isinstance(attachment, telegram.Contact):
return attachment.first_name
return attachment.file_id
def _process_telegram_attachment(self, attachment):
if isinstance(
attachment,
(
telegram.Game,
telegram.Invoice,
telegram.Location,
telegram.SuccessfulPayment,
telegram.Venue,
),
):
return
if isinstance(attachment, telegram.Contact):
data = attachment.vcard.encode("utf-8")
else:
data = bytes(attachment.get_file().download_as_bytearray())
file_name = self._get_telegram_attachment_name(attachment)
if isinstance(attachment, telegram.Sticker):
suf = "tgs"
for p in importers:
if suf in p.extensions:
importer = p
break
exporter = exporters.get("gif")
inpt = BytesIO(data)
an = importer.process(inpt, **self._telegram_sticker_input_options())
output_options = self._telegram_sticker_output_options()
fps = output_options.pop("fps", False)
if fps:
an.frame_rate = fps
output = BytesIO()
exporter.process(an, output, **output_options)
data = output.getvalue()
mimetype = guess_mimetype(data)
return (
"{}{}".format(file_name, mimetypes.guess_extension(mimetype)),
base64.b64encode(data).decode("utf-8"),
mimetype,
)
def _process_update(self, chat, update):
chat.ensure_one()
body = ""
attachments = []
if update.message.text_html:
body = update.message.text_html
if update.message.effective_attachment:
effective_attachment = update.message.effective_attachment
if isinstance(effective_attachment, list):
current_attachment = effective_attachment[0]
for attachment in effective_attachment[1:]:
if getattr(attachment, "file_size", 0) > getattr(
current_attachment, "file_size", 0
):
current_attachment = attachment
effective_attachment = current_attachment
if isinstance(effective_attachment, telegram.Location):
body += (
'<a target="_blank" href="https://www.google.com/'
'maps/search/?api=1&query=%s,%s">Location</a>'
% (
effective_attachment.latitude,
effective_attachment.longitude,
)
)
attachment_data = self._process_telegram_attachment(effective_attachment)
if attachment_data:
attachments.append(attachment_data)
if len(body) > 0 or attachments:
return chat.message_post_broker(
body=body,
broker_type="telegram",
date=update.message.date.replace(tzinfo=None),
message_id=update.message.message_id,
subtype="mt_comment",
attachments=attachments,
)
def _send(self, record, auto_commit=False, raise_exception=False, parse_mode=False):
message = False
try:
bot = self._get_telegram_bot()
chat = bot.get_chat(record.channel_id.token)
if record.body:
message = chat.send_message(
html2plaintext(record.body), parse_mode=parse_mode
)
for attachment in record.attachment_ids:
if attachment.mimetype.split("/")[0] == "image":
new_message = chat.send_photo(
BytesIO(base64.b64decode(attachment.datas))
)
else:
new_message = chat.send_document(
BytesIO(base64.b64decode(attachment.datas)),
filename=attachment.name,
)
if not message:
message = new_message
except Exception as exc:
buff = StringIO()
traceback.print_exc(file=buff)
_logger.error(buff.getvalue())
if raise_exception:
raise MailDeliveryException(
_("Unable to send the telegram message"), exc
)
else:
_logger.warning(
"Issue sending message with id {}: {}".format(record.id, exc)
)
record.write({"state": "exception", "failure_reason": exc})
if message:
record.write(
{
"state": "sent",
"message_id": message.message_id,
"failure_reason": False,
}
)
if auto_commit is True:
# pylint: disable=invalid-commit
self.env.cr.commit()

View File

@ -1,160 +1,82 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="Capa_1"
data-name="Capa 1"
viewBox="0 0 200.33 200"
id="Livello_1"
data-name="Livello 1"
viewBox="0 0 240 240"
version="1.1"
sodipodi:docname="icon.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
inkscape:export-filename="/home/operador/pyworkspace12/social/mail_telegram_broker/static/description/icon.png"
inkscape:export-xdpi="95.841858"
inkscape:export-ydpi="95.841858">
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview20"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="2.9958333"
inkscape:cx="116.16134"
inkscape:cy="120"
inkscape:window-width="1858"
inkscape:window-height="1016"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="Livello_1" />
<defs
id="defs7">
<linearGradient
id="linear-gradient"
x1="120"
y1="240"
x2="120"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
stop-color="#1d93d2"
id="stop2" />
<stop
offset="1"
stop-color="#38b0e3"
id="stop4" />
</linearGradient>
</defs>
<title
id="title9">Telegram_logo</title>
<rect
style="fill:#000000;fill-opacity:1;stroke-width:3.60801"
id="rect965"
width="240"
height="240"
x="0"
y="0" />
<path
d="M81.229,128.772l14.237,39.406s1.78,3.687,3.686,3.687,30.255-29.492,30.255-29.492l31.525-60.89L81.737,118.6Z"
fill="#c8daea"
id="path13" />
<path
d="M100.106,138.878l-2.733,29.046s-1.144,8.9,7.754,0,17.415-15.763,17.415-15.763"
fill="#a9c6d8"
id="path15" />
<path
d="M81.486,130.178,52.2,120.636s-3.5-1.42-2.373-4.64c.232-.664.7-1.229,2.1-2.2,6.489-4.523,120.106-45.36,120.106-45.36s3.208-1.081,5.1-.362a2.766,2.766,0,0,1,1.885,2.055,9.357,9.357,0,0,1,.254,2.585c-.009.752-.1,1.449-.169,2.542-.692,11.165-21.4,94.493-21.4,94.493s-1.239,4.876-5.678,5.043A8.13,8.13,0,0,1,146.1,172.5c-8.711-7.493-38.819-27.727-45.472-32.177a1.27,1.27,0,0,1-.546-.9c-.093-.469.417-1.05.417-1.05s52.426-46.6,53.821-51.492c.108-.379-.3-.566-.848-.4-3.482,1.281-63.844,39.4-70.506,43.607A3.21,3.21,0,0,1,81.486,130.178Z"
fill="#fff"
id="path17" />
<metadata
id="metadata24">
id="metadata919">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>icon</dc:title>
<dc:title>Telegram_logo</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1853"
inkscape:window-height="1025"
id="namedview22"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-text-baseline="true"
inkscape:zoom="1.668772"
inkscape:cx="32.080636"
inkscape:cy="54.692004"
inkscape:window-x="67"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="layer2">
<sodipodi:guide
position="51.271186,126.37712"
orientation="-0.70710678,0.70710678"
id="guide40"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="118.65012,168.08767"
orientation="-0.70710678,0.70710678"
id="guide42"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="138.02966,143.00847"
orientation="-0.70710678,0.70710678"
id="guide46"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="137.92373,118.22034"
orientation="-0.70710678,0.70710678"
id="guide48"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="156.99152,93.008474"
orientation="-0.70710678,0.70710678"
id="guide50"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="139.19491,46.822034"
orientation="-0.70710678,0.70710678"
id="guide52"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="33.070725,104.38065"
orientation="-0.70710678,0.70710678"
id="guide839"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="136.62741,46.516241"
orientation="-0.70710678,0.70710678"
id="guide841"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,0,255)" />
</sodipodi:namedview>
<defs
id="defs4">
<style
id="style2">.cls-1{fill:none;}.cls-2{fill:#3b588f;}.cls-3{fill:#070308;opacity:0.4;}.cls-4{fill:#fff;}</style>
</defs>
<title
id="title6">icon</title>
<rect
id="_Sector_"
data-name="&lt;Sector&gt;"
class="cls-1"
width="200"
height="200" />
<rect
class="cls-2"
x="0.33000001"
width="200"
height="200"
id="rect9"
ry="5.6928086"
y="0"
style="fill:#179cde;fill-opacity:1" />
<g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="Layer 1"
style="display:inline" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 3">
<path
style="fill:#000000;fill-opacity:0.29051986;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 34.149962,94.540113 0.33939645,128.35068 c 0,22.04621 -0.005125,-37.02851 -0.005125,66.40594 0,1.19849 0.87634879,2.80824 1.47559179,3.40749 0,0 1.6732367,1.97005 4.5570938,1.85769 l 83.7317707,-0.009 47.844652,-47.84466 4.88661,-95.023805 z"
id="path843"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccc" />
</g>
<g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="Layer 2"
style="display:inline">
<path
id="path828"
d="m 158.96325,57.709612 -19.05983,89.885658 c -1.43794,6.34387 -5.18788,7.92279 -10.51673,4.93412 l -29.04085,-21.4 -14.012923,13.47721 c -1.550725,1.55072 -2.84769,2.84769 -5.836362,2.84769 l 2.086431,-29.57656 53.824254,-48.636368 c 2.34019,-2.08643 -0.50751,-3.24242 -3.63715,-1.15599 L 66.229882,109.98314 37.583764,101.01713 c -6.23109,-1.945458 -6.34388,-6.231088 1.29697,-9.219758 L 150.92767,48.63082 c 5.18788,-1.945453 9.72728,1.155997 8.03558,9.078792 z"
inkscape:connector-curvature="0"
style="display:inline;fill:#ffffff;fill-opacity:1;stroke-width:0.28195" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Mail Telegram Broker</title>
<title>Mail Telegram Gateway</title>
<style type="text/css">
/*
@ -360,16 +359,16 @@ ul.auto-toc {
</style>
</head>
<body>
<div class="document" id="mail-telegram-broker">
<h1 class="title">Mail Telegram Broker</h1>
<div class="document" id="mail-telegram-gateway">
<h1 class="title">Mail Telegram Gateway</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:57eb6593eeb39836977570fe7bdf9b1824ade94c6b1dfc7a161fc57f2c8e564d
!! source digest: sha256:789836d72dab5a0af634604d4bc88ec56bf51c4134b20099438f14dfaaf9ca76
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/social/tree/16.0/mail_broker_telegram"><img alt="OCA/social" src="https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/social-16-0/social-16-0-mail_broker_telegram"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/social&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/social/tree/16.0/mail_gateway_telegram"><img alt="OCA/social" src="https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/social-16-0/social-16-0-mail_gateway_telegram"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/social&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module allows to respond telegram chats as a telegram bot.</p>
<p>This way, a group of users can respond customers or any other set
of partners in an integrated way.</p>
@ -409,9 +408,14 @@ to review again when one has responded.</p>
<h2><a class="toc-backref" href="#toc-entry-3">Configure Odoo</a></h2>
<ol class="arabic simple">
<li>Access on debug mode</li>
<li>Access <cite>Settings &gt; Technical Settings &gt; Email &gt; Mail Broker</cite>.</li>
<li>Create a bot and assign the token. Mark it as <cite>Show on App</cite></li>
<li>Press on <cite>Generate webhook</cite> in order to Open the webhook</li>
<li>Access <cite>Settings &gt; Technical Settings &gt; Email &gt; Mail Gateway</cite>.</li>
<li>Access Telegram and start a converstation with BotFather.</li>
<li>Create a bot using the command /newbot. The system will ask for a bot name. Remember that it needs to end with the word bot.</li>
<li>Copy the token to access the HTTP API to the token field.</li>
<li>Define Webhook key an webhook secret of your choice in its corresponding field, in order to secure the connection.</li>
<li>Press save button and the integrate webhook smart button will appear.</li>
<li>Press the Integrate webhook button.</li>
<li>If you want to add an extra layer of security, you can check Has New Channel Security and define a Telegram security key. New chats will be created only with the command /start SECURITY_KEY.</li>
</ol>
</div>
<div class="section" id="limitations">
@ -438,7 +442,7 @@ to you on an external process with the following code.</p>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-5">Usage</a></h1>
<ol class="arabic simple">
<li>Access <cite>Broker</cite></li>
<li>Access <cite>Gateway</cite></li>
<li>Wait until someone starts a conversation with your bot.</li>
<li>Now you will be able to respond and receive messages to this person.</li>
</ol>
@ -448,7 +452,7 @@ to you on an external process with the following code.</p>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/social/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/social/issues/new?body=module:%20mail_broker_telegram%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<a class="reference external" href="https://github.com/OCA/social/issues/new?body=module:%20mail_gateway_telegram%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
@ -457,6 +461,7 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
<h2><a class="toc-backref" href="#toc-entry-8">Authors</a></h2>
<ul class="simple">
<li>Creu Blanca</li>
<li>Dixmit</li>
</ul>
</div>
<div class="section" id="contributors">
@ -468,9 +473,7 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
</div>
<div class="section" id="other-credits">
<h2><a class="toc-backref" href="#toc-entry-10">Other credits</a></h2>
<ul class="simple">
<li>AEODOO</li>
</ul>
<p>This work has been funded by AEOdoo (Asociación Española de Odoo - <a class="reference external" href="https://www.aeodoo.org">https://www.aeodoo.org</a>)</p>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-11">Maintainers</a></h2>
@ -479,7 +482,7 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/social/tree/16.0/mail_broker_telegram">OCA/social</a> project on GitHub.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/social/tree/16.0/mail_gateway_telegram">OCA/social</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-inherit="mail.MessageViewNotification" t-inherit-mode="extension">
<xpath expr="//div" position="after">
<i
class="fa fa-paper-plane text-info"
t-if="messageView.message.gateway_type == 'telegram' and messageView.message.notifications.length == 0"
/>
</xpath>
</t>
</templates>

View File

@ -0,0 +1,21 @@
/** @odoo-module **/
import {registerPatch} from "@mail/model/model_core";
registerPatch({
name: "Message",
fields: {
avatarUrl: {
compute() {
if (
!this.author &&
!this.guestAuthor &&
this.gateway_type === "telegram"
) {
return "/mail_gateway_telegram/static/description/icon.png";
}
return this._super();
},
},
},
});

View File

@ -0,0 +1,25 @@
/** @odoo-module **/
import {registerPatch} from "@mail/model/model_core";
registerPatch({
name: "MessageView",
fields: {
notificationIconClassName: {
compute() {
if (this.message && this.message.gateway_type === "telegram") {
return "fa fa-paper-plane";
}
return this._super();
},
},
failureNotificationIconClassName: {
compute() {
if (this.message && this.message.gateway_type === "telegram") {
return "fa fa-paper-plane";
}
return this._super();
},
},
},
});

View File

@ -0,0 +1,25 @@
/** @odoo-module **/
import {registerPatch} from "@mail/model/model_core";
registerPatch({
name: "Notification",
fields: {
iconClass: {
compute() {
if (
this.notification_type === "gateway" &&
this.gateway_type === "telegram"
) {
switch (this.notification_status) {
case "sent":
return "fa fa-paper-plane";
case "exception":
return "fa fa-paper-plane text-danger";
}
}
return this._super();
},
},
},
});

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-extend="mail.widget.Thread.Message">
<t t-jquery=".o_thread_tooltip_broker_container" t-operation="append">
<i
t-att-class="'o_thread_message_broker_' + message.customer_status + ' fa fa-whatsapp fa-lg'"
t-if="message.broker_type == 'whatsapp'"
/>
</t>
</t>
</templates>

View File

@ -1 +1 @@
from . import test_mail_broker_telegram
from . import test_mail_gateway_telegram

Binary file not shown.

View File

@ -1,266 +0,0 @@
# Copyright 2022 CreuBlanca
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import telegram
from mock import patch
from telegram.ext import ExtBot
from odoo.addons.mail_broker.tests.common import MailBrokerComponentRegistryTestCase
class MyBot(ExtBot):
def _validate_token(self, *args, **kwargs):
return
def setWebhook(self, *args, **kwargs):
return {}
def get_webhook_info(self, *args, **kwargs):
return telegram.WebhookInfo.de_json(
{"pending_update_count": 0, "url": False, "has_custom_certificate": False},
self,
)
class TestMailBrokerTelegram(MailBrokerComponentRegistryTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls._load_module_components(cls, "mail_broker_telegram")
cls.webhook = "demo_hook"
cls.broker = cls.env["mail.broker"].create(
{"name": "broker", "broker_type": "telegram", "token": "token"}
)
cls.password = "my_new_password"
cls.message_01 = {
"update_id": 1,
"message": {
"message_id": 1,
"from": {
"id": 1,
"is_bot": False,
"first_name": "Demo",
"last_name": "Demo",
"language_code": "en",
},
"chat": {
"id": 1,
"first_name": "Demo",
"last_name": "Demo",
"type": "private",
},
"date": 1639666351,
"text": "Hi Friend!",
},
}
cls.message_02 = {
"update_id": 1,
"message": {
"message_id": 2,
"from": {
"id": 1,
"is_bot": False,
"first_name": "Demo",
"last_name": "Demo",
"language_code": "en",
},
"chat": {
"id": 1,
"first_name": "Demo",
"last_name": "Demo",
"type": "private",
},
"entities": [{"type": "bot_command", "offset": 0, "length": 6}],
"date": 1639666351,
"text": "/start",
},
}
cls.message_03 = {
"update_id": 1,
"message": {
"message_id": 3,
"from": {
"id": 1,
"is_bot": False,
"first_name": "Demo",
"last_name": "Demo",
"language_code": "en",
},
"chat": {
"id": 1,
"first_name": "Demo",
"last_name": "Demo",
"type": "private",
},
"entities": [{"type": "bot_command", "offset": 0, "length": 6}],
"date": 1639666351,
"text": "/start %s%s" % (cls.password, cls.password),
},
}
cls.message_04 = {
"update_id": 1,
"message": {
"message_id": 4,
"from": {
"id": 1,
"is_bot": False,
"first_name": "Demo",
"last_name": "Demo",
"language_code": "en",
},
"chat": {
"id": 1,
"first_name": "Demo",
"last_name": "Demo",
"type": "private",
},
"entities": [{"type": "bot_command", "offset": 0, "length": 6}],
"date": 1639666351,
"text": "/start %s" % cls.password,
},
}
def test_webhook_management(self):
self.broker.webhook_key = self.webhook
self.broker.flush()
self.assertTrue(self.broker.can_set_webhook)
with patch.object(telegram, "Bot", MyBot):
self.broker.set_webhook()
self.assertEqual(self.broker.integrated_webhook_state, "integrated")
with patch.object(telegram, "Bot", MyBot):
self.broker.update_webhook()
self.assertEqual(self.broker.integrated_webhook_state, "integrated")
with patch.object(telegram, "Bot", MyBot):
self.broker.remove_webhook()
self.assertFalse(self.broker.integrated_webhook_state)
def set_message(self, message, webhook):
with self.broker.work_on(self.broker._name) as work:
work.component(usage=self.broker.broker_type).post_update(
webhook, **message
)
def test_webhook_unsecure_channel(self):
self.broker.webhook_key = self.webhook
self.broker.flush()
self.assertTrue(self.broker.can_set_webhook)
with patch.object(telegram, "Bot", MyBot):
self.broker.set_webhook()
self.assertFalse(
self.env["mail.broker.channel"].search([("broker_id", "=", self.broker.id)])
)
slots = self.env["mail.broker"].broker_fetch_slot()
for slot in slots:
if slot["id"] == self.broker.id:
break
self.assertFalse(slot["threads"])
with patch("telegram.Bot") as mck:
mck.return_value = ExtBot
self.set_message(self.message_01, self.webhook)
mck.assert_called()
chat = self.env["mail.broker.channel"].search(
[("broker_id", "=", self.broker.id)]
)
self.assertTrue(chat)
self.assertTrue(chat.message_ids)
self.assertTrue(chat.message_fetch())
slots = self.env["mail.broker"].broker_fetch_slot()
for slot in slots:
if slot["id"] == self.broker.id:
break
self.assertTrue(slot["threads"])
self.assertTrue(self.broker.channel_search("Demo"))
self.assertFalse(self.broker.channel_search("Not Demo"))
def test_webhook_unsecure_channel_start(self):
self.broker.webhook_key = self.webhook
self.broker.flush()
self.assertTrue(self.broker.can_set_webhook)
with patch.object(telegram, "Bot", MyBot):
self.broker.set_webhook()
self.assertFalse(
self.env["mail.broker.channel"].search([("broker_id", "=", self.broker.id)])
)
with patch("telegram.Bot") as mck:
mck.return_value = ExtBot
self.set_message(self.message_02, self.webhook)
mck.assert_called()
chat = self.env["mail.broker.channel"].search(
[("broker_id", "=", self.broker.id)]
)
self.assertTrue(chat)
self.assertFalse(chat.message_ids)
self.assertFalse(chat.message_fetch())
with patch("telegram.Bot") as mck:
mck.return_value = ExtBot
self.set_message(self.message_01, self.webhook)
mck.assert_called()
chat.refresh()
self.assertTrue(chat.message_ids)
def test_webhook_secure_channel(self):
self.broker.webhook_key = self.webhook
self.broker.flush()
self.assertTrue(self.broker.can_set_webhook)
with patch.object(telegram, "Bot", MyBot):
self.broker.set_webhook()
self.broker.write(
{"has_new_channel_security": True, "telegram_security_key": self.password}
)
self.broker.flush()
self.assertFalse(
self.env["mail.broker.channel"].search([("broker_id", "=", self.broker.id)])
)
with patch("telegram.Bot") as mck:
mck.return_value = ExtBot
self.set_message(self.message_01, self.webhook)
mck.assert_called()
chat = self.env["mail.broker.channel"].search(
[("broker_id", "=", self.broker.id)]
)
self.assertFalse(chat)
with patch("telegram.Bot") as mck:
mck.return_value = ExtBot
self.set_message(self.message_02, self.webhook)
mck.assert_called()
chat = self.env["mail.broker.channel"].search(
[("broker_id", "=", self.broker.id)]
)
self.assertFalse(chat)
with patch("telegram.Bot") as mck:
mck.return_value = ExtBot
self.set_message(self.message_03, self.webhook)
mck.assert_called()
chat = self.env["mail.broker.channel"].search(
[("broker_id", "=", self.broker.id)]
)
self.assertFalse(chat)
with patch("telegram.Bot") as mck:
mck.return_value = ExtBot
self.set_message(self.message_04, self.webhook)
mck.assert_called()
chat = self.env["mail.broker.channel"].search(
[("broker_id", "=", self.broker.id)]
)
self.assertTrue(chat)
self.assertFalse(chat.message_ids)
with patch("telegram.Bot") as mck:
mck.return_value = ExtBot
self.set_message(self.message_01, self.webhook)
mck.assert_called()
chat.refresh()
self.assertTrue(chat.message_ids)
def test_webhook_no_webhook(self):
self.assertFalse(
self.env["mail.broker.channel"].search([("broker_id", "=", self.broker.id)])
)
self.set_message(self.message_01, self.webhook + self.webhook)
self.assertFalse(
self.env["mail.broker.channel"].search([("broker_id", "=", self.broker.id)])
)

View File

@ -0,0 +1,827 @@
# Copyright 2022 CreuBlanca
# Copyright 2024 Dixmit
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import json
import time
from datetime import datetime
from unittest.mock import patch
import telegram
from telegram.ext import ExtBot
from odoo.tests.common import tagged
from odoo.tools import file_open, mute_logger
from odoo.addons.mail_gateway.tests.common import MailGatewayTestCase
class AttachmentFile:
def __init__(self, file=False):
self.file = file
async def download_as_bytearray(self):
if not self.file:
return b"A" * 3138
return file_open(self.file, mode="rb").read()
def getMyBot(message_id=1234, file=False):
class MyBot(ExtBot):
async def get_file(self, file_id, *args, **kwargs):
return AttachmentFile(file)
def _validate_token(self, *args, **kwargs):
return
async def initialize(self, *args, **kwargs):
return
async def setWebhook(self, *args, **kwargs):
return {}
async def get_webhook_info(self, *args, **kwargs):
return telegram.WebhookInfo.de_json(
{
"pending_update_count": 0,
"url": False,
"has_custom_certificate": False,
},
self,
)
async def get_chat(self, chat_id, *args, **kwargs):
return telegram.Chat.de_json(
{
"id": chat_id,
"type": "private",
},
self,
)
async def _send_message(self, endpoint, data, *args, **kwargs):
return telegram.Message.de_json(
{
"date": time.mktime(datetime.now().timetuple()),
"message_id": message_id,
"chat": {
"id": data["chat_id"],
"type": "private",
},
},
self,
)
return MyBot
@tagged("-at_install", "post_install")
class TestMailGatewayTelegram(MailGatewayTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.webhook = "demo_hook"
cls.gateway = cls.env["mail.gateway"].create(
{"name": "gateway", "gateway_type": "telegram", "token": "token"}
)
cls.password = "my_new_password"
cls.gateway_token = "12341234"
cls.message_01 = {
"update_id": 1,
"message": {
"message_id": 1,
"from": {
"id": cls.gateway_token,
"is_bot": False,
"first_name": "Demo",
"last_name": "Demo",
"language_code": "en",
},
"chat": {
"id": cls.gateway_token,
"first_name": "Demo",
"last_name": "Demo",
"type": "private",
},
"date": 1639666351,
"text": "Hi Friend!",
},
}
cls.message_02 = {
"update_id": 1,
"message": {
"message_id": 2,
"from": {
"id": cls.gateway_token,
"is_bot": False,
"first_name": "Demo",
"last_name": "Demo",
"language_code": "en",
},
"chat": {
"id": cls.gateway_token,
"first_name": "Demo",
"last_name": "Demo",
"type": "private",
},
"entities": [{"type": "bot_command", "offset": 0, "length": 6}],
"date": 1639666351,
"text": "/start",
},
}
cls.message_03 = {
"update_id": 1,
"message": {
"message_id": 3,
"from": {
"id": cls.gateway_token,
"is_bot": False,
"first_name": "Demo",
"last_name": "Demo",
"language_code": "en",
},
"chat": {
"id": cls.gateway_token,
"first_name": "Demo",
"last_name": "Demo",
"type": "private",
},
"entities": [{"type": "bot_command", "offset": 0, "length": 6}],
"date": 1639666351,
"text": "/start %s%s" % (cls.password, cls.password),
},
}
cls.message_04 = {
"update_id": 1,
"message": {
"message_id": 4,
"from": {
"id": cls.gateway_token,
"is_bot": False,
"first_name": "Demo",
"last_name": "Demo",
"language_code": "en",
},
"chat": {
"id": cls.gateway_token,
"first_name": "Demo",
"last_name": "Demo",
"type": "private",
},
"entities": [{"type": "bot_command", "offset": 0, "length": 6}],
"date": 1639666351,
"text": "/start %s" % cls.password,
},
}
cls.message_05 = {
"update_id": 5,
"message": {
"message_id": 5,
"from": {
"id": cls.gateway_token,
"is_bot": False,
"first_name": "Demo",
"last_name": "Demo",
"language_code": "en",
},
"chat": {
"id": cls.gateway_token,
"first_name": "Demo",
"last_name": "Demo",
"type": "private",
},
"date": 1639666351,
"document": {
"title": "icon.svg",
"file_name": "icon.svg",
"mime_type": "image/svg+xml",
"file_id": "MY_FILE_ID",
"file_unique_id": "MY_FILE_UNIQUe_ID",
"file_size": 3138,
},
},
}
cls.reply_to_message_id = 500
cls.message_06 = {
"update_id": 1,
"message": {
"message_id": 6,
"from": {
"id": cls.gateway_token,
"is_bot": False,
"first_name": "Demo",
"last_name": "Demo",
"language_code": "en",
},
"chat": {
"id": cls.gateway_token,
"first_name": "Demo",
"last_name": "Demo",
"type": "private",
},
"reply_to_message": {
"message_id": cls.reply_to_message_id,
"from": {
"id": cls.gateway_token,
"is_bot": False,
"first_name": "Demo",
"last_name": "Demo",
"language_code": "en",
},
"chat": {
"id": cls.gateway_token,
"first_name": "Demo",
"last_name": "Demo",
"type": "private",
},
"date": 1639666351,
},
"date": 1639666351,
"text": "What do you want to do?",
},
}
cls.message_07 = {
"update_id": 7,
"message": {
"message_id": 7,
"from": {
"id": cls.gateway_token,
"is_bot": False,
"first_name": "Demo",
"last_name": "Demo",
"language_code": "en",
},
"chat": {
"id": cls.gateway_token,
"first_name": "Demo",
"last_name": "Demo",
"type": "private",
},
"date": 1639666351,
"sticker": {
"width": 512,
"height": 512,
"emoji": "<EFBFBD><EFBFBD><EFBFBD><EFBFBD>",
"set_name": "DrugStore",
"is_animated": True,
"is_video": False,
"type": "regular",
"thumbnail": {
"file_id": "FILE_ID",
"file_unique_id": "FILE_UNIQUE_ID",
"file_size": 4662,
"width": 128,
"height": 128,
},
"thumb": {
"file_id": "FILE_ID",
"file_unique_id": "FILE_UNIQUE_ID",
"file_size": 4662,
"width": 128,
"height": 128,
},
"file_id": "FILE_ID",
"file_unique_id": "FILE_UNIQUE_ID",
"file_size": 10094,
},
},
}
cls.partner = cls.env["res.partner"].create({"name": "Demo"})
def test_webhook_management(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch("telegram.Bot", getMyBot()):
self.gateway.set_webhook()
self.assertEqual(self.gateway.integrated_webhook_state, "integrated")
with patch("telegram.Bot", getMyBot()):
self.gateway.update_webhook()
self.assertEqual(self.gateway.integrated_webhook_state, "integrated")
with patch("telegram.Bot", getMyBot()):
self.gateway.remove_webhook()
self.assertFalse(self.gateway.integrated_webhook_state)
def set_message(self, message, webhook, timeout=12):
self.url_open(
"/gateway/{}/{}/update".format(self.gateway.gateway_type, webhook),
data=json.dumps(message),
headers={"Content-Type": "application/json"},
timeout=timeout,
# We need to increase the timeout to avoid the test to fail on sticker....
)
def test_webhook_unsecure_channel(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch("telegram.Bot", getMyBot()):
self.gateway.set_webhook()
self.assertFalse(
self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
)
with patch("telegram.Bot", ExtBot):
self.set_message(self.message_01, self.webhook)
chat = self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
self.assertTrue(chat)
self.assertTrue(chat.message_ids)
def test_webhook_unsecure_channel_start(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch.object(telegram, "Bot", getMyBot()):
self.gateway.set_webhook()
self.assertFalse(
self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
)
with patch("telegram.Bot", ExtBot):
self.set_message(self.message_02, self.webhook)
chat = self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
self.assertTrue(chat)
self.assertFalse(chat.message_ids)
with patch("telegram.Bot", getMyBot()):
self.set_message(self.message_01, self.webhook)
chat.invalidate_recordset()
self.assertTrue(chat.message_ids)
def test_webhook_secure_channel(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch.object(telegram, "Bot", getMyBot()):
self.gateway.set_webhook()
self.gateway.write(
{"has_new_channel_security": True, "telegram_security_key": self.password}
)
self.gateway.flush_recordset()
self.assertFalse(
self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
)
with patch("telegram.Bot", ExtBot):
self.set_message(self.message_01, self.webhook)
chat = self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
self.assertFalse(chat)
with patch("telegram.Bot", ExtBot):
self.set_message(self.message_02, self.webhook)
chat = self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
self.assertFalse(chat)
with patch("telegram.Bot", ExtBot):
self.set_message(self.message_03, self.webhook)
chat = self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
self.assertFalse(chat)
with patch("telegram.Bot", ExtBot):
self.set_message(self.message_04, self.webhook)
chat = self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
self.assertTrue(chat)
self.assertFalse(chat.message_ids)
with patch("telegram.Bot", ExtBot):
self.set_message(self.message_01, self.webhook)
chat.invalidate_recordset()
self.assertTrue(chat.message_ids)
def test_webhook_no_webhook(self):
self.assertFalse(
self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
)
self.set_message(self.message_01, self.webhook + self.webhook)
self.assertFalse(
self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
)
def test_post_message(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch.object(telegram, "Bot", getMyBot()):
self.gateway.set_webhook()
self.assertFalse(
self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
)
with patch.object(telegram, "Bot", getMyBot()):
self.set_message(self.message_02, self.webhook)
channel = self.env["mail.channel"].search(
[("gateway_id", "=", self.gateway.id)]
)
self.assertFalse(
self.env["mail.notification"].search(
[("gateway_channel_id", "=", channel.id)]
)
)
with patch.object(telegram, "Bot", getMyBot()):
channel.message_post(
body="HELLO",
subtype_xmlid="mail.mt_comment",
message_type="comment",
)
self.assertTrue(
self.env["mail.notification"].search(
[("gateway_channel_id", "=", channel.id)]
)
)
def test_post_message_image(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch.object(telegram, "Bot", getMyBot()):
self.gateway.set_webhook()
self.assertFalse(
self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
)
with patch.object(telegram, "Bot", getMyBot()):
self.set_message(self.message_02, self.webhook)
channel = self.env["mail.channel"].search(
[("gateway_id", "=", self.gateway.id)]
)
self.assertFalse(
self.env["mail.notification"].search(
[("gateway_channel_id", "=", channel.id)]
)
)
with patch.object(telegram, "Bot", getMyBot()):
channel.message_post(
attachments=[("demo.png", b"IMAGE")],
subtype_xmlid="mail.mt_comment",
message_type="comment",
)
self.assertTrue(
self.env["mail.notification"].search(
[("gateway_channel_id", "=", channel.id)]
)
)
def test_post_message_error(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch.object(telegram, "Bot", getMyBot()):
self.gateway.set_webhook()
self.assertFalse(
self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
)
with patch.object(telegram, "Bot", getMyBot()):
self.set_message(self.message_02, self.webhook)
channel = self.env["mail.channel"].search(
[("gateway_id", "=", self.gateway.id)]
)
self.assertFalse(
self.env["mail.notification"].search(
[("gateway_channel_id", "=", channel.id)]
)
)
with mute_logger(
"odoo.addons.mail_gateway_telegram.models.mail_gateway_telegram"
):
channel.message_post(
body="My message",
subtype_xmlid="mail.mt_comment",
message_type="comment",
)
notification = self.env["mail.notification"].search(
[("gateway_channel_id", "=", channel.id)]
)
self.assertTrue(notification)
self.assertEqual(notification.notification_status, "exception")
def test_post_message_document(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch.object(telegram, "Bot", getMyBot()):
self.gateway.set_webhook()
self.assertFalse(
self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
)
with patch.object(telegram, "Bot", getMyBot()):
self.set_message(self.message_02, self.webhook)
channel = self.env["mail.channel"].search(
[("gateway_id", "=", self.gateway.id)]
)
self.assertFalse(
self.env["mail.notification"].search(
[("gateway_channel_id", "=", channel.id)]
)
)
with patch.object(telegram, "Bot", getMyBot()):
channel.message_post(
attachments=[("application/pdf", b"PDF")],
subtype_xmlid="mail.mt_comment",
message_type="comment",
)
self.assertTrue(
self.env["mail.notification"].search(
[("gateway_channel_id", "=", channel.id)]
)
)
def test_webhook_attachment(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch("telegram.Bot", getMyBot()):
self.gateway.set_webhook()
self.assertFalse(
self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
)
with patch("telegram.Bot", getMyBot()):
self.set_message(self.message_05, self.webhook)
chat = self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
self.assertTrue(chat)
self.assertTrue(chat.message_ids)
self.assertTrue(chat.message_ids.attachment_ids)
def test_webhook_sticker(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch("telegram.Bot", getMyBot()):
self.gateway.set_webhook()
self.assertFalse(
self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
)
with patch(
"telegram.Bot",
getMyBot(file="addons/mail_gateway_telegram/tests/sticker.tgs"),
):
self.set_message(self.message_07, self.webhook, timeout=30)
chat = self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
self.assertTrue(chat)
self.assertTrue(chat.message_ids)
self.assertTrue(chat.message_ids.attachment_ids)
def test_webhook_reply(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch("telegram.Bot", getMyBot()):
self.gateway.set_webhook()
with patch.object(telegram, "Bot", getMyBot()):
self.set_message(self.message_02, self.webhook)
channel = self.env["mail.channel"].search(
[("gateway_id", "=", self.gateway.id)]
)
self.assertFalse(
self.env["mail.notification"].search(
[("gateway_channel_id", "=", channel.id)]
)
)
# Assign the partner to the channel
self.env["mail.guest.manage"].create(
{
"partner_id": self.partner.id,
"guest_id": self.env["mail.guest"]
.search(
[
("gateway_token", "=", self.gateway_token),
("gateway_id", "=", self.gateway.id),
]
)
.id,
}
).merge_partner()
self.assertTrue(self.partner.gateway_channel_ids)
with patch.object(telegram, "Bot", getMyBot(self.reply_to_message_id)):
new_message = self.partner.message_post(
body="HELLO",
subtype_xmlid="mail.mt_comment",
message_type="comment",
gateway_notifications=[
{
"channel_type": "gateway",
"gateway_channel_id": self.partner.gateway_channel_ids.id,
"partner_id": self.partner.id,
}
],
)
self.assertTrue(new_message.gateway_message_ids)
self.partner.invalidate_recordset()
messages = self.partner.message_ids
with patch.object(telegram, "Bot", getMyBot()):
self.set_message(self.message_06, self.webhook)
# The message should be assigned the the partner
self.partner.invalidate_recordset()
self.assertTrue(self.partner.message_ids - messages)
def test_webhook_reply_new_partner(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch("telegram.Bot", getMyBot()):
self.gateway.set_webhook()
with patch.object(telegram, "Bot", getMyBot()):
self.set_message(self.message_02, self.webhook)
channel = self.env["mail.channel"].search(
[("gateway_id", "=", self.gateway.id)]
)
self.assertFalse(
self.env["mail.notification"].search(
[("gateway_channel_id", "=", channel.id)]
)
)
# Assign the partner to the channel
partner_action = (
self.env["mail.guest.manage"]
.create(
{
"guest_id": self.env["mail.guest"]
.search(
[
("gateway_token", "=", self.gateway_token),
("gateway_id", "=", self.gateway.id),
]
)
.id,
}
)
.create_partner()
)
partner = self.env[partner_action["res_model"]].browse(partner_action["res_id"])
self.assertTrue(partner.gateway_channel_ids)
with patch.object(telegram, "Bot", getMyBot(self.reply_to_message_id)):
new_message = partner.message_post(
body="HELLO",
subtype_xmlid="mail.mt_comment",
message_type="comment",
gateway_notifications=[
{
"channel_type": "gateway",
"gateway_channel_id": partner.gateway_channel_ids.id,
"partner_id": partner.id,
}
],
)
self.assertTrue(new_message.gateway_message_ids)
partner.invalidate_recordset()
messages = partner.message_ids
with patch.object(telegram, "Bot", getMyBot()):
self.set_message(self.message_06, self.webhook)
# The message should be assigned the the partner
partner.invalidate_recordset()
self.assertTrue(partner.message_ids - messages)
def test_link_mail_message(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch("telegram.Bot", getMyBot()):
self.gateway.set_webhook()
with patch.object(telegram, "Bot", getMyBot()):
self.set_message(self.message_01, self.webhook)
messages = self.partner.message_ids
channel = self.env["mail.channel"].search(
[("gateway_id", "=", self.gateway.id)]
)
self.assertTrue(channel.message_ids)
self.env["mail.message.gateway.link"].create(
{
"message_id": channel.message_ids.id,
"resource_ref": "{},{}".format(self.partner._name, self.partner.id),
}
).link_message()
self.assertTrue(self.partner.message_ids - messages)
def test_send_mail_message(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch("telegram.Bot", getMyBot()):
self.gateway.set_webhook()
with patch.object(telegram, "Bot", getMyBot()):
self.set_message(self.message_01, self.webhook)
channel = self.env["mail.channel"].search(
[("gateway_id", "=", self.gateway.id)]
)
self.assertFalse(
self.env["mail.notification"].search(
[("gateway_channel_id", "=", channel.id)]
)
)
# Assign the partner to the channel
partner_action = (
self.env["mail.guest.manage"]
.create(
{
"guest_id": self.env["mail.guest"]
.search(
[
("gateway_token", "=", self.gateway_token),
("gateway_id", "=", self.gateway.id),
]
)
.id,
}
)
.create_partner()
)
partner = self.env[partner_action["res_model"]].browse(partner_action["res_id"])
message = partner.message_post(body="HELLO")
self.assertFalse(
self.env["mail.notification"].search(
[("gateway_channel_id", "=", channel.id)]
)
)
with patch.object(telegram, "Bot", getMyBot()):
self.env["mail.message.gateway.send"].create(
{
"message_id": message.id,
"partner_id": partner.id,
"gateway_channel_id": partner.gateway_channel_ids.id,
}
).send()
self.assertTrue(
self.env["mail.notification"].search(
[("gateway_channel_id", "=", channel.id)]
)
)
def test_channel(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch("telegram.Bot", getMyBot()):
self.gateway.set_webhook()
with patch.object(telegram, "Bot", getMyBot()):
self.set_message(self.message_02, self.webhook)
channel = self.env["mail.channel"].search(
[("gateway_id", "=", self.gateway.id)]
)
channel_info = channel.channel_info()[0]
self.assertEqual(channel_info["gateway"]["id"], self.gateway.id)
self.assertTrue(channel.avatar_128)
def test_message_update(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch.object(telegram, "Bot", getMyBot()):
self.gateway.set_webhook()
self.assertFalse(
self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
)
with patch.object(telegram, "Bot", getMyBot()):
self.set_message(self.message_02, self.webhook)
channel = self.env["mail.channel"].search(
[("gateway_id", "=", self.gateway.id)]
)
self.assertFalse(
self.env["mail.notification"].search(
[("gateway_channel_id", "=", channel.id)]
)
)
with patch.object(telegram, "Bot", getMyBot()):
message = channel.message_post(
body="HELLO",
subtype_xmlid="mail.mt_comment",
message_type="comment",
)
with patch.object(telegram, "Bot", getMyBot()):
channel._message_update_content(message, "New message")
self.assertRegex(message.body, ".*New message.*")
def test_messaging(self):
self.gateway.webhook_key = self.webhook
self.gateway.flush_recordset()
self.assertTrue(self.gateway.can_set_webhook)
with patch.object(telegram, "Bot", getMyBot()):
self.gateway.set_webhook()
self.assertFalse(
self.env["mail.channel"].search([("gateway_id", "=", self.gateway.id)])
)
with patch.object(telegram, "Bot", getMyBot()):
self.set_message(self.message_02, self.webhook)
messaging = self.env.user._init_messaging()
self.assertTrue(messaging["gateways"])
self.assertEqual(1, len(messaging["gateways"]))
self.assertEqual(self.gateway.id, messaging["gateways"][0]["id"])
self.assertTrue("gateway_channels" in messaging["current_partner"])
self.assertEqual(0, len(messaging["current_partner"]["gateway_channels"]))
channel_info = self.partner.mail_partner_format()
self.assertTrue("gateway_channels" in channel_info[self.partner])
self.assertEqual(0, len(channel_info[self.partner]["gateway_channels"]))
# Assign the partner to the channel
self.env["mail.guest.manage"].create(
{
"partner_id": self.partner.id,
"guest_id": self.env["mail.guest"]
.search(
[
("gateway_token", "=", self.gateway_token),
("gateway_id", "=", self.gateway.id),
]
)
.id,
}
).merge_partner()
self.assertTrue(self.partner.gateway_channel_ids)
channel_info = self.partner.mail_partner_format()
self.assertTrue(channel_info[self.partner]["gateway_channels"])
self.assertEqual(1, len(channel_info[self.partner]["gateway_channels"]))

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2021 Creu Blanca
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record model="ir.ui.view" id="mail_broker_form_view">
<field name="name">mail.broker.form (in mail_broker_telegram)</field>
<field name="model">mail.broker</field>
<field name="inherit_id" ref="mail_broker.mail_broker_form_view" />
<field name="arch" type="xml">
<field name="has_new_channel_security" position="after">
<field
name="telegram_security_key"
attrs="{'invisible': ['|', ('broker_type', '!=', 'telegram'), ('has_new_channel_security', '=', False)], 'required': [('broker_type', '=', 'telegram'), ('has_new_channel_security', '=', True)]}"
/>
</field>
</field>
</record>
</odoo>

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2021 Creu Blanca
Copyright 2024 Dixmit
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record model="ir.ui.view" id="mail_gateway_form_view">
<field name="name">mail.gateway.form (in mail_gateway_telegram)</field>
<field name="model">mail.gateway</field>
<field name="inherit_id" ref="mail_gateway.mail_gateway_form_view" />
<field name="arch" type="xml">
<field name="has_new_channel_security" position="after">
<field
name="telegram_security_key"
attrs="{'invisible': ['|', ('gateway_type', '!=', 'telegram'), ('has_new_channel_security', '=', False)], 'required': [('gateway_type', '=', 'telegram'), ('has_new_channel_security', '=', True)]}"
/>
</field>
<notebook position="inside">
<page
name="telegram"
string="Telegram configuration"
attrs="{'invisible': [('gateway_type', '!=', 'telegram')]}"
>
<div>
<ol>
<li>Access Telegram and start a converstation with <a
href="https://t.me/BotFather"
target="_blank"
rel="noopener noreferrer"
>BotFather</a>.</li>
<li
>Create a bot using the command /newbot. The system will ask for a bot name. Remember that it needs to end with the word bot.</li>
<li>Copy the <b
>token given by BotFather to access the HTTP API</b> to the token field.</li>
<li
>Define a Webhook key and a webhook secret of your choice in its corresponding field, in order to secure the connection.</li>
<li
>Press the save button and the integrate webhook smart button will appear.</li>
<li>Press the Integrate webhook button.</li>
</ol>
</div>
<div>
If you want to add an extra layer of security, you can check <b
>Has New Channel Security</b>
and define a <b>Telegram security key</b>.
New chats will be created only with the command <b
>/start SECURITY_KEY</b>.
</div>
</page>
</notebook>
</field>
</record>
</odoo>

View File

@ -1,4 +1,7 @@
# generated from manifests external_dependencies
cairosvg
cryptography<37
extract_msg
lottie
premailer
python-telegram-bot

View File

@ -0,0 +1 @@
../../../../mail_gateway_telegram

View File

@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)