diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/mail_bot/models/mail_bot.py | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/mail_bot/models/mail_bot.py')
| -rw-r--r-- | addons/mail_bot/models/mail_bot.py | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/addons/mail_bot/models/mail_bot.py b/addons/mail_bot/models/mail_bot.py new file mode 100644 index 00000000..a548647e --- /dev/null +++ b/addons/mail_bot/models/mail_bot.py @@ -0,0 +1,238 @@ +# -*- 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' + _description = '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("base.partner_root") + 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_command" + self.env.user.odoobot_failed = False + return _("Great! 👍<br/>To access special commands, <b>start your sentence with</b> <span class=\"o_odoobot_command\">/</span>. Try getting help.") + elif odoobot_state == 'onboarding_command' and command == 'help': + self.env.user.odoobot_state = "onboarding_ping" + self.env.user.odoobot_failed = False + return _("Wow you are a natural!<br/>Ping someone with @username to grab their attention. <b>Try to ping me using</b> <span class=\"o_odoobot_command\">@OdooBot</span> in a sentence.") + elif odoobot_state == 'onboarding_ping' and self._is_bot_pinged(values): + self.env.user.odoobot_state = "onboarding_attachement" + self.env.user.odoobot_failed = False + return _("Yep, I am here! 🎉 <br/>Now, try <b>sending 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 = "idle" + self.env.user.odoobot_failed = False + return _("I am a simple bot, but if that's a dog, he is the cutest 😊 <br/>Congratulations, you finished this tour. You can now <b>close this chat window</b>. Enjoy discovering Odoo.") + elif odoobot_state in (False, "idle", "not_initialized") 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 _('fuck') in body or "fuck" in body: + return _("That's not nice! I'm a bot but I have feelings... 💔") + # help message + elif self._is_help_requested(body) or odoobot_state == 'idle': + return _("Unfortunately, I'm just a bot 😞 I don't understand! If you need help discovering our product, please check " + "<a href=\"https://www.odoo.com/page/docs\" target=\"_blank\">our documentation</a> or " + "<a href=\"https://www.odoo.com/slides\" target=\"_blank\">our videos</a>.") + else: + # repeat question + if odoobot_state == 'onboarding_emoji': + self.env.user.odoobot_failed = True + return _("Not exactly. To continue the tour, send an emoji: <b>type</b> <span class=\"o_odoobot_command\">:)</span> and press enter.") + elif odoobot_state == 'onboarding_attachement': + self.env.user.odoobot_failed = True + return _("To <b>send an attachment</b>, click on the <i class=\"fa fa-paperclip\" aria-hidden=\"true\"></i> icon and select a file.") + elif odoobot_state == 'onboarding_command': + self.env.user.odoobot_failed = True + return _("Not sure what you are doing. Please, type <span class=\"o_odoobot_command\">/</span> and wait for the propositions. Select <span class=\"o_odoobot_command\">help</span> and press enter") + elif odoobot_state == 'onboarding_ping': + self.env.user.odoobot_failed = True + return _("Sorry, I am not listening. To get someone's attention, <b>ping him</b>. Write <span class=\"o_odoobot_command\">@OdooBot</span> and select me.") + return random.choice([ + _("I'm not smart enough to answer your question.<br/>To follow my guide, ask: <span class=\"o_odoobot_command\">start the tour</span>."), + _("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: <span class=\"o_odoobot_command\">start the tour</span>.") + ]) + 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("base.partner_root") + return 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("base.partner_root") + 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 + + def _is_help_requested(self, body): + """Returns whether a message linking to the documentation and videos + should be sent back to the user. + """ + return any(token in body for token in ['help', _('help'), '?']) or self.env.user.odoobot_failed |
