mirror of
https://github.com/bringout/oca-ocb-mail.git
synced 2026-04-22 19:42:06 +02:00
Initial commit: Mail packages
This commit is contained in:
commit
4e53507711
1948 changed files with 751201 additions and 0 deletions
|
|
@ -0,0 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import chatbot
|
||||
from . import main
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.tools import get_lang, is_html_empty, plaintext2html
|
||||
|
||||
|
||||
class LivechatChatbotScriptController(http.Controller):
|
||||
@http.route('/chatbot/restart', type="json", auth="public", cors="*")
|
||||
def chatbot_restart(self, channel_uuid, chatbot_script_id):
|
||||
chatbot_language = self._get_chatbot_language()
|
||||
mail_channel = request.env['mail.channel'].sudo().with_context(lang=chatbot_language).search([('uuid', '=', channel_uuid)], limit=1)
|
||||
chatbot = request.env['chatbot.script'].browse(chatbot_script_id)
|
||||
if not mail_channel or not chatbot.exists():
|
||||
return None
|
||||
|
||||
return mail_channel._chatbot_restart(chatbot).message_format()[0]
|
||||
|
||||
@http.route('/chatbot/post_welcome_steps', type="json", auth="public", cors="*")
|
||||
def chatbot_post_welcome_steps(self, channel_uuid, chatbot_script_id):
|
||||
mail_channel = request.env['mail.channel'].sudo().search([('uuid', '=', channel_uuid)], limit=1)
|
||||
chatbot_language = self._get_chatbot_language()
|
||||
chatbot = request.env['chatbot.script'].sudo().with_context(lang=chatbot_language).browse(chatbot_script_id)
|
||||
if not mail_channel or not chatbot.exists():
|
||||
return None
|
||||
|
||||
return chatbot._post_welcome_steps(mail_channel).message_format()
|
||||
|
||||
@http.route('/chatbot/answer/save', type="json", auth="public", cors="*")
|
||||
def chatbot_save_answer(self, channel_uuid, message_id, selected_answer_id):
|
||||
mail_channel = request.env['mail.channel'].sudo().search([('uuid', '=', channel_uuid)], limit=1)
|
||||
chatbot_message = request.env['chatbot.message'].sudo().search([
|
||||
('mail_message_id', '=', message_id),
|
||||
('mail_channel_id', '=', mail_channel.id),
|
||||
], limit=1)
|
||||
selected_answer = request.env['chatbot.script.answer'].sudo().browse(selected_answer_id)
|
||||
|
||||
if not mail_channel or not chatbot_message or not selected_answer.exists():
|
||||
return
|
||||
|
||||
if selected_answer in chatbot_message.script_step_id.answer_ids:
|
||||
chatbot_message.write({'user_script_answer_id': selected_answer_id})
|
||||
|
||||
@http.route('/chatbot/step/trigger', type="json", auth="public", cors="*")
|
||||
def chatbot_trigger_step(self, channel_uuid, chatbot_script_id=None):
|
||||
chatbot_language = self._get_chatbot_language()
|
||||
mail_channel = request.env['mail.channel'].sudo().with_context(lang=chatbot_language).search([('uuid', '=', channel_uuid)], limit=1)
|
||||
if not mail_channel:
|
||||
return None
|
||||
|
||||
next_step = False
|
||||
if mail_channel.chatbot_current_step_id:
|
||||
chatbot = mail_channel.chatbot_current_step_id.chatbot_script_id
|
||||
user_messages = mail_channel.message_ids.filtered(
|
||||
lambda message: message.author_id != chatbot.operator_partner_id
|
||||
)
|
||||
user_answer = request.env['mail.message'].sudo()
|
||||
if user_messages:
|
||||
user_answer = user_messages.sorted(lambda message: message.id)[-1]
|
||||
next_step = mail_channel.chatbot_current_step_id._process_answer(mail_channel, user_answer.body)
|
||||
elif chatbot_script_id: # when restarting, we don't have a "current step" -> set "next" as first step of the script
|
||||
chatbot = request.env['chatbot.script'].sudo().with_context(lang=chatbot_language).browse(chatbot_script_id)
|
||||
if chatbot.exists():
|
||||
next_step = chatbot.script_step_ids[:1]
|
||||
|
||||
if not next_step:
|
||||
return None
|
||||
|
||||
posted_message = next_step._process_step(mail_channel)
|
||||
return {
|
||||
'chatbot_posted_message': posted_message.message_format()[0] if posted_message else None,
|
||||
'chatbot_step': {
|
||||
'chatbot_operator_found': next_step.step_type == 'forward_operator' and len(
|
||||
mail_channel.channel_member_ids) > 2,
|
||||
'chatbot_script_step_id': next_step.id,
|
||||
'chatbot_step_answers': [{
|
||||
'id': answer.id,
|
||||
'label': answer.name,
|
||||
'redirect_link': answer.redirect_link,
|
||||
} for answer in next_step.answer_ids],
|
||||
'chatbot_step_is_last': next_step._is_last_step(mail_channel),
|
||||
'chatbot_step_message': plaintext2html(next_step.message) if not is_html_empty(next_step.message) else False,
|
||||
'chatbot_step_type': next_step.step_type,
|
||||
}
|
||||
}
|
||||
|
||||
@http.route('/chatbot/step/validate_email', type="json", auth="public", cors="*")
|
||||
def chatbot_validate_email(self, channel_uuid):
|
||||
mail_channel = request.env['mail.channel'].sudo().search([('uuid', '=', channel_uuid)], limit=1)
|
||||
if not mail_channel or not mail_channel.chatbot_current_step_id:
|
||||
return None
|
||||
|
||||
chatbot = mail_channel.chatbot_current_step_id.chatbot_script_id
|
||||
user_messages = mail_channel.message_ids.filtered(
|
||||
lambda message: message.author_id != chatbot.operator_partner_id
|
||||
)
|
||||
|
||||
if user_messages:
|
||||
user_answer = user_messages.sorted(lambda message: message.id)[-1]
|
||||
result = chatbot._validate_email(user_answer.body, mail_channel)
|
||||
|
||||
if result['posted_message']:
|
||||
result['posted_message'] = result['posted_message'].message_format()[0]
|
||||
|
||||
return result
|
||||
|
||||
def _get_chatbot_language(self):
|
||||
return request.httprequest.cookies.get('frontend_lang', request.env.user.lang or get_lang(request.env).code)
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from odoo import http, tools, _
|
||||
from odoo.http import request
|
||||
from odoo.addons.base.models.assetsbundle import AssetsBundle
|
||||
|
||||
|
||||
class LivechatController(http.Controller):
|
||||
|
||||
# Note: the `cors` attribute on many routes is meant to allow the livechat
|
||||
# to be embedded in an external website.
|
||||
|
||||
@http.route('/im_livechat/external_lib.<any(css,js):ext>', type='http', auth='public')
|
||||
def livechat_lib(self, ext, **kwargs):
|
||||
# _get_asset return the bundle html code (script and link list) but we want to use the attachment content
|
||||
bundle = 'im_livechat.external_lib'
|
||||
files, _ = request.env["ir.qweb"]._get_asset_content(bundle)
|
||||
asset = AssetsBundle(bundle, files)
|
||||
|
||||
mock_attachment = getattr(asset, ext)()
|
||||
if isinstance(mock_attachment, list): # suppose that CSS asset will not required to be split in pages
|
||||
mock_attachment = mock_attachment[0]
|
||||
|
||||
stream = request.env['ir.binary']._get_stream_from(mock_attachment)
|
||||
return stream.get_response()
|
||||
|
||||
@http.route('/im_livechat/load_templates', type='json', auth='none', cors="*")
|
||||
def load_templates(self, **kwargs):
|
||||
templates = self._livechat_templates_get()
|
||||
return [tools.file_open(tmpl, 'rb').read() for tmpl in templates]
|
||||
|
||||
def _livechat_templates_get(self):
|
||||
return [
|
||||
'im_livechat/static/src/legacy/widgets/feedback/feedback.xml',
|
||||
'im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.xml',
|
||||
'im_livechat/static/src/legacy/widgets/public_livechat_view/public_livechat_view.xml',
|
||||
'im_livechat/static/src/legacy/public_livechat_chatbot.xml',
|
||||
]
|
||||
|
||||
@http.route('/im_livechat/support/<int:channel_id>', type='http', auth='public')
|
||||
def support_page(self, channel_id, **kwargs):
|
||||
channel = request.env['im_livechat.channel'].sudo().browse(channel_id)
|
||||
return request.render('im_livechat.support_page', {'channel': channel})
|
||||
|
||||
@http.route('/im_livechat/loader/<int:channel_id>', type='http', auth='public')
|
||||
def loader(self, channel_id, **kwargs):
|
||||
username = kwargs.get("username", _("Visitor"))
|
||||
channel = request.env['im_livechat.channel'].sudo().browse(channel_id)
|
||||
info = channel.get_livechat_info(username=username)
|
||||
return request.render('im_livechat.loader', {'info': info}, headers=[('Content-Type', 'application/javascript')])
|
||||
|
||||
@http.route('/im_livechat/init', type='json', auth="public", cors="*")
|
||||
def livechat_init(self, channel_id):
|
||||
operator_available = len(request.env['im_livechat.channel'].sudo().browse(channel_id)._get_available_users())
|
||||
rule = {}
|
||||
# find the country from the request
|
||||
country_id = False
|
||||
country_code = request.geoip.get('country_code')
|
||||
if country_code:
|
||||
country_id = request.env['res.country'].sudo().search([('code', '=', country_code)], limit=1).id
|
||||
# extract url
|
||||
url = request.httprequest.headers.get('Referer')
|
||||
# find the first matching rule for the given country and url
|
||||
matching_rule = request.env['im_livechat.channel.rule'].sudo().match_rule(channel_id, url, country_id)
|
||||
if matching_rule and (not matching_rule.chatbot_script_id or matching_rule.chatbot_script_id.script_step_ids):
|
||||
frontend_lang = request.httprequest.cookies.get('frontend_lang', request.env.user.lang or 'en_US')
|
||||
matching_rule = matching_rule.with_context(lang=frontend_lang)
|
||||
rule = {
|
||||
'action': matching_rule.action,
|
||||
'auto_popup_timer': matching_rule.auto_popup_timer,
|
||||
'regex_url': matching_rule.regex_url,
|
||||
}
|
||||
if matching_rule.chatbot_script_id.active and (not matching_rule.chatbot_only_if_no_operator or
|
||||
(not operator_available and matching_rule.chatbot_only_if_no_operator)) and matching_rule.chatbot_script_id.script_step_ids:
|
||||
chatbot_script = matching_rule.chatbot_script_id
|
||||
rule.update({'chatbot': chatbot_script._format_for_frontend()})
|
||||
return {
|
||||
'available_for_me': (rule and rule.get('chatbot'))
|
||||
or operator_available and (not rule or rule['action'] != 'hide_button'),
|
||||
'rule': rule,
|
||||
}
|
||||
|
||||
@http.route('/im_livechat/operator/<int:operator_id>/avatar',
|
||||
type='http', auth="public", cors="*")
|
||||
def livechat_operator_get_avatar(self, operator_id):
|
||||
""" Custom route allowing to retrieve an operator's avatar.
|
||||
|
||||
This is done to bypass more complicated rules, notably 'website_published' when the website
|
||||
module is installed.
|
||||
|
||||
Here, we assume that if you are a member of at least one im_livechat.channel, then it's ok
|
||||
to make your avatar publicly available.
|
||||
|
||||
We also make the chatbot operator avatars publicly available. """
|
||||
|
||||
is_livechat_member = False
|
||||
operator = request.env['res.partner'].sudo().browse(operator_id)
|
||||
if operator.exists():
|
||||
is_livechat_member = bool(request.env['im_livechat.channel'].sudo().search_count([
|
||||
('user_ids', 'in', operator.user_ids.ids)
|
||||
]))
|
||||
|
||||
if not is_livechat_member:
|
||||
# we don't put chatbot operators as livechat members (because we don't have a user_id for them)
|
||||
is_livechat_member = bool(request.env['chatbot.script'].sudo().search_count([
|
||||
('operator_partner_id', 'in', operator.ids)
|
||||
]))
|
||||
|
||||
return request.env['ir.binary']._get_image_stream_from(
|
||||
operator if is_livechat_member else request.env['res.partner'],
|
||||
field_name='avatar_128',
|
||||
placeholder='mail/static/src/img/smiley/avatar.jpg',
|
||||
).get_response()
|
||||
|
||||
@http.route('/im_livechat/get_session', type="json", auth='public', cors="*")
|
||||
def get_session(self, channel_id, anonymous_name, previous_operator_id=None, chatbot_script_id=None, persisted=True, **kwargs):
|
||||
user_id = None
|
||||
country_id = None
|
||||
# if the user is identifiy (eg: portal user on the frontend), don't use the anonymous name. The user will be added to session.
|
||||
if request.session.uid:
|
||||
user_id = request.env.user.id
|
||||
country_id = request.env.user.country_id.id
|
||||
else:
|
||||
# if geoip, add the country name to the anonymous name
|
||||
if request.geoip:
|
||||
# get the country of the anonymous person, if any
|
||||
country_code = request.geoip.get('country_code', "")
|
||||
country = request.env['res.country'].sudo().search([('code', '=', country_code)], limit=1) if country_code else None
|
||||
if country:
|
||||
country_id = country.id
|
||||
|
||||
if previous_operator_id:
|
||||
previous_operator_id = int(previous_operator_id)
|
||||
|
||||
chatbot_script = False
|
||||
if chatbot_script_id:
|
||||
frontend_lang = request.httprequest.cookies.get('frontend_lang', request.env.user.lang or 'en_US')
|
||||
chatbot_script = request.env['chatbot.script'].sudo().with_context(lang=frontend_lang).browse(chatbot_script_id)
|
||||
|
||||
return request.env["im_livechat.channel"].with_context(lang=False).sudo().browse(channel_id)._open_livechat_mail_channel(
|
||||
anonymous_name,
|
||||
previous_operator_id=previous_operator_id,
|
||||
chatbot_script=chatbot_script,
|
||||
user_id=user_id,
|
||||
country_id=country_id,
|
||||
persisted=persisted
|
||||
)
|
||||
|
||||
@http.route('/im_livechat/feedback', type='json', auth='public', cors="*")
|
||||
def feedback(self, uuid, rate, reason=None, **kwargs):
|
||||
channel = request.env['mail.channel'].sudo().search([('uuid', '=', uuid)], limit=1)
|
||||
if channel:
|
||||
# limit the creation : only ONE rating per session
|
||||
values = {
|
||||
'rating': rate,
|
||||
'consumed': True,
|
||||
'feedback': reason,
|
||||
'is_internal': False,
|
||||
}
|
||||
if not channel.rating_ids:
|
||||
values.update({
|
||||
'res_id': channel.id,
|
||||
'res_model_id': request.env['ir.model']._get_id('mail.channel'),
|
||||
})
|
||||
# find the partner (operator)
|
||||
if channel.channel_partner_ids:
|
||||
values['rated_partner_id'] = channel.channel_partner_ids[0].id
|
||||
# if logged in user, set its partner on rating
|
||||
values['partner_id'] = request.env.user.partner_id.id if request.session.uid else False
|
||||
# create the rating
|
||||
rating = request.env['rating.rating'].sudo().create(values)
|
||||
else:
|
||||
rating = channel.rating_ids[0]
|
||||
rating.write(values)
|
||||
return rating.id
|
||||
return False
|
||||
|
||||
@http.route('/im_livechat/history', type="json", auth="public", cors="*")
|
||||
def history_pages(self, pid, channel_uuid, page_history=None):
|
||||
partner_ids = (pid, request.env.user.partner_id.id)
|
||||
channel = request.env['mail.channel'].sudo().search([('uuid', '=', channel_uuid), ('channel_partner_ids', 'in', partner_ids)])
|
||||
if channel:
|
||||
channel._send_history_message(pid, page_history)
|
||||
return True
|
||||
|
||||
@http.route('/im_livechat/notify_typing', type='json', auth='public', cors="*")
|
||||
def notify_typing(self, uuid, is_typing):
|
||||
""" Broadcast the typing notification of the website user to other channel members
|
||||
:param uuid: (string) the UUID of the livechat channel
|
||||
:param is_typing: (boolean) tells whether the website user is typing or not.
|
||||
"""
|
||||
channel = request.env['mail.channel'].sudo().search([('uuid', '=', uuid)])
|
||||
if not channel:
|
||||
raise NotFound()
|
||||
channel_member = channel.env['mail.channel.member'].search([('channel_id', '=', channel.id), ('partner_id', '=', request.env.user.partner_id.id)])
|
||||
if not channel_member:
|
||||
raise NotFound()
|
||||
channel_member._notify_typing(is_typing=is_typing)
|
||||
|
||||
@http.route('/im_livechat/email_livechat_transcript', type='json', auth='public', cors="*")
|
||||
def email_livechat_transcript(self, uuid, email):
|
||||
channel = request.env['mail.channel'].sudo().search([
|
||||
('channel_type', '=', 'livechat'),
|
||||
('uuid', '=', uuid)], limit=1)
|
||||
if channel:
|
||||
channel._email_livechat_transcript(email)
|
||||
|
||||
@http.route('/im_livechat/visitor_leave_session', type='json', auth="public")
|
||||
def visitor_leave_session(self, uuid):
|
||||
""" Called when the livechat visitor leaves the conversation.
|
||||
This will clean the chat request and warn the operator that the conversation is over.
|
||||
This allows also to re-send a new chat request to the visitor, as while the visitor is
|
||||
in conversation with an operator, it's not possible to send the visitor a chat request."""
|
||||
mail_channel = request.env['mail.channel'].sudo().search([('uuid', '=', uuid)])
|
||||
if mail_channel:
|
||||
mail_channel._close_livechat_session()
|
||||
Loading…
Add table
Add a link
Reference in a new issue