When creating a new user, the linked partner may not yet have an email address (not mandatory field). When logging in for the first time, after some time an error comes up "Unable to post message, please configure the sender's email address." While the user should set up an email, this popup coming from no specific action is confusing. With this patch, the message will be posted as the system (which has an email) The user will be warned of having no email when trying to reply. Closes #26601
223 lines
11 KiB
Python
223 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import itertools
|
|
import random
|
|
|
|
from odoo import models, _
|
|
|
|
|
|
class MailBot(models.AbstractModel):
|
|
_name = 'mail.bot'
|
|
|
|
def _apply_logic(self, record, values, command=None):
|
|
""" Apply bot logic to generate an answer (or not) for the user
|
|
The logic will only be applied if odoobot is in a chat with a user or
|
|
if someone pinged odoobot.
|
|
|
|
:param record: the mail_thread (or mail_channel) where the user
|
|
message was posted/odoobot will answer.
|
|
:param values: msg_values of the message_post or other values needed by logic
|
|
:param command: the name of the called command if the logic is not triggered by a message_post
|
|
"""
|
|
odoobot_id = self.env['ir.model.data'].xmlid_to_res_id("mail_bot.partner_odoobot")
|
|
if len(record) != 1 or values.get("author_id") == odoobot_id:
|
|
return
|
|
if self._is_bot_pinged(values) or self._is_bot_in_private_channel(record):
|
|
body = values.get("body", "").replace(u'\xa0', u' ').strip().lower().strip(".?!")
|
|
answer = self._get_answer(record, body, values, command)
|
|
if answer:
|
|
message_type = values.get('message_type', 'comment')
|
|
subtype_id = values.get('subtype_id', self.env['ir.model.data'].xmlid_to_res_id('mail.mt_comment'))
|
|
record.with_context({"mail_create_nosubscribe": True}).sudo().message_post(body=answer, author_id=odoobot_id, message_type=message_type, subtype_id=subtype_id)
|
|
|
|
def _get_answer(self, record, body, values, command=False):
|
|
# onboarding
|
|
odoobot_state = self.env.user.odoobot_state
|
|
if self._is_bot_in_private_channel(record):
|
|
# main flow
|
|
if odoobot_state == 'onboarding_emoji' and self._body_contains_emoji(body):
|
|
self.env.user.odoobot_state = "onboarding_attachement"
|
|
return _("Great! 👍<br/>Now, try to <b>send an attachment</b>, like a picture of your cute dog...")
|
|
elif odoobot_state == 'onboarding_attachement' and values.get("attachment_ids"):
|
|
self.env.user.odoobot_state = "onboarding_command"
|
|
return _("Not a cute dog, but you get it 😊<br/>To access special features, <b>start your sentence with '/'</b>. Try to get help.")
|
|
elif odoobot_state == 'onboarding_command' and command == 'help':
|
|
self.env.user.odoobot_state = "onboarding_ping"
|
|
return _("Wow you are a natural!<br/>Ping someone to grab its attention with @nameoftheuser. <b>Try to ping me using @OdooBot</b> in a sentence.")
|
|
elif odoobot_state == 'onboarding_ping' and self._is_bot_pinged(values):
|
|
self.env.user.odoobot_state = "idle"
|
|
return _("Yep, I am here! 🎉 <br/>You finished the tour, you can <b>close this chat window</b>. Enjoy discovering Odoo.")
|
|
elif odoobot_state == "idle" and (_('start the tour') in body.lower()):
|
|
self.env.user.odoobot_state = "onboarding_emoji"
|
|
return _("To start, try to send me an emoji :)")
|
|
# easter eggs
|
|
elif odoobot_state == "idle" and body in ['❤️', _('i love you'), _('love')]:
|
|
return _("Aaaaaw that's really cute but, you know, bots don't work that way. You're too human for me! Let's keep it professional ❤️")
|
|
elif odoobot_state == "idle" and (('help' in body) or _('help') in body):
|
|
return _("I'm just a bot... :( You can check <a href=\"https://www.odoo.com/page/docs\">our documentation</a>) for more information!")
|
|
elif _('fuck') in body or "fuck" in body:
|
|
return _("That's not nice! I'm a bot but I have feelings... 💔")
|
|
else:
|
|
#repeat question
|
|
if odoobot_state == 'onboarding_emoji':
|
|
return _("Not exactly. To continue the tour, send an emoji, <b>type \":)\"</b> and press enter.")
|
|
elif odoobot_state == 'onboarding_attachement':
|
|
return _("To <b>send an attachment</b>, click the 📎 icon on the right, and select a file.")
|
|
elif odoobot_state == 'onboarding_command':
|
|
return _("Not sure wat you are doing. Please press / and wait for the propositions. Select \"help\" and press enter")
|
|
elif odoobot_state == 'onboarding_ping':
|
|
return _("Sorry, I am not listening. To get someone's attention, <b>ping him</b>. Write \"@odoobot\" and select me.")
|
|
return random.choice([
|
|
_("I'm not smart enough to answer your question.<br/>To follow my guide, ask") + ": <b>"+_('start the tour') + "</b>",
|
|
_("Hmmm..."),
|
|
_("I'm afraid I don't understand. Sorry!"),
|
|
_("Sorry I'm sleepy. Or not! Maybe I'm just trying to hide my unawareness of human language...<br/>I can show you features if you write")+ ": '<b>"+_('start the tour')+"</b>'.",
|
|
])
|
|
elif self._is_bot_pinged(values):
|
|
return random.choice([_("Yep, OdooBot is in the place!"), _("Pong.")])
|
|
return False
|
|
|
|
def _body_contains_emoji(self, body):
|
|
# coming from https://unicode.org/emoji/charts/full-emoji-list.html
|
|
emoji_list = itertools.chain(
|
|
range(0x231A, 0x231c),
|
|
range(0x23E9, 0x23f4),
|
|
range(0x23F8, 0x23fb),
|
|
range(0x25AA, 0x25ac),
|
|
range(0x25FB, 0x25ff),
|
|
range(0x2600, 0x2605),
|
|
range(0x2614, 0x2616),
|
|
range(0x2622, 0x2624),
|
|
range(0x262E, 0x2630),
|
|
range(0x2638, 0x263b),
|
|
range(0x2648, 0x2654),
|
|
range(0x265F, 0x2661),
|
|
range(0x2665, 0x2667),
|
|
range(0x267E, 0x2680),
|
|
range(0x2692, 0x2698),
|
|
range(0x269B, 0x269d),
|
|
range(0x26A0, 0x26a2),
|
|
range(0x26AA, 0x26ac),
|
|
range(0x26B0, 0x26b2),
|
|
range(0x26BD, 0x26bf),
|
|
range(0x26C4, 0x26c6),
|
|
range(0x26D3, 0x26d5),
|
|
range(0x26E9, 0x26eb),
|
|
range(0x26F0, 0x26f6),
|
|
range(0x26F7, 0x26fb),
|
|
range(0x2708, 0x270a),
|
|
range(0x270A, 0x270c),
|
|
range(0x270C, 0x270e),
|
|
range(0x2733, 0x2735),
|
|
range(0x2753, 0x2756),
|
|
range(0x2763, 0x2765),
|
|
range(0x2795, 0x2798),
|
|
range(0x2934, 0x2936),
|
|
range(0x2B05, 0x2b08),
|
|
range(0x2B1B, 0x2b1d),
|
|
range(0x1F170, 0x1f172),
|
|
range(0x1F191, 0x1f19b),
|
|
range(0x1F1E6, 0x1f200),
|
|
range(0x1F201, 0x1f203),
|
|
range(0x1F232, 0x1f23b),
|
|
range(0x1F250, 0x1f252),
|
|
range(0x1F300, 0x1f321),
|
|
range(0x1F324, 0x1f32d),
|
|
range(0x1F32D, 0x1f330),
|
|
range(0x1F330, 0x1f336),
|
|
range(0x1F337, 0x1f37d),
|
|
range(0x1F37E, 0x1f380),
|
|
range(0x1F380, 0x1f394),
|
|
range(0x1F396, 0x1f398),
|
|
range(0x1F399, 0x1f39c),
|
|
range(0x1F39E, 0x1f3a0),
|
|
range(0x1F3A0, 0x1f3c5),
|
|
range(0x1F3C6, 0x1f3cb),
|
|
range(0x1F3CB, 0x1f3cf),
|
|
range(0x1F3CF, 0x1f3d4),
|
|
range(0x1F3D4, 0x1f3e0),
|
|
range(0x1F3E0, 0x1f3f1),
|
|
range(0x1F3F3, 0x1f3f6),
|
|
range(0x1F3F8, 0x1f400),
|
|
range(0x1F400, 0x1f43f),
|
|
range(0x1F442, 0x1f4f8),
|
|
range(0x1F4F9, 0x1f4fd),
|
|
range(0x1F500, 0x1f53e),
|
|
range(0x1F549, 0x1f54b),
|
|
range(0x1F54B, 0x1f54f),
|
|
range(0x1F550, 0x1f568),
|
|
range(0x1F56F, 0x1f571),
|
|
range(0x1F573, 0x1f57a),
|
|
range(0x1F58A, 0x1f58e),
|
|
range(0x1F595, 0x1f597),
|
|
range(0x1F5B1, 0x1f5b3),
|
|
range(0x1F5C2, 0x1f5c5),
|
|
range(0x1F5D1, 0x1f5d4),
|
|
range(0x1F5DC, 0x1f5df),
|
|
range(0x1F5FB, 0x1f600),
|
|
range(0x1F601, 0x1f611),
|
|
range(0x1F612, 0x1f615),
|
|
range(0x1F61C, 0x1f61f),
|
|
range(0x1F620, 0x1f626),
|
|
range(0x1F626, 0x1f628),
|
|
range(0x1F628, 0x1f62c),
|
|
range(0x1F62E, 0x1f630),
|
|
range(0x1F630, 0x1f634),
|
|
range(0x1F635, 0x1f641),
|
|
range(0x1F641, 0x1f643),
|
|
range(0x1F643, 0x1f645),
|
|
range(0x1F645, 0x1f650),
|
|
range(0x1F680, 0x1f6c6),
|
|
range(0x1F6CB, 0x1f6d0),
|
|
range(0x1F6D1, 0x1f6d3),
|
|
range(0x1F6E0, 0x1f6e6),
|
|
range(0x1F6EB, 0x1f6ed),
|
|
range(0x1F6F4, 0x1f6f7),
|
|
range(0x1F6F7, 0x1f6f9),
|
|
range(0x1F910, 0x1f919),
|
|
range(0x1F919, 0x1f91f),
|
|
range(0x1F920, 0x1f928),
|
|
range(0x1F928, 0x1f930),
|
|
range(0x1F931, 0x1f933),
|
|
range(0x1F933, 0x1f93b),
|
|
range(0x1F93C, 0x1f93f),
|
|
range(0x1F940, 0x1f946),
|
|
range(0x1F947, 0x1f94c),
|
|
range(0x1F94D, 0x1f950),
|
|
range(0x1F950, 0x1f95f),
|
|
range(0x1F95F, 0x1f96c),
|
|
range(0x1F96C, 0x1f971),
|
|
range(0x1F973, 0x1f977),
|
|
range(0x1F97C, 0x1f980),
|
|
range(0x1F980, 0x1f985),
|
|
range(0x1F985, 0x1f992),
|
|
range(0x1F992, 0x1f998),
|
|
range(0x1F998, 0x1f9a3),
|
|
range(0x1F9B0, 0x1f9ba),
|
|
range(0x1F9C1, 0x1f9c3),
|
|
range(0x1F9D0, 0x1f9e7),
|
|
range(0x1F9E7, 0x1fa00),
|
|
[0x2328, 0x23cf, 0x24c2, 0x25b6, 0x25c0, 0x260e, 0x2611, 0x2618, 0x261d, 0x2620, 0x2626,
|
|
0x262a, 0x2640, 0x2642, 0x2663, 0x2668, 0x267b, 0x2699, 0x26c8, 0x26ce, 0x26cf,
|
|
0x26d1, 0x26fd, 0x2702, 0x2705, 0x270f, 0x2712, 0x2714, 0x2716, 0x271d, 0x2721, 0x2728, 0x2744, 0x2747, 0x274c,
|
|
0x274e, 0x2757, 0x27a1, 0x27b0, 0x27bf, 0x2b50, 0x2b55, 0x3030, 0x303d, 0x3297, 0x3299, 0x1f004, 0x1f0cf, 0x1f17e,
|
|
0x1f17f, 0x1f18e, 0x1f21a, 0x1f22f, 0x1f321, 0x1f336, 0x1f37d, 0x1f3c5, 0x1f3f7, 0x1f43f, 0x1f440, 0x1f441, 0x1f4f8,
|
|
0x1f4fd, 0x1f4ff, 0x1f57a, 0x1f587, 0x1f590, 0x1f5a4, 0x1f5a5, 0x1f5a8, 0x1f5bc, 0x1f5e1, 0x1f5e3, 0x1f5e8, 0x1f5ef,
|
|
0x1f5f3, 0x1f5fa, 0x1f600, 0x1f611, 0x1f615, 0x1f616, 0x1f617, 0x1f618, 0x1f619, 0x1f61a, 0x1f61b, 0x1f61f, 0x1f62c,
|
|
0x1f62d, 0x1f634, 0x1f6d0, 0x1f6e9, 0x1f6f0, 0x1f6f3, 0x1f6f9, 0x1f91f, 0x1f930, 0x1f94c, 0x1f97a, 0x1f9c0]
|
|
)
|
|
if any(chr(emoji) in body for emoji in emoji_list):
|
|
return True
|
|
return False
|
|
|
|
def _is_bot_pinged(self, values):
|
|
odoobot_id = self.env['ir.model.data'].xmlid_to_res_id("mail_bot.partner_odoobot")
|
|
return (4, odoobot_id) in values.get('partner_ids', [])
|
|
|
|
def _is_bot_in_private_channel(self, record):
|
|
odoobot_id = self.env['ir.model.data'].xmlid_to_res_id("mail_bot.partner_odoobot")
|
|
if record._name == 'mail.channel' and record.channel_type == 'chat':
|
|
return odoobot_id in record.with_context(active_test=False).channel_partner_ids.ids
|
|
return False
|