mirror of https://github.com/OCA/social.git
391 lines
14 KiB
Python
391 lines
14 KiB
Python
# 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.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))
|